Compare commits
1082 Commits
4cb37f8347
...
1.4.0
Author | SHA1 | Date | |
---|---|---|---|
1093c55c19 | |||
069251d68c | |||
8050a7a8f0 | |||
2f259eacd4 | |||
184f419905 | |||
545d4176b2 | |||
1d9d426601 | |||
bcf353bee6 | |||
795c6985a7 | |||
d923490ff3 | |||
de987f1fee | |||
b291b989b1 | |||
ba85c694ba | |||
ae2891f0ac | |||
346b41cc26 | |||
3d980de05c | |||
4db65bcbad | |||
baa4f68004 | |||
dd3c8e51d2 | |||
42621e33a2 | |||
4544c0768d | |||
0cda597a2d | |||
c3547a49eb | |||
579e55af08 | |||
82ed07e62c | |||
e10a4972af | |||
e17de299f1 | |||
ab6a965ef1 | |||
c09baf0204 | |||
0d9cab032b | |||
3a132e2cd9 | |||
f9209e8bc7 | |||
2429a94c7d | |||
7acb73f9da | |||
87108e9d05 | |||
faf441b338 | |||
cad845267c | |||
2647bbd522 | |||
ad54494df0 | |||
95ac6a4277 | |||
8cca6e3a9e | |||
e9298d7b89 | |||
89c3feee18 | |||
59f842b3aa | |||
426f7ab512 | |||
9f1e04f078 | |||
5fe1e39e0d | |||
dd4e6242b5 | |||
86f4a2f00f | |||
3188818247 | |||
ad7f3870d0 | |||
79fbd400e5 | |||
2ac9137068 | |||
56b5c2c210 | |||
3c88a556d3 | |||
f1780cca7a | |||
6fe6c58828 | |||
96c4c59bae | |||
058eb47df7 | |||
e2c059cacd | |||
49554f7b68 | |||
92b1222df3 | |||
5b8da99ba2 | |||
7551a38f49 | |||
09f432f900 | |||
2e6db419b2 | |||
c6a1790b7d | |||
855d5b0062 | |||
a35684d1d2 | |||
f81373b832 | |||
744f448423 | |||
a7b0ace2ef | |||
f4c06da385 | |||
bd7c0a4901 | |||
06fff3b2df | |||
aebd0f3580 | |||
3afc63d8fc | |||
5f7b6ef9eb | |||
581598dabf | |||
0c3c6adaea | |||
84c65419c2 | |||
7930af269d | |||
c2751f9ae0 | |||
21c70225c8 | |||
11fa10214c | |||
048956fd22 | |||
81e63cccd8 | |||
20f6b4f368 | |||
be2699e2e0 | |||
463774a718 | |||
05f03efc64 | |||
f420bc6f6a | |||
a60cf6c288 | |||
10bade6633 | |||
96670857e9 | |||
7a5c55a46e | |||
18aa58b85e | |||
e0168640cf | |||
5899bb17ca | |||
16927fc732 | |||
5129a8de5c | |||
1e556f1011 | |||
20c09ec9ee | |||
279967d26c | |||
56dd268e20 | |||
6668c6ae35 | |||
228e36d3cd | |||
46441aa667 | |||
d7c24c3910 | |||
e94e28ad33 | |||
55b123a095 | |||
e42a009014 | |||
2b25e2bb02 | |||
f345dd366c | |||
c54c91f4bf | |||
5be205a611 | |||
696b6e7b16 | |||
8837811c18 | |||
c4f396af2e | |||
b258800884 | |||
98c753d0e0 | |||
cd0bcf6731 | |||
e196033821 | |||
3dac12c96f | |||
06b936b355 | |||
1c5fb0de18 | |||
5987e62063 | |||
ad9ed13bc4 | |||
bfbaac0c21 | |||
198232a43f | |||
f695f14e14 | |||
506c5354cc | |||
3f92afef13 | |||
48541f117e | |||
873411c875 | |||
b25bd4cb34 | |||
211ead9900 | |||
febf2718ea | |||
e31511c93b | |||
d64d1635d8 | |||
0eb88038a4 | |||
a47da4d957 | |||
0b258b3638 | |||
89b0a6b003 | |||
da9799cb3c | |||
e28d25b137 | |||
8fbbf124df | |||
c415d53708 | |||
86d4f7684f | |||
e89dff1c2f | |||
5ef5103ea9 | |||
5dabf00980 | |||
ce170ff9aa | |||
df73e86c88 | |||
72fe8b8422 | |||
7a4edc69ba | |||
5e6d675cb9 | |||
80575e02c7 | |||
84dafcae87 | |||
ed87241763 | |||
18db1f58ff | |||
552bce4d47 | |||
3bb7978d15 | |||
e771631a04 | |||
5821c5b33b | |||
b1a42e01bf | |||
1b286d0873 | |||
85a9cf17cd | |||
3ef7d175c1 | |||
67fa708edf | |||
b53f4aa770 | |||
bf1e211c22 | |||
39a2f4c303 | |||
8c782b19ce | |||
ec3ab3e055 | |||
525e826857 | |||
e8629b2e1c | |||
fe56fab6fd | |||
66d7d513ae | |||
a00cc52ff0 | |||
13ab073f99 | |||
a2cac47414 | |||
f522e6f9c1 | |||
11ce8cc864 | |||
352b34294c | |||
6428e96c09 | |||
9908253a7e | |||
74c6ee24cf | |||
74d55fb81e | |||
7fc5138fcd | |||
73d7338e7a | |||
189b81106d | |||
223e78ae07 | |||
d20a1460da | |||
429f3a9a94 | |||
a04cfeef86 | |||
8cd442ea25 | |||
8e21cb5a85 | |||
91e38a80f7 | |||
df55cf0450 | |||
dac01ace32 | |||
e39ec70236 | |||
4acfe5f313 | |||
7d3a4c7c63 | |||
08e13a728a | |||
f27e0379c2 | |||
effa2904f8 | |||
9fe4883f91 | |||
e61b883c14 | |||
04ae338864 | |||
bf56b271d7 | |||
daa8dbe04b | |||
7d62d6b1c7 | |||
f45019024b | |||
2c934d4eaf | |||
0965f56719 | |||
80b8cdb356 | |||
8479f8c30b | |||
821ba0edbf | |||
a75a56eb96 | |||
fe5aac0218 | |||
32e3a97b09 | |||
7b70fd30b3 | |||
14776a0334 | |||
0404b52924 | |||
fdd50c3208 | |||
4c927efeef | |||
6b6b70d653 | |||
863b2d46c6 | |||
b972ea1f97 | |||
e220303d06 | |||
0b37f8f2b3 | |||
da414d741f | |||
f1332cecf4 | |||
31af109742 | |||
d4aa3e673f | |||
fffe78a4ed | |||
95f9a2620f | |||
7869ff4478 | |||
c06d09f507 | |||
846eb79c6e | |||
28805ed727 | |||
858217d2a2 | |||
848c342397 | |||
71ad05e051 | |||
5cb329c282 | |||
0ba1a34ee7 | |||
68ee0b3701 | |||
873f8e16f2 | |||
5381b96ad0 | |||
76320251a1 | |||
805cc888a5 | |||
5589b67e24 | |||
e0baa57735 | |||
dd8cbc9da4 | |||
811e723006 | |||
10703067fa | |||
38e1742f15 | |||
487cbd8e54 | |||
b5bc45fa2b | |||
a4e62b3718 | |||
564bae99b1 | |||
c36d36be0b | |||
da5d19cbcb | |||
db8a815dc0 | |||
0b00193bed | |||
8f13047a1f | |||
faa368331c | |||
0eef42b3e6 | |||
e6a5e446ab | |||
43f90251b0 | |||
d57d4ad96a | |||
4542301446 | |||
a4bfb3a7e8 | |||
34a9d202c3 | |||
f5eb4c25b2 | |||
0612c2350d | |||
726a3ac406 | |||
d8e6437241 | |||
5fe2269e98 | |||
68c22966bc | |||
d137ea663a | |||
268e3294a2 | |||
cdacd70bf1 | |||
4412ff14e7 | |||
50edfd5997 | |||
9137758294 | |||
94045d136f | |||
768fd8d640 | |||
926349e04e | |||
ec16910ec6 | |||
20dc72d564 | |||
00c2826d9a | |||
2077becc89 | |||
86008e9cbd | |||
fb0905c44a | |||
4336d90dc0 | |||
7322667a99 | |||
6cb99609cd | |||
bad610be36 | |||
f53c6d0f6d | |||
5fd3123c9b | |||
fa3698aa2b | |||
82a06e57b1 | |||
a09fb4c8cd | |||
91444172aa | |||
745983dfab | |||
58216073d7 | |||
7263b3a8a3 | |||
8cc3adf3c2 | |||
dff794c433 | |||
c89a8e8007 | |||
89588211fd | |||
ab41c96182 | |||
9327c6b377 | |||
036dbdeba3 | |||
b1b127b9f4 | |||
77e09e8a13 | |||
663f496dc7 | |||
85a4158413 | |||
ce566a8928 | |||
9ecd545d1f | |||
11e3b04b46 | |||
a708644809 | |||
e524d4d43d | |||
48b5754dcf | |||
814c4b2beb | |||
2dbee2a058 | |||
ed34ed34cb | |||
5fe2b9e646 | |||
a230edf25a | |||
a7c9ff4cee | |||
6e34e33c00 | |||
a92bfd427f | |||
0803b2c766 | |||
800b5d87cf | |||
bfe3f10535 | |||
176f1c1073 | |||
8f68345bb3 | |||
fe1816156a | |||
d945092153 | |||
cf31f198c8 | |||
b4f3f0ec48 | |||
cfedd4b74e | |||
06362f226c | |||
02822cc70d | |||
f1c5ebca71 | |||
e180c3aa54 | |||
e504393c09 | |||
d6eebc1cab | |||
5a7dc5afd1 | |||
613b904648 | |||
ae7355ba35 | |||
f44b97248f | |||
7d73de2264 | |||
6ba172e2dc | |||
1f9c665d96 | |||
39bc70558a | |||
fbbd809ef5 | |||
01ecc68384 | |||
523c82d72f | |||
4f66af4bbb | |||
2908f6bd4c | |||
35e547268b | |||
ef5dd3dc21 | |||
65926210b2 | |||
cd78a6b12f | |||
8b155eb895 | |||
92825d3b34 | |||
794fb6a733 | |||
23bcdce938 | |||
7bd50c6805 | |||
911ba67459 | |||
85cde78203 | |||
efa805816f | |||
cb73866cdc | |||
b7bbf02dff | |||
9e83ec9adb | |||
0314684064 | |||
5ac063d8c9 | |||
873dc71c08 | |||
09b2ea378b | |||
538265f526 | |||
4dc0f31e69 | |||
1daa0ca219 | |||
318d342d2b | |||
947112ba39 | |||
e54d7cfeb2 | |||
bcb0ce42df | |||
f0b14cfa66 | |||
d1c66318b2 | |||
5f4a09da25 | |||
32ac01dc55 | |||
9884312d47 | |||
3f41fc19d2 | |||
5d953771d7 | |||
94119b1f9f | |||
4841a36968 | |||
319eaadbc2 | |||
df5a185986 | |||
3e210ed217 | |||
0ce5b0d98c | |||
a0b013d44e | |||
9f81c49119 | |||
2363064b1a | |||
419725f13f | |||
4a2ad114d7 | |||
c7d4f2b496 | |||
e8279dd363 | |||
0ed6133698 | |||
2dd01111d9 | |||
40f110e7e5 | |||
b9b2142cc1 | |||
71f746dba6 | |||
f019791fa4 | |||
99313d7a8d | |||
114272471e | |||
7d3576a879 | |||
5ab7ce05bc | |||
99550e7ab3 | |||
bd11d82107 | |||
3482b7dd0a | |||
cb6f15b933 | |||
976a5f0706 | |||
7d35db030c | |||
0c9a9f5ae1 | |||
8219bf6c9d | |||
a0ac0e4839 | |||
fae48f2c5a | |||
518142492f | |||
cc6770278f | |||
8b091e3c79 | |||
10704457b0 | |||
5b9ed1db2c | |||
47d5e40315 | |||
9278668e58 | |||
56235e6ffe | |||
ed6cf305ae | |||
df213c2015 | |||
ea4952bd3e | |||
138f8237fe | |||
a4369c4a00 | |||
42e38e26d9 | |||
073330015c | |||
80e981d40f | |||
6d58f33a67 | |||
a87b313fdf | |||
8b31a96508 | |||
a32fe28a0d | |||
308b5168d8 | |||
62ca58e820 | |||
94f3925127 | |||
b8cafeb99b | |||
61450b442d | |||
7e92760340 | |||
3cb9aa6f59 | |||
a77e01f86f | |||
6f7638d9cf | |||
2b24f29837 | |||
835e5258a5 | |||
84de741f53 | |||
59e34cae4d | |||
4b873a4e44 | |||
80d307b2a5 | |||
9beb9a9d2c | |||
636f6c5b58 | |||
0877fcf7e9 | |||
0faa6a5a18 | |||
fb7fda7ea2 | |||
b6acba9930 | |||
af3a84f0dc | |||
200909fb82 | |||
1d198dd2df | |||
c34dd9f2a4 | |||
2690e17f93 | |||
5edbdd76f4 | |||
e917fbf68d | |||
606477cb0d | |||
8da3a04730 | |||
c6c8bfd045 | |||
0bd057b436 | |||
bb93ef3f1d | |||
79f17a7d57 | |||
b9301e4697 | |||
074d12b99b | |||
1073b2228e | |||
a05cf021c1 | |||
c185c192a7 | |||
77ef69488b | |||
a1625517d1 | |||
32d5008f63 | |||
e765b03d3b | |||
01da3f6fd0 | |||
ad72cc4f7d | |||
1f9070c420 | |||
783f063ef0 | |||
31af310d3d | |||
630223b15a | |||
d64d744f9f | |||
f67051fb15 | |||
4a5991f9db | |||
c0a0bc544a | |||
3f03b8710f | |||
9f8d2acd99 | |||
dc6f9d3f17 | |||
4e358a0541 | |||
8ffffab395 | |||
396566c3be | |||
5cbb081462 | |||
8058369276 | |||
c205a75ce0 | |||
3998325acd | |||
8f410b370d | |||
790cbaf452 | |||
54796badc9 | |||
939424788f | |||
73517733b5 | |||
5565670495 | |||
66b60e02e6 | |||
d98cd846bf | |||
baef2e9287 | |||
dbde9144d9 | |||
1d60e1105a | |||
b1fc59be3a | |||
1e4d6087ff | |||
c45da0f676 | |||
c1daab6069 | |||
372e4c190f | |||
5cc7ba0237 | |||
5042124803 | |||
ce58d13ab5 | |||
61bfffa3ca | |||
0615977024 | |||
2563f6f8e5 | |||
21a50f16ae | |||
f362e3493f | |||
f24e645d55 | |||
26640926a8 | |||
da49b98c60 | |||
ab69d8d174 | |||
2f0a2e282b | |||
b57df0d5bc | |||
6ecaa08782 | |||
f134b6828f | |||
5776a61ff4 | |||
a1bd6a81b3 | |||
f814151d33 | |||
1d1e295af7 | |||
a78f7660e2 | |||
8fa468f735 | |||
65759827c8 | |||
98ded4c748 | |||
18f8e515d8 | |||
8121879165 | |||
066d8b903a | |||
11fd7f8c6c | |||
3b358599ad | |||
7dcde1926e | |||
9e3a61a890 | |||
3080b1c1b7 | |||
8e0af707cf | |||
d5a4a570e3 | |||
8824fcebdf | |||
e52a9c0003 | |||
ebf51da951 | |||
af27b55ad7 | |||
617bbcc724 | |||
72c42f07cb | |||
67b8648a69 | |||
37176458ac | |||
ad1b35dfc7 | |||
cf8014a26b | |||
030bb13396 | |||
fe6a405a41 | |||
97afeeb45d | |||
d23c2907cf | |||
8f9682b0c4 | |||
244ae6c3f9 | |||
1b5411136d | |||
11cf3a11fc | |||
3a3ec331d7 | |||
1df9589d55 | |||
039ae89814 | |||
976f5da446 | |||
af9d99774e | |||
aa57005013 | |||
374c18d997 | |||
73d56692d1 | |||
8b0bcfa886 | |||
4a27360b7c | |||
a93e5625be | |||
fa9a36dffa | |||
766706268b | |||
36d044dc43 | |||
b827dfac61 | |||
b5c1e85258 | |||
f8a1087af3 | |||
a776fcc760 | |||
8f2726aafb | |||
95675da022 | |||
3488d200b6 | |||
20b77b4b1e | |||
d545efc0d3 | |||
8a70ec1452 | |||
b8c127d144 | |||
447c46eb47 | |||
9fe671e43c | |||
3bfd2d4ef6 | |||
65a47db81d | |||
b471718b6a | |||
50e6a24a4d | |||
935dfb7faa | |||
849b8da7eb | |||
991fb3432c | |||
c0cf657bea | |||
07ceae6471 | |||
5a88b23ae7 | |||
d9783477ce | |||
7b7b12d08e | |||
cbe01e6b77 | |||
68c787fab4 | |||
26b0bfac70 | |||
d68d6f85d2 | |||
61f34876ed | |||
ed0e57c8a1 | |||
49dabbd941 | |||
4b8353350b | |||
464af55612 | |||
9f826d7142 | |||
ea8a054005 | |||
8479130ef1 | |||
4303232543 | |||
0cd4a2bab4 | |||
0b4fc25462 | |||
f17d74f8dc | |||
07be745bbe | |||
510ab69af8 | |||
b9349e6590 | |||
4fd3abb9d6 | |||
ac562e7490 | |||
4fe5916a76 | |||
b858825838 | |||
5d92a3315f | |||
7a0f85c556 | |||
cfa284d4ad | |||
447e985740 | |||
7d872b97f2 | |||
ca3b99cb3a | |||
8d4f686f6c | |||
6f57bd84e7 | |||
6351acabe5 | |||
9d6ea1d7c3 | |||
035c6ed60c | |||
31e88b2f96 | |||
28f51d3bb6 | |||
c634695e4e | |||
983921fc22 | |||
9fc5b0eb09 | |||
b1fc8e0f98 | |||
551473feb7 | |||
105cb63d71 | |||
1dda4a9fb5 | |||
c53ec9ec5d | |||
a44b2479e3 | |||
ec26e16132 | |||
59a930f934 | |||
c7701bb2df | |||
4ac0da6ba2 | |||
19ad262617 | |||
0e987088a3 | |||
c74b89e0d6 | |||
e762713416 | |||
facdd0111b | |||
280abb8841 | |||
064f74736f | |||
d26868eced | |||
b5ec42fc56 | |||
104575780a | |||
1c1f4ecdfa | |||
582ee4031d | |||
fca8ad5a78 | |||
fff3a96889 | |||
dfdc5bac9d | |||
c022551427 | |||
76954c019b | |||
c3de79050e | |||
3cce436938 | |||
ed2bf96626 | |||
3693ddadad | |||
8866f94fab | |||
0ae1395d92 | |||
cf73323dd3 | |||
f019c50e44 | |||
3e56c9861c | |||
d6f475a009 | |||
b2f20766de | |||
81e98cfd6f | |||
8cae85badc | |||
d6e0fd0b46 | |||
8d7d79c29b | |||
92d3692f85 | |||
7f094a9f96 | |||
7c9d6ced6a | |||
9b747b08da | |||
f4695bad1e | |||
744f2f380f | |||
22c6b9d3fe | |||
c1aeb3a258 | |||
0eb6a8bde9 | |||
477f089de3 | |||
64faffa7b6 | |||
2196db9479 | |||
14d949228d | |||
f8e96a556d | |||
5ad974f947 | |||
9c9009ff52 | |||
bd09412f6f | |||
82d48fe27b | |||
1315cc584b | |||
17bed1ef7a | |||
f0dd7bc256 | |||
5b356c3c11 | |||
fbbae64ff2 | |||
34b85441e2 | |||
e9b122cf8c | |||
3f0853654e | |||
0de451af70 | |||
a4926f8a0d | |||
1314f9a1ff | |||
d5e344f67c | |||
d8654fae6d | |||
033f47b6b9 | |||
383bca4ff9 | |||
22f7b1e7c5 | |||
d5ad03546c | |||
f79076ddef | |||
7a31498e91 | |||
95f9d90877 | |||
3bac0c19ac | |||
b8f4123185 | |||
d2e9c838de | |||
5cd51ba00e | |||
a3fafdafbd | |||
df9ad069c4 | |||
2ff3dab014 | |||
ae9526ef57 | |||
5e235617e0 | |||
a373247cda | |||
c81e319aac | |||
d05537a619 | |||
7894ac5522 | |||
64577cf806 | |||
c3307152e8 | |||
a7e99eb5b4 | |||
c45aae7048 | |||
9cb3afeb30 | |||
6e7e6587c2 | |||
5f3c7296b7 | |||
2e15e61059 | |||
574357b60a | |||
1576605acb | |||
9d564ffbb2 | |||
b56f3236fd | |||
6e566b8840 | |||
34a1a19089 | |||
80dc797651 | |||
f2a4223d25 | |||
902aaf31dd | |||
1be23148d7 | |||
88d6a73454 | |||
01c57b37ad | |||
9fbb88fa5f | |||
a0b0d79777 | |||
f3dccd3b84 | |||
56b715797e | |||
11948c9500 | |||
1161bf79aa | |||
40f564b32a | |||
4fdabc16ba | |||
e68dbdfb4c | |||
f2898037b0 | |||
5d116c7224 | |||
2a31815267 | |||
ded6a72072 | |||
7d80857d8d | |||
297e318243 | |||
6cc992f6d6 | |||
6402897329 | |||
5f7d0e474e | |||
09bb2d8e27 | |||
e28dbea05b | |||
9d8c894cff | |||
9942348ba3 | |||
2e98eda8a4 | |||
2ab2b65fc2 | |||
3e684b117f | |||
51f28a6cf8 | |||
212e864db1 | |||
0adfd95ced | |||
c9d05152c9 | |||
a8b21d7c74 | |||
97874b73f6 | |||
948713d13d | |||
63146aa41c | |||
c7f69ad7c1 | |||
008f8c1554 | |||
122216dbe4 | |||
c0ac09b928 | |||
cfda7e5e1e | |||
91af43adba | |||
bfb7176db3 | |||
411cd5d4a3 | |||
91aa87d122 | |||
14bdc67a4a | |||
b86b1fd1ad | |||
00ec5179f9 | |||
200c9d8d8d | |||
b69d210759 | |||
eb4a58ed01 | |||
bc2f30ac9b | |||
e87bbe9ed8 | |||
68785ef6c0 | |||
b78e2adb09 | |||
26c985e683 | |||
58e2b9f7f5 | |||
34f2d4c4d4 | |||
56c3b9d7de | |||
c83ffe542e | |||
b07872c8c2 | |||
1cc196fd06 | |||
dd68bfbea8 | |||
ea5888f39f | |||
78607a0bf9 | |||
fa9cff390a | |||
2613a132a6 | |||
7d8b54a980 | |||
6371fa03a1 | |||
daa872d2e7 | |||
4d1600e396 | |||
63d33c287c | |||
ae72d4ab6a | |||
8a747d1d1f | |||
55e512efb8 | |||
f09e7b77db | |||
4177f71972 | |||
0c3e38c543 | |||
94dd555e9b | |||
10bffe0f0f | |||
aa6baf94a6 | |||
44604d98ab | |||
63077dfa26 | |||
0794e5b58f | |||
c67e1430bd | |||
f3b249d18f | |||
857b8781cb | |||
118d41a53b | |||
2cb21ae4a7 | |||
7e0bd630b5 | |||
a57e977131 | |||
082a351c17 | |||
a47e1977f0 | |||
b0a4d2ca84 | |||
afceaca736 | |||
c59dc7d2bf | |||
e7015570d5 | |||
a68a9e7ef3 | |||
3ea84f008b | |||
47aeac846b | |||
69124cff08 | |||
a99c1bb418 | |||
b80ca93ced | |||
8998292a0f | |||
bc39b1b8b5 | |||
b93d3d2175 | |||
b1277c98ab | |||
c2eaf120b6 | |||
41158e495f | |||
3261342c4f | |||
8b4068ac7e | |||
27958f5e7a | |||
819dc01451 | |||
8277e05205 | |||
f4d96c78e7 | |||
a08d74cd3c | |||
878d2509cd | |||
09247246bb | |||
685b8266e4 | |||
7a2f7fdf3d | |||
ecaa24192f | |||
f49cc9c286 | |||
53ae19eda8 | |||
d8361bf741 | |||
d70f04c63d | |||
0f4b9fef9e | |||
05c18702ff | |||
754c311580 | |||
f6dd5a3156 | |||
53489ec43b | |||
da8d97a274 | |||
8161d67a1f | |||
381d59c18f | |||
55a40fcf4d | |||
cfc690f1c2 | |||
a3bfa921e8 | |||
c670f017a0 | |||
f70c5a28af | |||
c3d64a031d | |||
eb94729277 | |||
c339f17c5c | |||
d7dd2d6d8b | |||
342fe7da9e | |||
910a002201 | |||
5f6c36e823 | |||
cf577bbb4f | |||
b8253ae9ba | |||
978bcf3b45 | |||
f76f81a312 | |||
5793295e1a | |||
6df22314c9 | |||
d7c3e051de | |||
057a39091d | |||
ba8b618b7d | |||
0b86ded4f5 | |||
523341cf4a | |||
ee17095a14 | |||
7829f4b7d8 | |||
058e057088 | |||
c8d7b52fbf | |||
9732656556 | |||
967b9251e2 | |||
3ba89a926b | |||
b04b6fe645 | |||
1829395a8a | |||
6f2d431ae1 | |||
9132608aaf | |||
f0b604b5dc | |||
d1b9283a9a | |||
fe353904d8 | |||
011a6c156e | |||
8662feb1c7 | |||
097b3fe8b6 | |||
7f138d4b68 | |||
6532d0e0d7 | |||
8242d9f269 | |||
28fe89e048 | |||
b709839c38 | |||
638ea466f0 | |||
31bc1e4e76 | |||
0535674a96 | |||
5f3b12a472 | |||
6ec16cbeb0 | |||
1665e519a4 | |||
95979c6095 | |||
8b004466d1 | |||
3f1f2fd8d4 | |||
96ed198efc | |||
6a52730b49 | |||
5c4a802017 | |||
e59a08b351 | |||
2a7857b60d | |||
09afd7f165 | |||
e63bec83e8 | |||
5d74ddfee5 | |||
3c44561b19 | |||
8edb209d16 | |||
81bd635ca4 | |||
6ff3fe5949 | |||
31067aab95 | |||
7b3a007862 | |||
c5a5c393a8 | |||
52db44eac7 | |||
cb9c782d0c | |||
b8c4a540fa | |||
91d85d93c7 | |||
73ca0feb55 | |||
64fd8b5686 | |||
58a9532c70 | |||
3c206f5aef | |||
9bc4f186d5 | |||
f883d4190a | |||
c6fc2d3e7c | |||
e4d1bb4d3c | |||
376faf3d5a | |||
6c0ca9cb86 | |||
326753d0ff | |||
47f95ddae2 | |||
86fc1448a6 | |||
a91ae337c4 | |||
58466fa490 | |||
de78a30a5d | |||
f7d61696d1 | |||
e35ccd360b | |||
5f3a778002 | |||
dcba456af3 | |||
6d98006a37 | |||
4ac0c157bc | |||
e696129f0b | |||
cf5c512a64 | |||
32f45f2d5f | |||
66b8bd5a74 | |||
bfb20dab0f | |||
6b28569bca | |||
79b2c668fa | |||
5d660694c3 | |||
e70d0392c0 | |||
caa47a3bb6 | |||
75203c022a | |||
b65e577017 | |||
45ef81481f | |||
f9dee4465b | |||
8ec1ea2a4c | |||
17df9d1fa3 | |||
24967ae3a6 | |||
30bd8aa483 | |||
efce9c0219 | |||
0020550dde | |||
04a9cde47e | |||
e472022c91 | |||
d778cd0e83 | |||
1f76da8709 | |||
7ddc0abce6 | |||
a2af58ae09 | |||
8e71bb932e | |||
8c1f033b1c | |||
eb4df77614 | |||
fed6eee951 | |||
6a0b507c3b | |||
47b2e61987 | |||
28259b329e | |||
4391aa3ea8 | |||
4a4c8e94e4 | |||
d41cc312bb | |||
12abc741d2 | |||
a8262e0a54 | |||
2011c212ba | |||
564871ca3c | |||
95bb15238a | |||
cafc5ce6e2 | |||
68c3b64424 | |||
300fe283d6 | |||
ac6408c3bb | |||
750e323947 | |||
4cca9d9904 | |||
955081f155 | |||
01e47c889b | |||
2cd4d387a7 | |||
ea8c60ccc5 | |||
4ecf5236c1 | |||
eb919f2d5e | |||
485dd43b58 | |||
fd4da657fb | |||
acc6879fb1 | |||
d339fdb645 | |||
53a720a802 | |||
0b04cdcfbf | |||
b5d2fe70ff | |||
6d6f1c5401 | |||
7b8b8a6394 | |||
2a3373a19b | |||
eb320c4e95 | |||
0b220424bb | |||
a948ec7bd7 | |||
56196f721d | |||
3d06112860 | |||
05484d9e02 | |||
b73807a140 | |||
215c5e464c | |||
cf2dce320c | |||
32cdcc38b5 | |||
4b3ea06f70 | |||
2e7a6a42b4 | |||
be0ec86c48 | |||
8c493e8fa3 | |||
9668e811c5 | |||
28ce99f46a | |||
9b4dbc58f3 | |||
f295f15034 | |||
4c41994068 | |||
6f7b3ffad6 | |||
cc97128e25 | |||
b6ba3d38dc | |||
18b788844a |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.0.0-b14
|
||||
version: 1.4.0
|
||||
version_scheme: semver
|
||||
|
41
.github/pull_request_template.md
vendored
Normal file
41
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
### :books: Summary
|
||||
<!-- your summary here emojis ref: https://github.com/yodamad/gitlab-emoji -->
|
||||
|
||||
|
||||
|
||||
### :link: Links / References
|
||||
<!--
|
||||
|
||||
using a list as any links to other references or links as required. if relevant, describe the link/reference
|
||||
|
||||
Include any issues or related merge requests. Note: dependent MR's also to be added to "Merge request dependencies"
|
||||
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### :construction_worker: Tasks
|
||||
|
||||
- [ ] Add your tasks here if required (delete)
|
||||
|
||||
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
|
||||
|
||||
- [ ] :firecracker: Contains breaking-change Any Breaking change(s)?
|
||||
|
||||
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
|
||||
|
||||
- [ ] :notebook: Release notes updated
|
||||
|
||||
- [ ] :blue_book: Documentation written
|
||||
|
||||
_All features to be documented within the correct section(s). Administration, Development and/or User_
|
||||
|
||||
- [ ] :checkered_flag: Milestone assigned
|
||||
|
||||
- [ ] :gear: :test_tube: [Functional Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
- [ ] :test_tube: [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
_ensure test coverage delta is not less than zero_
|
||||
|
||||
- [ ] :page_facing_up: Roadmap updated
|
11
.github/workflows/ci.yaml
vendored
11
.github/workflows/ci.yaml
vendored
@ -16,6 +16,17 @@ env:
|
||||
jobs:
|
||||
|
||||
|
||||
mkdocs:
|
||||
name: 'MKDocs'
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
statuses: write
|
||||
checks: write
|
||||
actions: write
|
||||
uses: nofusscomputing/action_mkdocs/.github/workflows/reusable_mkdocs.yaml@development
|
||||
|
||||
|
||||
docker:
|
||||
name: 'Docker'
|
||||
uses: nofusscomputing/action_docker/.github/workflows/docker.yaml@development
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -9,3 +9,9 @@ artifacts/
|
||||
volumes/
|
||||
build/
|
||||
pages/
|
||||
node_modules/
|
||||
.markdownlint-cli2.jsonc
|
||||
.markdownlint.json
|
||||
package-lock.json
|
||||
package.json
|
||||
**.junit.xml
|
||||
|
@ -35,3 +35,5 @@
|
||||
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
_ensure test coverage delta is not less than zero_
|
||||
|
||||
- [ ] :page_facing_up: Roadmap updated
|
||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -7,5 +7,6 @@
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"qwtel.sqlite-viewer",
|
||||
"jebbs.markdown-extended",
|
||||
"william-voyek.vscode-nginx",
|
||||
]
|
||||
}
|
36
.vscode/launch.json
vendored
36
.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug: Django",
|
||||
"name": "Centurion",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
@ -16,9 +16,41 @@
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
|
||||
"name": "Debug: Gunicorn",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "gunicorn",
|
||||
"args": [
|
||||
"--access-logfile",
|
||||
"-",
|
||||
"--workers",
|
||||
"3",
|
||||
"--bind",
|
||||
"0.0.0.0:8002",
|
||||
"app.wsgi:application",
|
||||
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"cwd": "${workspaceFolder}/app"
|
||||
},
|
||||
{
|
||||
"name": "Migrate",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"migrate"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Debug: Celery",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"console": "integratedTerminal",
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -17,4 +17,5 @@
|
||||
"ITSM"
|
||||
],
|
||||
"cSpell.language": "en-AU",
|
||||
"jest.enable": false,
|
||||
}
|
1049
CHANGELOG.md
1049
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,84 @@
|
||||
# Contribution Guide
|
||||
|
||||
|
||||
Development of this project has been setup to be done from VSCodium. The following additional requirements need to be met:
|
||||
|
||||
- npm has been installed. _required for `markdown` linting_
|
||||
|
||||
`sudo apt install -y --no-install-recommends npm`
|
||||
|
||||
- setup of other requirements can be done with `make prepare`
|
||||
|
||||
- **ALL** Linting must pass for Merge to be conducted.
|
||||
|
||||
_`make lint`_
|
||||
|
||||
## TL;DR
|
||||
|
||||
|
||||
from the root of the project to start a test server use:
|
||||
|
||||
``` bash
|
||||
|
||||
# activate python venv
|
||||
source /tmp/centurion_erp/bin/activate
|
||||
|
||||
# enter app dir
|
||||
cd app
|
||||
|
||||
# Start dev server can be viewed at http://127.0.0.1:8002
|
||||
python manage.py runserver 8002
|
||||
|
||||
# Run any migrations, if required
|
||||
python manage.py migrate
|
||||
|
||||
# Create a super suer if required
|
||||
python manage.py createsuperuser
|
||||
|
||||
```
|
||||
|
||||
## Makefile
|
||||
|
||||
!!! tip "TL;DR"
|
||||
Common make commands are `make prepare` then `make docs` and `make lint`
|
||||
|
||||
Included within the root of the repository is a makefile that can be used during development to check/run different items as is required during development. The following make targets are available:
|
||||
|
||||
- `prepare`
|
||||
|
||||
_prepare the repository. init's all git submodules and sets up a python virtual env and other make targets_
|
||||
|
||||
- `docs`
|
||||
|
||||
_builds the docs and places them within a directory called build, which can be viewed within a web browser_
|
||||
|
||||
- `lint`
|
||||
|
||||
_conducts all required linting_
|
||||
|
||||
- `docs-lint`
|
||||
|
||||
_lints the markdown documents within the docs directory for formatting errors that MKDocs may/will have an issue with._
|
||||
|
||||
- `clean`
|
||||
|
||||
_cleans up build artifacts and removes the python virtual environment_
|
||||
|
||||
|
||||
> this doc is yet to receive a re-write
|
||||
|
||||
|
||||
## Docker Container
|
||||
|
||||
within the `deploy/` directory there is a docker compose file. running `docker compose up` from this directory will launch a full stack deployment locally containing Centurion API, User Interface, a worker and a RabbitMQ server. once launched you can navigate to `http://127.0.0.1/` to start browsing the site.
|
||||
|
||||
You may need to run migrations if your not mounting your own DB. to do this run `docker exec -ti centurion-erp python manage.py migrate`
|
||||
|
||||
|
||||
|
||||
# Old working docs
|
||||
|
||||
|
||||
## Dev Environment
|
||||
|
||||
It's advised to setup a python virtual env for development. this can be done with the following commands.
|
||||
@ -30,6 +108,9 @@ python3 manage.py createsuperuser
|
||||
# If model changes
|
||||
python3 manage.py makemigrations --noinput
|
||||
|
||||
# To update code highlight run
|
||||
pygmentize -S default -f html -a .codehilite > project-static/code.css
|
||||
|
||||
```
|
||||
|
||||
Updates to python modules will need to be captured with SCM. This can be done by running `pip freeze > requirements.txt` from the running virtual environment.
|
||||
|
13
README.md
13
README.md
@ -32,9 +32,14 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
|
||||
|
||||
**Stable Branch**
|
||||
|
||||
  
|
||||
 
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
----
|
||||
@ -43,9 +48,13 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
|
||||
|
||||
|
||||
|
||||
  
|
||||
 
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
----
|
||||
<br>
|
||||
|
@ -1,7 +1,49 @@
|
||||
# Version 1.0.0
|
||||
## Version 1.4.0
|
||||
|
||||
API redesign in preparation for moving the UI out of centurion to it's [own project](https://github.com/nofusscomputing/centurion_erp_ui). This release introduces a **Feature freeze** to the current UI. Only bug fixes will be done for the current UI.
|
||||
API v2 is a beta release and is subject to change. On completion of the new UI, API v2 will more likely than not be set as stable.
|
||||
|
||||
- A large emphasis is being placed upon API stability. This is being achieved by ensuring the following:
|
||||
|
||||
- Actions can only be carried out by users whom have the correct permissions
|
||||
|
||||
- fields are of the correct type and visible when required as part of the API response
|
||||
|
||||
- Data validations work and notify the user of any issue
|
||||
|
||||
We are make the above possible by ensuring a more stringent test policy.
|
||||
|
||||
- New API will be at path `api/v2`.
|
||||
|
||||
- API v1 is now **Feature frozen** with only bug fixes being completed. It's recommended that you move to and start using API v2 as this has feature parity with API v1.
|
||||
|
||||
- API v1 is **depreciated**
|
||||
|
||||
- Depreciation of **ALL** API urls. API v1 Will be [removed in v2.0.0](https://github.com/nofusscomputing/centurion_erp/issues/343) release of Centurion.
|
||||
|
||||
|
||||
# Version 1.3.0
|
||||
|
||||
!!! danger "Security"
|
||||
As is currently the recommended method of deployment, the Centurion Container must be deployed behind a reverse proxy the conducts the SSL termination.
|
||||
|
||||
This release updates the docker container to be a production setup for deployment of Centurion. Prior to this version Centurion ERP was using a development setup for the webserver.
|
||||
|
||||
- Docker now uses SupervisorD for container
|
||||
|
||||
- Gunicorn WSGI setup for Centurion with NginX as the webserver.
|
||||
|
||||
- Container now has a health check.
|
||||
|
||||
- To setup container as "Worker", set `IS_WORKER='True'` environmental variable within container. _**Note:** You can still use command `celery -A app worker -l INFO`, although **not** recommended as the container health check will not be functioning_
|
||||
|
||||
|
||||
## Version 1.0.0
|
||||
|
||||
|
||||
Initial Release of Centurion ERP.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Nil
|
||||
|
@ -11,12 +11,20 @@ class AutoCreatedField(models.DateTimeField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'Date and time of creation'
|
||||
|
||||
verbose_name = 'Created'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("editable", False)
|
||||
|
||||
kwargs.setdefault("default", now)
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
@ -28,6 +36,18 @@ class AutoLastModifiedField(AutoCreatedField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'Date and time of last modification'
|
||||
|
||||
verbose_name = 'Modified'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
value = now()
|
||||
@ -45,6 +65,20 @@ class AutoSlugField(models.SlugField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'slug for this field'
|
||||
|
||||
verbose_name = 'Slug'
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
if not model_instance.slug or model_instance.slug == '_':
|
||||
|
@ -1,13 +1,13 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from app import settings
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
|
||||
from access.models import Team
|
||||
from access.functions import permissions
|
||||
|
||||
from app import settings
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
@ -66,37 +66,4 @@ class TeamForm(CommonModelForm):
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'config_management',
|
||||
'core',
|
||||
'django_celery_results',
|
||||
'itam',
|
||||
'settings',
|
||||
]
|
||||
|
||||
exclude_models = [
|
||||
'appsettings',
|
||||
'chordcounter',
|
||||
'groupresult',
|
||||
'organization'
|
||||
'settings',
|
||||
'usersettings',
|
||||
]
|
||||
|
||||
exclude_permissions = [
|
||||
'add_organization',
|
||||
'add_taskresult',
|
||||
'change_organization',
|
||||
'change_taskresult',
|
||||
'delete_organization',
|
||||
'delete_taskresult',
|
||||
]
|
||||
|
||||
self.fields['permissions'].queryset = Permission.objects.filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
self.fields['permissions'].queryset = permissions.permission_queryset()
|
||||
|
46
app/access/functions/permissions.py
Normal file
46
app/access/functions/permissions.py
Normal file
@ -0,0 +1,46 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
def permission_queryset():
|
||||
"""Filter Permissions to those used within the application
|
||||
|
||||
Returns:
|
||||
list: Filtered queryset that only contains the used permissions
|
||||
"""
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
'django_celery_results',
|
||||
'itam',
|
||||
'itim',
|
||||
'settings',
|
||||
]
|
||||
|
||||
exclude_models = [
|
||||
'appsettings',
|
||||
'chordcounter',
|
||||
'comment',
|
||||
'groupresult',
|
||||
'organization'
|
||||
'settings',
|
||||
'usersettings',
|
||||
]
|
||||
|
||||
exclude_permissions = [
|
||||
'add_organization',
|
||||
'add_taskresult',
|
||||
'change_organization',
|
||||
'change_taskresult',
|
||||
'delete_organization',
|
||||
'delete_taskresult',
|
||||
]
|
||||
|
||||
return Permission.objects.filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
17
app/access/migrations/0002_alter_team_options.py
Normal file
17
app/access/migrations/0002_alter_team_options.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-13 06:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='team',
|
||||
options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'},
|
||||
),
|
||||
]
|
@ -0,0 +1,57 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-13 15:27
|
||||
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_options'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='id',
|
||||
field=models.AutoField(help_text='ID of this item', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='manager',
|
||||
field=models.ForeignKey(help_text='Manager for this organization', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of this Organization', max_length=50, unique=True, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='is_global',
|
||||
field=models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='team_name',
|
||||
field=models.CharField(default='', help_text='Name to give this team', max_length=50, verbose_name='Name'),
|
||||
),
|
||||
]
|
@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-16 06:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='organization',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='teamusers',
|
||||
options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='id',
|
||||
field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='manager',
|
||||
field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='team',
|
||||
field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='user',
|
||||
field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
]
|
18
app/access/migrations/0005_alter_team_team_name.py
Normal file
18
app/access/migrations/0005_alter_team_team_name.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-07 06:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0004_alter_organization_options_alter_teamusers_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='team_name',
|
||||
field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'),
|
||||
),
|
||||
]
|
20
app/access/migrations/0006_alter_team_organization.py
Normal file
20
app/access/migrations/0006_alter_team_organization.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-20 02:41
|
||||
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0005_alter_team_team_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
|
||||
),
|
||||
]
|
@ -10,6 +10,19 @@ from .models import Organization, 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 = []
|
||||
@ -26,20 +39,24 @@ class OrganizationMixin():
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.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 hasattr(self, 'parent_model'):
|
||||
if self.parent_model:
|
||||
obj = self.get_parent_obj()
|
||||
|
||||
id = obj.get_organization().id
|
||||
@ -61,6 +78,10 @@ class OrganizationMixin():
|
||||
|
||||
id = 0
|
||||
|
||||
if hasattr(self, 'instance') and id is None: # Form Instance
|
||||
|
||||
id = self.instance.get_organization()
|
||||
|
||||
|
||||
except AttributeError:
|
||||
|
||||
@ -84,6 +105,10 @@ class OrganizationMixin():
|
||||
|
||||
pass
|
||||
|
||||
if id is not None:
|
||||
|
||||
self._object_organization = id
|
||||
|
||||
|
||||
return id
|
||||
|
||||
@ -99,9 +124,13 @@ class OrganizationMixin():
|
||||
|
||||
is_member = False
|
||||
|
||||
if organization in self.user_organizations():
|
||||
if organization is None:
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
if int(organization) in self.user_organizations():
|
||||
|
||||
is_member = True
|
||||
|
||||
return is_member
|
||||
|
||||
@ -111,6 +140,10 @@ class OrganizationMixin():
|
||||
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 "
|
||||
@ -147,6 +180,10 @@ class OrganizationMixin():
|
||||
|
||||
user_organizations = []
|
||||
|
||||
if hasattr(self, '_user_organizations'):
|
||||
|
||||
return self._user_organizations
|
||||
|
||||
teams = Team.objects
|
||||
|
||||
for group in self.request.user.groups.all():
|
||||
@ -157,18 +194,41 @@ class OrganizationMixin():
|
||||
|
||||
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) -> bool:
|
||||
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)
|
||||
@ -182,7 +242,7 @@ class OrganizationMixin():
|
||||
|
||||
assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])
|
||||
|
||||
if assembled_permission in self.get_permission_required() and (team['organization_id'] == organization or organization == 0):
|
||||
if assembled_permission in permissions_required and (team['organization_id'] == organization or organization == 0):
|
||||
|
||||
return True
|
||||
|
||||
@ -242,15 +302,23 @@ class OrganizationMixin():
|
||||
|
||||
return True
|
||||
|
||||
perms = self.get_permission_required()
|
||||
if permissions_required:
|
||||
|
||||
if self.has_organization_permission():
|
||||
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 len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
|
||||
if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':
|
||||
|
||||
return True
|
||||
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:
|
||||
|
||||
@ -327,6 +395,12 @@ class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
|
||||
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:
|
||||
|
||||
|
@ -3,6 +3,8 @@ from django.db import models
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.forms import ValidationError
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from .fields import *
|
||||
|
||||
from core.middleware.get_request import get_request
|
||||
@ -12,12 +14,10 @@ from core.mixin.history_save import SaveHistory
|
||||
class Organization(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Organization"
|
||||
verbose_name_plural = "Organizations"
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.slug == '_':
|
||||
@ -26,28 +26,34 @@ class Organization(SaveHistory):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
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,
|
||||
on_delete=models.SET_NULL,
|
||||
blank = False,
|
||||
help_text = 'Manager for this organization',
|
||||
null = True,
|
||||
help_text = 'Organization Manager'
|
||||
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',
|
||||
)
|
||||
@ -62,6 +68,62 @@ class Organization(SaveHistory):
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
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": "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})
|
||||
|
||||
|
||||
|
||||
class TenancyManager(models.Manager):
|
||||
@ -106,7 +168,9 @@ class TenancyManager(models.Manager):
|
||||
|
||||
if request:
|
||||
|
||||
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
|
||||
# user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
|
||||
|
||||
user = request.user
|
||||
|
||||
|
||||
if user.is_authenticated:
|
||||
@ -124,13 +188,22 @@ class TenancyManager(models.Manager):
|
||||
user_organizations += [ team_user.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:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
if getattr(self.model, 'is_global', False) is True:
|
||||
|
||||
return super().get_queryset().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()
|
||||
|
||||
@ -165,23 +238,36 @@ class TenancyObject(SaveHistory):
|
||||
raise ValidationError('You must provide an organization')
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of the item',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
on_delete=models.CASCADE,
|
||||
blank = False,
|
||||
null = True,
|
||||
help_text = 'Organization this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
blank = False,
|
||||
default = False,
|
||||
blank = False
|
||||
help_text = 'Is this a global object?',
|
||||
verbose_name = 'Global Object'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
@ -189,15 +275,59 @@ class TenancyObject(SaveHistory):
|
||||
return self.organization
|
||||
|
||||
|
||||
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_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
"""Fetch the URL kwargs
|
||||
|
||||
Returns:
|
||||
dict: kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
if self.organization is None:
|
||||
|
||||
raise ValidationError('Organization not defined')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
|
||||
class Team(Group, TenancyObject):
|
||||
class Meta:
|
||||
# proxy = True
|
||||
verbose_name_plural = "Teams"
|
||||
ordering = ['team_name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
class Meta:
|
||||
|
||||
ordering = [ 'team_name' ]
|
||||
|
||||
verbose_name = 'Team'
|
||||
|
||||
verbose_name_plural = "Teams"
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
@ -208,17 +338,79 @@ class Team(Group, TenancyObject):
|
||||
|
||||
|
||||
team_name = models.CharField(
|
||||
verbose_name = 'Name',
|
||||
blank = False,
|
||||
help_text = 'Name to give this team',
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
default = ''
|
||||
verbose_name = 'Name',
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'team_name',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "table",
|
||||
"name": "Users",
|
||||
"field": "user",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
'team_name',
|
||||
'modified',
|
||||
'created',
|
||||
]
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
"""Fetch the URL kwargs
|
||||
|
||||
Returns:
|
||||
dict: kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'organization_id': self.organization.id,
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
@ -241,40 +433,64 @@ class Team(Group, TenancyObject):
|
||||
return [permission_list, self.permissions.all()]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.team_name
|
||||
|
||||
|
||||
|
||||
class TeamUsers(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
# proxy = True
|
||||
verbose_name_plural = "Team Users"
|
||||
|
||||
ordering = ['user']
|
||||
|
||||
verbose_name = "Team User"
|
||||
|
||||
verbose_name_plural = "Team Users"
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this Team User',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
blank = False,
|
||||
help_text = 'Team user belongs to',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="team",
|
||||
on_delete=models.CASCADE)
|
||||
verbose_name = 'Team'
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE
|
||||
blank = False,
|
||||
help_text = 'User who will be added to the team',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'User'
|
||||
)
|
||||
|
||||
manager = models.BooleanField(
|
||||
verbose_name='manager',
|
||||
blank=True,
|
||||
default=False,
|
||||
blank=True
|
||||
help_text = 'Is this user to be a manager of this team',
|
||||
verbose_name='manager',
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
page_layout: list = []
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Delete Team
|
||||
@ -296,6 +512,24 @@ class TeamUsers(SaveHistory):
|
||||
return self.team.organization
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
url_kwargs: dict = {
|
||||
'organization_id': self.team.organization.id,
|
||||
'team_id': self.team.id,
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
print(f'url kwargs are: {url_kwargs}')
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team_user-detail", request=request, kwargs = url_kwargs )
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team_user-detail", kwargs = url_kwargs )
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" Save Team
|
||||
|
||||
@ -318,3 +552,6 @@ class TeamUsers(SaveHistory):
|
||||
|
||||
return self.team
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
|
89
app/access/serializers/organization.py
Normal file
89
app/access/serializers/organization.py
Normal file
@ -0,0 +1,89 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
|
||||
|
||||
class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v2:_api_v2_organization-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class OrganizationModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
OrganizationBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'model_notes',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
class OrganizationViewSerializer(OrganizationModelSerializer):
|
||||
pass
|
||||
|
||||
manager = UserBaseSerializer(many=False, read_only = True)
|
99
app/access/serializers/team_user.py
Normal file
99
app/access/serializers/team_user.py
Normal file
@ -0,0 +1,99 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models import TeamUsers
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
|
||||
|
||||
class TeamUserBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamUserModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
TeamUserBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request )
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'manager',
|
||||
'user',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self.validated_data['team_id'] = int(self._context['view'].kwargs['team_id'])
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
class TeamUserViewSerializer(TeamUserModelSerializer):
|
||||
|
||||
user = UserBaseSerializer(read_only = True)
|
126
app/access/serializers/teams.py
Normal file
126
app/access/serializers/teams.py
Normal file
@ -0,0 +1,126 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models import Team
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
|
||||
from app.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
|
||||
|
||||
class TeamBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Team
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
TeamBaseSerializer
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
'users': reverse(
|
||||
'v2:_api_v2_organization_team_user-list',
|
||||
request=self.context['view'].request,
|
||||
kwargs={
|
||||
'organization_id': item.organization.id,
|
||||
'team_id': item.pk
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
team_name = centurion_field.CharField( autolink = True )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Team
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'model_notes',
|
||||
'permissions',
|
||||
'organization',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self.validated_data['organization_id'] = int(self._context['view'].kwargs['organization_id'])
|
||||
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
class TeamViewSerializer(TeamModelSerializer):
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
|
||||
permissions = PermissionBaseSerializer(many = True)
|
@ -11,6 +11,19 @@ class TenancyObject:
|
||||
model = None
|
||||
""" Model to be tested """
|
||||
|
||||
should_model_history_be_saved: bool = True
|
||||
""" Should model history be saved.
|
||||
|
||||
By default this should always be 'True', however in special
|
||||
circumstances, this may not be desired.
|
||||
"""
|
||||
|
||||
|
||||
def test_history_save(self):
|
||||
"""Confirm the desired intent for saving model history."""
|
||||
|
||||
assert self.model.save_model_history == self.should_model_history_be_saved
|
||||
|
||||
|
||||
def test_has_attr_get_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
@ -0,0 +1,92 @@
|
||||
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)
|
@ -0,0 +1,280 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
|
||||
|
||||
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_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.super_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
|
||||
|
||||
|
||||
|
||||
class OrganizationViewSet(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase
|
||||
):
|
||||
|
||||
pass
|
201
app/access/tests/functional/team/test_team_permission_viewset.py
Normal file
201
app/access/tests/functional/team/test_team_permission_viewset.py
Normal file
@ -0,0 +1,201 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'API'
|
||||
|
||||
url_name = '_api_v2_organization_team'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
delete_data = {'device': 'device'}
|
||||
|
||||
@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
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.different_organization = different_organization
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization=organization,
|
||||
name = 'teamone'
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization=different_organization,
|
||||
name = 'teamtwo'
|
||||
)
|
||||
|
||||
|
||||
self.url_kwargs = {'organization_id': self.organization.id}
|
||||
|
||||
self.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
|
||||
|
||||
self.add_data = {'team_name': 'team_post'}
|
||||
|
||||
|
||||
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")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
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 TeamPermissionsAPI(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TeamViewSet(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
180
app/access/tests/functional/team/test_team_serializer.py
Normal file
180
app/access/tests/functional/team/test_team_serializer.py
Normal file
@ -0,0 +1,180 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.models import Organization, Permission
|
||||
|
||||
from access.serializers.teams import (
|
||||
Team,
|
||||
TeamModelSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class MockView:
|
||||
|
||||
action: str = None
|
||||
|
||||
kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class MockRequest:
|
||||
|
||||
user = None
|
||||
|
||||
|
||||
|
||||
class TeamValidationAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Team
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an org
|
||||
2. Create an item
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(
|
||||
name = 'team org serializer test'
|
||||
)
|
||||
|
||||
self.user = User.objects.create(username = 'org_user', password='random password')
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
self.valid_data = {
|
||||
'organization': self.organization.id,
|
||||
'team_name': 'valid_org_data',
|
||||
'permissions': [
|
||||
view_permissions.id,
|
||||
]
|
||||
}
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'random team title',
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating an item supplied valid data
|
||||
creates an item.
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
serializer = TeamModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['team_name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = TeamModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['team_name'][0] == 'required'
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_permissions_optional(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and permissions are not supplied, the item is
|
||||
still created.
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['permissions']
|
||||
|
||||
serializer = TeamModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
@ -0,0 +1,211 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
app_namespace = 'API'
|
||||
|
||||
url_name = '_api_v2_organization_team_user'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
delete_data = {'device': 'device'}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and 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
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.different_organization = different_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")
|
||||
|
||||
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
|
||||
|
||||
|
||||
self.item = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.other_org_item = TeamUsers.objects.create(
|
||||
team = view_team_b,
|
||||
user = self.view_user_b
|
||||
)
|
||||
|
||||
self.url_view_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id, 'pk': self.item.id}
|
||||
|
||||
self.url_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id}
|
||||
|
||||
|
||||
random_user = User.objects.create_user(username="random_user", password="password")
|
||||
self.add_data = {'user': random_user.id}
|
||||
|
||||
|
||||
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 TeamUserPermissionsAPI(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
TestCase
|
||||
):
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self):
|
||||
"""This test is not applicable for team_user as users are not tenancy objects
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TeamUserViewSet(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,186 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.models import Organization, Permission, Team
|
||||
|
||||
from access.serializers.team_user import (
|
||||
TeamUsers,
|
||||
TeamUserModelSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class MockView:
|
||||
|
||||
action: str = None
|
||||
|
||||
kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class MockRequest:
|
||||
|
||||
user = None
|
||||
|
||||
|
||||
|
||||
class TeamValidationAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an org
|
||||
2. Create an item
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(
|
||||
name = 'team org serializer test'
|
||||
)
|
||||
|
||||
self.user = User.objects.create(username = 'org_user', password='random password')
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
self.team = Team.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'random team title',
|
||||
)
|
||||
|
||||
|
||||
self.valid_data = {
|
||||
'team': self.team.id,
|
||||
'user': self.user.id
|
||||
}
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
team = self.team,
|
||||
user = self.user,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating an item supplied valid data
|
||||
creates an item.
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.team.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
serializer = TeamUserModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_team_creates(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no team is provided no validation
|
||||
error occurs as the team id is collected from the view
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.team.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['team']
|
||||
|
||||
serializer = TeamUserModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_user(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no user is provided a validation error occurs
|
||||
"""
|
||||
|
||||
|
||||
mock_view = MockView()
|
||||
mock_view.action = 'create'
|
||||
mock_view.kwargs: dict = {
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.team.id
|
||||
}
|
||||
|
||||
mock_request = MockRequest()
|
||||
mock_request.user = self.user
|
||||
|
||||
mock_view.request = mock_request
|
||||
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['user']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = TeamUserModelSerializer(
|
||||
context = {
|
||||
'request': mock_request,
|
||||
'view': mock_view,
|
||||
},
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['user'][0] == 'required'
|
@ -17,7 +17,7 @@ class OrganizationAPI(TestCase):
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
|
192
app/access/tests/unit/organization/test_organizaiton_api_v2.py
Normal file
192
app/access/tests/unit/organization/test_organizaiton_api_v2.py
Normal file
@ -0,0 +1,192 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.relations import Hyperlink
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_fields import APICommonFields
|
||||
|
||||
|
||||
class OrganizationAPI(
|
||||
TestCase,
|
||||
APICommonFields
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create the object
|
||||
2. create view user
|
||||
3. add user as org manager
|
||||
4. make api request
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org', model_notes='random text')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
|
||||
self.item = organization
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.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])
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
organization.manager = self.view_user
|
||||
|
||||
organization.save()
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
self.api_data = response.data
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['name']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_manager(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
manager field must exist
|
||||
"""
|
||||
|
||||
assert 'manager' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_manager(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
manager field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['manager']) is dict
|
||||
|
||||
|
||||
def test_api_field_exists_manager_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
manager.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['manager']
|
||||
|
||||
|
||||
def test_api_field_type_manager_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
manager.id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['manager']['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_manager_display_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
manager.display_name field must exist
|
||||
"""
|
||||
|
||||
assert 'display_name' in self.api_data['manager']
|
||||
|
||||
|
||||
def test_api_field_type_manager_display_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
manager.display_name field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['manager']['display_name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_manager_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
manager.display_name field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data['manager']
|
||||
|
||||
|
||||
def test_api_field_type_manager_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
manager.url field must be Hyperlink
|
||||
"""
|
||||
|
||||
assert type(self.api_data['manager']['url']) is Hyperlink
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_url_teams(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls.teams field must exist
|
||||
"""
|
||||
|
||||
assert 'teams' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_url_teams(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls.teams field must be Hyperlink
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['teams']) is str
|
@ -20,7 +20,7 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
|
||||
model_name = 'organization'
|
||||
app_label = 'access'
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
|
@ -34,7 +34,7 @@ class OrganizationHistory(TestCase):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -44,7 +44,7 @@ class OrganizationHistory(TestCase):
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -72,7 +72,7 @@ class OrganizationHistory(TestCase):
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.ADD[0])
|
||||
assert history['action'] == int(History.Actions.ADD)
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@ class OrganizationHistory(TestCase):
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.UPDATE[0])
|
||||
assert history['action'] == int(History.Actions.UPDATE)
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
|
@ -67,4 +67,74 @@ class TeamModel(
|
||||
|
||||
@pytest.mark.skip(reason="uses Django group manager")
|
||||
def test_model_class_tenancy_manager_function_get_queryset_called(self):
|
||||
pass
|
||||
pass
|
||||
|
||||
|
||||
def test_model_fields_parameter_not_empty_help_text(self):
|
||||
"""Test Field called with Parameter
|
||||
|
||||
This is a custom test of a test derived of the samae name. It's required
|
||||
as the team model extends the Group model.
|
||||
|
||||
During field creation, paramater `help_text` must not be `None` or empty ('')
|
||||
"""
|
||||
|
||||
group_mode_fields_to_ignore: list = [
|
||||
'id',
|
||||
'name',
|
||||
'group_ptr_id'
|
||||
]
|
||||
|
||||
fields_have_test_value: bool = True
|
||||
|
||||
for field in self.model._meta.fields:
|
||||
|
||||
if field.attname in group_mode_fields_to_ignore:
|
||||
|
||||
continue
|
||||
|
||||
print(f'Checking field {field.attname} is not empty')
|
||||
|
||||
if (
|
||||
field.help_text is None
|
||||
or field.help_text == ''
|
||||
):
|
||||
|
||||
print(f' Failure on field {field.attname}')
|
||||
|
||||
fields_have_test_value = False
|
||||
|
||||
|
||||
assert fields_have_test_value
|
||||
|
||||
def test_model_fields_parameter_type_verbose_name(self):
|
||||
"""Test Field called with Parameter
|
||||
|
||||
This is a custom test of a test derived of the samae name. It's required
|
||||
as the team model extends the Group model.
|
||||
|
||||
During field creation, paramater `verbose_name` must be of type str
|
||||
"""
|
||||
|
||||
group_mode_fields_to_ignore: list = [
|
||||
'name',
|
||||
]
|
||||
|
||||
fields_have_test_value: bool = True
|
||||
|
||||
for field in self.model._meta.fields:
|
||||
|
||||
if field.attname in group_mode_fields_to_ignore:
|
||||
|
||||
continue
|
||||
|
||||
print(f'Checking field {field.attname} is of type str')
|
||||
|
||||
if not type(field.verbose_name) is str:
|
||||
|
||||
print(f' Failure on field {field.attname}')
|
||||
|
||||
fields_have_test_value = False
|
||||
|
||||
|
||||
assert fields_have_test_value
|
||||
|
@ -21,7 +21,7 @@ class TeamAPI(TestCase):
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_team'
|
||||
|
||||
|
174
app/access/tests/unit/team/test_team_api_v2.py
Normal file
174
app/access/tests/unit/team/test_team_api_v2.py
Normal file
@ -0,0 +1,174 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.relations import Hyperlink
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_fields import APITenancyObject
|
||||
|
||||
|
||||
|
||||
class TeamAPI(
|
||||
TestCase,
|
||||
APITenancyObject
|
||||
):
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization_team'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create the object
|
||||
2. create view user
|
||||
3. add user as org manager
|
||||
4. make api request
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization=organization,
|
||||
team_name = 'teamone',
|
||||
model_notes = 'random note'
|
||||
)
|
||||
|
||||
self.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.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,
|
||||
)
|
||||
)
|
||||
|
||||
self.item.permissions.set([view_permissions])
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = self.item,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
organization.manager = self.view_user
|
||||
|
||||
organization.save()
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
self.api_data = response.data
|
||||
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_team_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
team_name field must exist
|
||||
"""
|
||||
|
||||
assert 'team_name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_team_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
team_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['team_name']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_permissions(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
permissions field must exist
|
||||
"""
|
||||
|
||||
assert 'permissions' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_permissions(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
url field must be list
|
||||
"""
|
||||
|
||||
assert type(self.api_data['permissions']) is list
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_permissions_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
permissions.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
def test_api_field_type_permissions_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
permissions.id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['permissions'][0]['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_permissions_display_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
permissions.display_name field must exist
|
||||
"""
|
||||
|
||||
assert 'display_name' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
def test_api_field_type_permissions_display_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
permissions.display_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['permissions'][0]['display_name']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_permissions_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
permissions.url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
def test_api_field_type_permissions_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
permissions.url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['permissions'][0]['url']) is Hyperlink
|
@ -39,7 +39,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -51,7 +51,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
self.field_after_expected_value = '{"name": "test_org_' + self.item_change.team_name + '", "team_name": "' + self.item_change.team_name + '"}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -68,7 +68,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
action = int(History.Actions.DELETE),
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ class TeamPermissionsAPI(TestCase, APIPermissions):
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_team'
|
||||
|
||||
|
@ -6,9 +6,13 @@ from django.contrib.auth.models import User
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from app.tests.abstract.models import BaseModel
|
||||
|
||||
|
||||
class TeamUsersModel(TestCase):
|
||||
class TeamUsersModel(
|
||||
TestCase,
|
||||
BaseModel
|
||||
):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
|
214
app/access/tests/unit/team_user/test_team_user_api_v2.py
Normal file
214
app/access/tests/unit/team_user/test_team_user_api_v2.py
Normal file
@ -0,0 +1,214 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.relations import Hyperlink
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_fields import APICommonFields
|
||||
|
||||
|
||||
|
||||
class TeamUserAPI(
|
||||
TestCase,
|
||||
APICommonFields
|
||||
):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization_team_user'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create the object
|
||||
2. create view user
|
||||
3. add user as org manager
|
||||
4. make api request
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_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.permissions.set([view_permissions])
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
|
||||
self.item = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.url_view_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id, 'pk': self.item.id}
|
||||
|
||||
self.url_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id}
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
self.api_data = response.data
|
||||
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_manager(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
manager field must exist
|
||||
"""
|
||||
|
||||
assert 'manager' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_manager(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
manager field must be bool
|
||||
"""
|
||||
|
||||
assert type(self.api_data['manager']) is bool
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_created(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
created field must exist
|
||||
"""
|
||||
|
||||
assert 'created' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_created(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
created field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['created']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_modified(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
modified field must exist
|
||||
"""
|
||||
|
||||
assert 'modified' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_modified(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
modified field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['modified']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
# def test_api_field_exists_permissions(self):
|
||||
# """ Test for existance of API Field
|
||||
|
||||
# permissions field must exist
|
||||
# """
|
||||
|
||||
# assert 'permissions' in self.api_data
|
||||
|
||||
|
||||
# def test_api_field_type_permissions(self):
|
||||
# """ Test for type for API Field
|
||||
|
||||
# url field must be list
|
||||
# """
|
||||
|
||||
# assert type(self.api_data['permissions']) is list
|
||||
|
||||
|
||||
|
||||
# def test_api_field_exists_permissions_id(self):
|
||||
# """ Test for existance of API Field
|
||||
|
||||
# permissions.id field must exist
|
||||
# """
|
||||
|
||||
# assert 'id' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
# def test_api_field_type_permissions_id(self):
|
||||
# """ Test for type for API Field
|
||||
|
||||
# permissions.id field must be int
|
||||
# """
|
||||
|
||||
# assert type(self.api_data['permissions'][0]['id']) is int
|
||||
|
||||
|
||||
# def test_api_field_exists_permissions_display_name(self):
|
||||
# """ Test for existance of API Field
|
||||
|
||||
# permissions.display_name field must exist
|
||||
# """
|
||||
|
||||
# assert 'display_name' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
# def test_api_field_type_permissions_display_name(self):
|
||||
# """ Test for type for API Field
|
||||
|
||||
# permissions.display_name field must be str
|
||||
# """
|
||||
|
||||
# assert type(self.api_data['permissions'][0]['display_name']) is str
|
||||
|
||||
|
||||
|
||||
# def test_api_field_exists_permissions_url(self):
|
||||
# """ Test for existance of API Field
|
||||
|
||||
# permissions.url field must exist
|
||||
# """
|
||||
|
||||
# assert 'url' in self.api_data['permissions'][0]
|
||||
|
||||
|
||||
# def test_api_field_type_permissions_url(self):
|
||||
# """ Test for type for API Field
|
||||
|
||||
# permissions.url field must be str
|
||||
# """
|
||||
|
||||
# assert type(self.api_data['permissions'][0]['url']) is Hyperlink
|
@ -48,7 +48,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -60,7 +60,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
self.field_after_expected_value = '{"manager": true}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
@ -81,7 +81,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
action = int(History.Actions.DELETE),
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
@ -91,3 +91,13 @@ class TenancyObjectTests(TestCase):
|
||||
"""
|
||||
|
||||
assert self.item.objects is not None
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="write test")
|
||||
def test_field_not_none_organzation(self):
|
||||
""" Ensure field is set
|
||||
|
||||
Field organization must be defined for all tenancy objects
|
||||
"""
|
||||
|
||||
assert self.item.objects is not None
|
||||
|
42
app/access/tests/unit/test_access_viewset.py
Normal file
42
app/access/tests/unit/test_access_viewset.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetCommon
|
||||
|
||||
from access.viewsets.index import Index
|
||||
|
||||
|
||||
class AccessViewset(
|
||||
TestCase,
|
||||
ViewSetCommon
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
|
||||
route_name = 'API:_api_v2_access_home'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_add", password="password", is_superuser=True)
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.route_name + '-list')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
self.http_options_response_list = client.options(url)
|
30
app/access/viewsets/index.py
Normal file
30
app/access/viewsets/index.py
Normal file
@ -0,0 +1,30 @@
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from api.viewsets.common import CommonViewSet
|
||||
|
||||
|
||||
|
||||
@extend_schema(exclude = True)
|
||||
class Index(CommonViewSet):
|
||||
|
||||
allowed_methods: list = [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS'
|
||||
]
|
||||
|
||||
view_description = "Access Module"
|
||||
|
||||
view_name = "Access"
|
||||
|
||||
|
||||
def list(self, request, pk=None):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"organization": reverse('v2:_api_v2_organization-list', request=request)
|
||||
}
|
||||
)
|
89
app/access/viewsets/organization.py
Normal file
89
app/access/viewsets/organization.py
Normal file
@ -0,0 +1,89 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from access.serializers.organization import (
|
||||
Organization,
|
||||
OrganizationModelSerializer,
|
||||
OrganizationViewSerializer
|
||||
)
|
||||
|
||||
from api.viewsets.common import ModelViewSet
|
||||
|
||||
|
||||
|
||||
# @extend_schema(tags=['access'])
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Create an orgnaization',
|
||||
description='',
|
||||
responses = {
|
||||
# 200: OpenApiResponse(description='Allready exists', response=OrganizationViewSerializer),
|
||||
201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
|
||||
# 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing add permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete an orgnaization',
|
||||
description = '',
|
||||
responses = {
|
||||
204: OpenApiResponse(description=''),
|
||||
403: OpenApiResponse(description='User is missing delete permissions'),
|
||||
}
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all orgnaizations',
|
||||
description='',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single orgnaization',
|
||||
description='',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update an orgnaization',
|
||||
description = '',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
|
||||
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
|
||||
# # 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing change permissions'),
|
||||
}
|
||||
),
|
||||
)
|
||||
class ViewSet( ModelViewSet ):
|
||||
|
||||
filterset_fields = [
|
||||
'name',
|
||||
'manager',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'name',
|
||||
]
|
||||
|
||||
model = Organization
|
||||
|
||||
documentation: str = ''
|
||||
|
||||
view_description = 'Centurion Organizations'
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
|
||||
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']
|
||||
|
148
app/access/viewsets/team.py
Normal file
148
app/access/viewsets/team.py
Normal file
@ -0,0 +1,148 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
|
||||
|
||||
from access.serializers.teams import (
|
||||
Team,
|
||||
TeamModelSerializer,
|
||||
TeamViewSerializer
|
||||
)
|
||||
|
||||
from api.viewsets.common import ModelViewSet
|
||||
|
||||
|
||||
|
||||
# @extend_schema(tags=['access'])
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Create a team within this organization',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Allready exists', response=TeamViewSerializer),
|
||||
201: OpenApiResponse(description='Created', response=TeamViewSerializer),
|
||||
# 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing add permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete a team from this organization',
|
||||
description = '',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
204: OpenApiResponse(description=''),
|
||||
403: OpenApiResponse(description='User is missing delete permissions'),
|
||||
}
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all teams from this organization',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single team from this organization',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update a team within this organization',
|
||||
description = '',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamViewSerializer),
|
||||
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
|
||||
# # 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing change permissions'),
|
||||
}
|
||||
),
|
||||
)
|
||||
class ViewSet( ModelViewSet ):
|
||||
|
||||
filterset_fields = [
|
||||
'team_name',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'team_name',
|
||||
]
|
||||
|
||||
model = Team
|
||||
|
||||
documentation: str = ''
|
||||
|
||||
view_description = 'Teams belonging to a single organization'
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
queryset = super().get_queryset()
|
||||
|
||||
queryset = queryset.filter(organization_id=self.kwargs['organization_id'])
|
||||
|
||||
self.queryset = queryset
|
||||
|
||||
return self.queryset
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
|
||||
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']
|
||||
|
174
app/access/viewsets/team_user.py
Normal file
174
app/access/viewsets/team_user.py
Normal file
@ -0,0 +1,174 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
|
||||
|
||||
from access.serializers.team_user import (
|
||||
TeamUsers,
|
||||
TeamUserModelSerializer,
|
||||
TeamUserViewSerializer
|
||||
)
|
||||
|
||||
from api.viewsets.common import ModelViewSet
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Create a user within this team',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'team_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
# 200: OpenApiResponse(description='Allready exists', response=TeamUserViewSerializer),
|
||||
201: OpenApiResponse(description='Created', response=TeamUserViewSerializer),
|
||||
# 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing add permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete a user from this team',
|
||||
description = '',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'team_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
204: OpenApiResponse(description=''),
|
||||
403: OpenApiResponse(description='User is missing delete permissions'),
|
||||
}
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all users from this team',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'team_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single user from this team',
|
||||
description='',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'team_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update a user within this team',
|
||||
description = '',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'organization_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
OpenApiParameter(
|
||||
name = 'team_id',
|
||||
location = 'path',
|
||||
type = int
|
||||
),
|
||||
],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
|
||||
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
|
||||
# # 400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing change permissions'),
|
||||
}
|
||||
),
|
||||
)
|
||||
class ViewSet( ModelViewSet ):
|
||||
|
||||
filterset_fields = [
|
||||
'manager',
|
||||
'team__organization',
|
||||
]
|
||||
|
||||
search_fields = []
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
documentation: str = ''
|
||||
|
||||
view_description = 'Users belonging to a single team'
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
queryset = super().get_queryset()
|
||||
|
||||
queryset = queryset.filter(
|
||||
team_id = self.kwargs['team_id']
|
||||
)
|
||||
|
||||
self.queryset = queryset
|
||||
|
||||
return self.queryset
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ViewSerializer']
|
||||
|
||||
|
||||
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer']
|
||||
|
@ -5,6 +5,20 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
|
||||
|
||||
from api.models.tokens import AuthToken
|
||||
|
||||
# scheme.py
|
||||
from drf_spectacular.extensions import OpenApiAuthenticationExtension
|
||||
|
||||
class TokenScheme(OpenApiAuthenticationExtension):
|
||||
target_class = "api.auth.TokenAuthentication"
|
||||
name = "TokenAuthentication"
|
||||
|
||||
def get_security_definition(self, auto_schema):
|
||||
return {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "Token Authorization",
|
||||
"description": "Token-based authentication with required prefix 'Token'",
|
||||
}
|
||||
|
||||
|
||||
class TokenAuthentication(BaseAuthentication):
|
||||
|
8
app/api/exceptions.py
Normal file
8
app/api/exceptions.py
Normal file
@ -0,0 +1,8 @@
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
class UnknownTicketType(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_detail = 'Unable to determin the ticket type.'
|
||||
default_code = 'unknown_ticket_type'
|
@ -0,0 +1,41 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-13 15:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='authtoken',
|
||||
name='expires',
|
||||
field=models.DateTimeField(help_text='When this token expires', verbose_name='Expiry Date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authtoken',
|
||||
name='id',
|
||||
field=models.AutoField(help_text='ID of this token', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authtoken',
|
||||
name='note',
|
||||
field=models.CharField(blank=True, default=None, help_text='A note about this token', max_length=50, null=True, verbose_name='Note'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authtoken',
|
||||
name='token',
|
||||
field=models.CharField(db_index=True, help_text='The authorization token', max_length=64, unique=True, verbose_name='Auth Token'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authtoken',
|
||||
name='user',
|
||||
field=models.ForeignKey(help_text='User this token belongs to', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner'),
|
||||
),
|
||||
]
|
@ -48,37 +48,45 @@ class AuthToken(models.Model):
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this token',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
note = models.CharField(
|
||||
blank = True,
|
||||
max_length = 50,
|
||||
default = None,
|
||||
help_text = 'A note about this token',
|
||||
max_length = 50,
|
||||
null= True,
|
||||
verbose_name = 'Note'
|
||||
)
|
||||
|
||||
token = models.CharField(
|
||||
verbose_name = 'Auth Token',
|
||||
blank = False,
|
||||
db_index=True,
|
||||
help_text = 'The authorization token',
|
||||
max_length = 64,
|
||||
null = False,
|
||||
blank = False,
|
||||
unique = True,
|
||||
verbose_name = 'Auth Token',
|
||||
)
|
||||
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE
|
||||
help_text = 'User this token belongs to',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'Owner'
|
||||
)
|
||||
|
||||
expires = models.DateTimeField(
|
||||
verbose_name = 'Expiry Date',
|
||||
blank = False,
|
||||
help_text = 'When this token expires',
|
||||
null = False,
|
||||
blank = False
|
||||
verbose_name = 'Expiry Date',
|
||||
)
|
||||
|
||||
|
||||
|
356
app/api/react_ui_metadata.py
Normal file
356
app/api/react_ui_metadata.py
Normal file
@ -0,0 +1,356 @@
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework_json_api.metadata import JSONAPIMetadata
|
||||
from rest_framework.request import clone_request
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.utils.field_mapping import ClassLookupDict
|
||||
|
||||
from rest_framework_json_api.utils import get_related_resource_type
|
||||
|
||||
from app.serializers.user import User, UserBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
from core.fields.badge import BadgeField
|
||||
from core.fields.icon import IconField
|
||||
|
||||
|
||||
|
||||
class OverRideJSONAPIMetadata(JSONAPIMetadata):
|
||||
|
||||
type_lookup = ClassLookupDict(
|
||||
{
|
||||
serializers.Field: "GenericField",
|
||||
serializers.RelatedField: "Relationship",
|
||||
serializers.BooleanField: "Boolean",
|
||||
serializers.CharField: "String",
|
||||
serializers.URLField: "URL",
|
||||
serializers.EmailField: "Email",
|
||||
serializers.RegexField: "Regex",
|
||||
serializers.SlugField: "Slug",
|
||||
serializers.IntegerField: "Integer",
|
||||
serializers.FloatField: "Float",
|
||||
serializers.DecimalField: "Decimal",
|
||||
serializers.DateField: "Date",
|
||||
serializers.DateTimeField: "DateTime",
|
||||
serializers.TimeField: "Time",
|
||||
serializers.ChoiceField: "Choice",
|
||||
serializers.MultipleChoiceField: "MultipleChoice",
|
||||
serializers.FileField: "File",
|
||||
serializers.ImageField: "Image",
|
||||
serializers.ListField: "List",
|
||||
serializers.DictField: "Dict",
|
||||
serializers.Serializer: "Serializer",
|
||||
serializers.JSONField: "JSON", # New. Does not exist in base class
|
||||
BadgeField: 'Badge',
|
||||
IconField: 'Icon',
|
||||
User: 'Relationship',
|
||||
UserBaseSerializer: 'Relationship',
|
||||
centurion_field.CharField: 'String',
|
||||
centurion_field.MarkdownField: 'Markdown'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
|
||||
|
||||
def determine_metadata(self, request, view):
|
||||
|
||||
metadata = {}
|
||||
|
||||
metadata["name"] = view.get_view_name()
|
||||
|
||||
metadata["description"] = view.get_view_description()
|
||||
|
||||
if 'pk' in view.kwargs:
|
||||
|
||||
if view.kwargs['pk']:
|
||||
|
||||
qs = view.get_queryset()[0]
|
||||
|
||||
if hasattr(qs, 'get_url'):
|
||||
|
||||
metadata['return_url'] = qs.get_url( request )
|
||||
|
||||
elif view.kwargs:
|
||||
|
||||
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
|
||||
|
||||
else:
|
||||
|
||||
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request )
|
||||
|
||||
|
||||
metadata["renders"] = [
|
||||
renderer.media_type for renderer in view.renderer_classes
|
||||
]
|
||||
|
||||
metadata["parses"] = [parser.media_type for parser in view.parser_classes]
|
||||
|
||||
metadata["allowed_methods"] = view.allowed_methods
|
||||
|
||||
if hasattr(view, 'get_serializer'):
|
||||
serializer = view.get_serializer()
|
||||
metadata['fields'] = self.get_serializer_info(serializer)
|
||||
|
||||
|
||||
if view.suffix == 'Instance':
|
||||
|
||||
metadata['layout'] = view.get_page_layout()
|
||||
|
||||
|
||||
if hasattr(view, 'get_model_documentation'):
|
||||
|
||||
if view.get_model_documentation():
|
||||
|
||||
metadata['documentation'] = view.get_model_documentation()
|
||||
|
||||
|
||||
elif view.suffix == 'List':
|
||||
|
||||
if hasattr(view, 'table_fields'):
|
||||
|
||||
metadata['table_fields'] = view.get_table_fields()
|
||||
|
||||
if view.documentation:
|
||||
|
||||
metadata['documentation'] = view.documentation
|
||||
|
||||
if hasattr(view, 'page_layout'):
|
||||
|
||||
metadata['layout'] = view.get_page_layout()
|
||||
|
||||
|
||||
metadata['navigation'] = [
|
||||
{
|
||||
"display_name": "Access",
|
||||
"name": "access",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Organization",
|
||||
"name": "organization",
|
||||
"link": "/access/organization"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_name": "Assistance",
|
||||
"name": "assistance",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Requests",
|
||||
"name": "request",
|
||||
"icon": "ticket_request",
|
||||
"link": "/assistance/ticket/request"
|
||||
},
|
||||
{
|
||||
"display_name": "Knowledge Base",
|
||||
"name": "knowledge_base",
|
||||
"icon": "information",
|
||||
"link": "/assistance/knowledge_base"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_name": "ITAM",
|
||||
"name": "itam",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Devices",
|
||||
"name": "device",
|
||||
"icon": "device",
|
||||
"link": "/itam/device"
|
||||
},
|
||||
{
|
||||
"display_name": "Operating System",
|
||||
"name": "operating_system",
|
||||
"link": "/itam/operating_system"
|
||||
},
|
||||
{
|
||||
"display_name": "Software",
|
||||
"name": "software",
|
||||
"link": "/itam/software"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_name": "ITIM",
|
||||
"name": "itim",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Changes",
|
||||
"name": "ticket_change",
|
||||
"link": "/itim/ticket/change"
|
||||
},
|
||||
{
|
||||
"display_name": "Clusters",
|
||||
"name": "cluster",
|
||||
"link": "/itim/cluster"
|
||||
},
|
||||
{
|
||||
"display_name": "Incidents",
|
||||
"name": "ticket_incident",
|
||||
"link": "/itim/ticket/incident"
|
||||
},
|
||||
{
|
||||
"display_name": "Problems",
|
||||
"name": "ticket_problem",
|
||||
"link": "/itim/ticket/problem"
|
||||
},
|
||||
{
|
||||
"display_name": "Services",
|
||||
"name": "service",
|
||||
"link": "/itim/service"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_name": "Config Management",
|
||||
"name": "config_management",
|
||||
"icon": "ansible",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Groups",
|
||||
"name": "group",
|
||||
"icon": 'config_management',
|
||||
"link": "/config_management/group"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"display_name": "Project Management",
|
||||
"name": "project_management",
|
||||
"icon": 'project',
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "Projects",
|
||||
"name": "project",
|
||||
"icon": 'kanban',
|
||||
"link": "/project_management/project"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"display_name": "Settings",
|
||||
"name": "settings",
|
||||
"pages": [
|
||||
{
|
||||
"display_name": "System",
|
||||
"name": "setting",
|
||||
"icon": "system",
|
||||
"link": "/settings"
|
||||
},
|
||||
{
|
||||
"display_name": "Task Log",
|
||||
"name": "celery_log",
|
||||
# "icon": "settings",
|
||||
"link": "/settings/celery_log"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
|
||||
|
||||
def get_field_info(self, field):
|
||||
""" Custom from `rest_framewarok_json_api.metadata.py`
|
||||
|
||||
Require that read-only fields have their choices added to the
|
||||
metadata.
|
||||
|
||||
Given an instance of a serializer field, return a dictionary
|
||||
of metadata about it.
|
||||
"""
|
||||
field_info = {}
|
||||
serializer = field.parent
|
||||
|
||||
if hasattr(field, 'textarea'):
|
||||
|
||||
if field.textarea:
|
||||
|
||||
field_info["multi_line"] = True
|
||||
|
||||
if isinstance(field, serializers.ManyRelatedField):
|
||||
field_info["type"] = self.type_lookup[field.child_relation]
|
||||
else:
|
||||
field_info["type"] = self.type_lookup[field]
|
||||
|
||||
try:
|
||||
serializer_model = serializer.Meta.model
|
||||
field_info["relationship_type"] = self.relation_type_lookup[
|
||||
getattr(serializer_model, field.field_name)
|
||||
]
|
||||
except KeyError:
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
field_info["relationship_resource"] = get_related_resource_type(field)
|
||||
|
||||
if hasattr(field, 'autolink'):
|
||||
|
||||
if field.autolink:
|
||||
|
||||
field_info['autolink'] = field.autolink
|
||||
|
||||
|
||||
field_info["required"] = getattr(field, "required", False)
|
||||
|
||||
|
||||
if hasattr(field, 'style_class'):
|
||||
|
||||
field_info["style"]: dict = {
|
||||
'class': field.style_class
|
||||
}
|
||||
|
||||
|
||||
attrs = [
|
||||
"read_only",
|
||||
"write_only",
|
||||
"label",
|
||||
"help_text",
|
||||
"min_length",
|
||||
"max_length",
|
||||
"min_value",
|
||||
"max_value",
|
||||
"initial",
|
||||
]
|
||||
|
||||
for attr in attrs:
|
||||
value = getattr(field, attr, None)
|
||||
if value is not None and value != "":
|
||||
field_info[attr] = force_str(value, strings_only=True)
|
||||
|
||||
if getattr(field, "child", None):
|
||||
field_info["child"] = self.get_field_info(field.child)
|
||||
elif getattr(field, "fields", None):
|
||||
field_info["children"] = self.get_serializer_info(field)
|
||||
|
||||
if (
|
||||
# not field_info.get("read_only")
|
||||
hasattr(field, "choices")
|
||||
):
|
||||
field_info["choices"] = [
|
||||
{
|
||||
"value": choice_value,
|
||||
"display_name": force_str(choice_name, strings_only=True),
|
||||
}
|
||||
for choice_value, choice_name in field.choices.items()
|
||||
]
|
||||
|
||||
if (
|
||||
hasattr(serializer, "included_serializers")
|
||||
and "relationship_resource" in field_info
|
||||
):
|
||||
field_info["allows_include"] = (
|
||||
field.field_name in serializer.included_serializers
|
||||
)
|
||||
|
||||
return field_info
|
@ -25,7 +25,7 @@ class TeamSerializerBase(serializers.ModelSerializer):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_team", args=[obj.organization.id,obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_api_team", args=[obj.organization.id,obj.pk]))
|
||||
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ class TeamSerializer(TeamSerializerBase):
|
||||
|
||||
team = Team.objects.get(pk=obj.id)
|
||||
|
||||
return request.build_absolute_uri(reverse('API:_api_team_permission', args=[team.organization_id,team.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_team_permission', args=[team.organization_id,team.id]))
|
||||
|
||||
|
||||
def validate(self, data):
|
||||
@ -67,7 +67,7 @@ class TeamSerializer(TeamSerializerBase):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse('API:_api_team', args=[obj.organization_id,obj.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_team', args=[obj.organization_id,obj.id]))
|
||||
|
||||
|
||||
class Meta:
|
||||
@ -93,7 +93,7 @@ class TeamSerializer(TeamSerializerBase):
|
||||
class OrganizationListSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_organization", format="html"
|
||||
view_name="v1:_api_organization", format="html"
|
||||
)
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ class OrganizationListSerializer(serializers.ModelSerializer):
|
||||
class OrganizationSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_organization", format="html"
|
||||
view_name="v1:_api_organization", format="html"
|
||||
)
|
||||
|
||||
team_url = serializers.SerializerMethodField('get_url')
|
||||
@ -121,11 +121,11 @@ class OrganizationSerializer(serializers.ModelSerializer):
|
||||
|
||||
team = Team.objects.filter(pk=obj.id)
|
||||
|
||||
return request.build_absolute_uri(reverse('API:_api_organization_teams', args=[obj.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_organization_teams', args=[obj.id]))
|
||||
|
||||
teams = TeamSerializer(source='team_set', many=True, read_only=False)
|
||||
|
||||
view_name="API:_api_organization"
|
||||
view_name="v1:_api_organization"
|
||||
|
||||
|
||||
class Meta:
|
||||
|
63
app/api/serializers/assistance/request.py
Normal file
63
app/api/serializers/assistance/request.py
Normal file
@ -0,0 +1,63 @@
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket import TicketSerializer
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class RequestTicketSerializer(
|
||||
TicketSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'estimate',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
# 'planned_start_date',
|
||||
# 'planned_finish_date',
|
||||
# 'real_start_date',
|
||||
# 'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'milestone',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'ticket_type',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
|
||||
request = True
|
||||
)
|
16
app/api/serializers/common.py
Normal file
16
app/api/serializers/common.py
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
|
||||
|
||||
class CommonBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class CommonModelSerializer(CommonBaseSerializer):
|
||||
|
||||
model_notes = centurion_field.MarkdownField( required = False )
|
@ -28,7 +28,7 @@ class ParentGroupSerializer(serializers.ModelSerializer):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
|
||||
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ class ConfigGroupsSerializerBase(serializers.ModelSerializer):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
|
||||
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ class ConfigGroupsSerializer(ConfigGroupsSerializerBase):
|
||||
'parent',
|
||||
'name',
|
||||
'config',
|
||||
'hosts',
|
||||
'url',
|
||||
]
|
||||
read_only_fields = [
|
||||
|
196
app/api/serializers/core/ticket.py
Normal file
196
app/api/serializers/core/ticket.py
Normal file
@ -0,0 +1,196 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket_comment import TicketCommentSerializer
|
||||
|
||||
from core import exceptions as centurion_exception
|
||||
from core.forms.validate_ticket import TicketValidation
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class TicketSerializer(
|
||||
serializers.ModelSerializer,
|
||||
TicketValidation,
|
||||
):
|
||||
|
||||
url = serializers.SerializerMethodField('get_url_ticket')
|
||||
|
||||
|
||||
def get_url_ticket(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
kwargs: dict = {
|
||||
'pk': item.id
|
||||
}
|
||||
|
||||
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
|
||||
|
||||
view_name = '_api_itim_change'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
|
||||
|
||||
view_name = '_api_itim_incident'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
|
||||
|
||||
view_name = '_api_itim_problem'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
|
||||
|
||||
view_name = '_api_assistance_request'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
|
||||
|
||||
view_name = '_api_project_tasks'
|
||||
|
||||
kwargs.update({'project_id': item.project.id})
|
||||
else:
|
||||
|
||||
raise ValueError('Serializer unable to obtain ticket type')
|
||||
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'v1:' + view_name + '-detail',
|
||||
kwargs = kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
ticket_comments = serializers.SerializerMethodField('get_url_ticket_comments')
|
||||
|
||||
|
||||
def get_url_ticket_comments(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
kwargs: dict = {
|
||||
'ticket_id': item.id
|
||||
}
|
||||
|
||||
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
|
||||
|
||||
view_name = '_api_itim_change_ticket_comments'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
|
||||
|
||||
view_name = '_api_itim_incident_ticket_comments'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
|
||||
|
||||
view_name = '_api_itim_problem_ticket_comments'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
|
||||
|
||||
view_name = '_api_assistance_request_ticket_comments'
|
||||
|
||||
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
|
||||
|
||||
view_name = '_api_project_tasks_comments'
|
||||
|
||||
kwargs.update({'project_id': item.project.id})
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError('Serializer unable to obtain ticket type')
|
||||
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'v1:' + view_name + '-list',
|
||||
kwargs = kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
'planned_start_date',
|
||||
'planned_finish_date',
|
||||
'real_start_date',
|
||||
'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
self.fields.fields['status'].initial = Ticket.TicketStatus.All.NEW
|
||||
self.fields.fields['status'].default = Ticket.TicketStatus.All.NEW
|
||||
|
||||
self.ticket_type_fields = self.Meta.fields
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields['organization'].required = True
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
try:
|
||||
|
||||
self.request = self._context['request']
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self._ticket_type = str(self.fields['ticket_type'].choices[self._context['view']._ticket_type_value]).lower().replace(' ', '_')
|
||||
|
||||
self.validated_data['ticket_type'] = int(self._context['view']._ticket_type_value)
|
||||
|
||||
is_valid = self.validate_ticket()
|
||||
|
||||
if self.instance is None:
|
||||
|
||||
subscribed_users: list = []
|
||||
|
||||
if 'subscribed_users' in self.validated_data:
|
||||
|
||||
subscribed_users = self.validated_data['subscribed_users']
|
||||
|
||||
self.validated_data['subscribed_users'] = subscribed_users + [ self.validated_data['opened_by'] ]
|
||||
|
||||
except Exception as unhandled_exception:
|
||||
|
||||
centurion_exception.ParseError(
|
||||
detail=f"Server encountered an error during validation, Traceback: {unhandled_exception.with_traceback}"
|
||||
)
|
||||
|
||||
return is_valid
|
44
app/api/serializers/core/ticket_category.py
Normal file
44
app/api/serializers/core/ticket_category.py
Normal file
@ -0,0 +1,44 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket_comment import TicketCommentSerializer
|
||||
|
||||
from core.forms.validate_ticket import TicketValidation
|
||||
from core.models.ticket.ticket_category import TicketCategory
|
||||
|
||||
|
||||
|
||||
class TicketCategorySerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v1:_api_ticket_category-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TicketCategory
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
if instance is not None:
|
||||
|
||||
if hasattr(instance, 'id'):
|
||||
|
||||
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
|
||||
id=instance.id
|
||||
)
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
74
app/api/serializers/core/ticket_comment.py
Normal file
74
app/api/serializers/core/ticket_comment.py
Normal file
@ -0,0 +1,74 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from core.models.ticket.ticket_comment import Ticket, TicketComment
|
||||
|
||||
|
||||
|
||||
class TicketCommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
url = serializers.SerializerMethodField('get_url_ticket_comment')
|
||||
|
||||
def get_url_ticket_comment(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
if item.ticket.ticket_type == item.ticket.__class__.TicketType.CHANGE:
|
||||
|
||||
view_name = '_api_itim_change_ticket_comments'
|
||||
|
||||
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.INCIDENT:
|
||||
|
||||
view_name = '_api_itim_incident_ticket_comments'
|
||||
|
||||
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.PROBLEM:
|
||||
|
||||
view_name = '_api_itim_problem_ticket_comments'
|
||||
|
||||
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.REQUEST:
|
||||
|
||||
view_name = '_api_assistance_request_ticket_comments'
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError('Serializer unable to obtain ticket type')
|
||||
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse('v1:' + view_name + '-detail',
|
||||
kwargs={
|
||||
'ticket_id': item.ticket.id,
|
||||
'pk': item.id
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
class Meta:
|
||||
model = TicketComment
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
if 'context' in self._kwargs:
|
||||
|
||||
if 'view' in self._kwargs['context']:
|
||||
|
||||
if 'ticket_id' in self._kwargs['context']['view'].kwargs:
|
||||
|
||||
ticket = Ticket.objects.get(pk=int(self._kwargs['context']['view'].kwargs['ticket_id']))
|
||||
self.fields.fields['organization'].initial = ticket.organization.id
|
||||
|
||||
self.fields.fields['ticket'].initial = int(self._kwargs['context']['view'].kwargs['ticket_id'])
|
||||
|
||||
self.fields.fields['comment_type'].initial = TicketComment.CommentType.COMMENT
|
||||
|
||||
self.fields.fields['user'].initial = kwargs['context']['request']._user.id
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
42
app/api/serializers/core/ticket_comment_category.py
Normal file
42
app/api/serializers/core/ticket_comment_category.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
|
||||
from core.models.ticket.ticket_comment_category import TicketCommentCategory
|
||||
|
||||
|
||||
|
||||
class TicketCommentCategorySerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v1:_api_ticket_comment_category-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TicketCommentCategory
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
if instance is not None:
|
||||
|
||||
if hasattr(instance, 'id'):
|
||||
|
||||
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
|
||||
id=instance.id
|
||||
)
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
@ -1,6 +1,9 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.html import escape
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
|
||||
class Inventory:
|
||||
""" Inventory Object
|
||||
|
||||
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||
|
||||
from api.serializers.config import ParentGroupSerializer
|
||||
|
||||
from config_management.models.groups import ConfigGroupHosts
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
@ -12,15 +12,13 @@ from itam.models.device import Device
|
||||
|
||||
class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
|
||||
|
||||
name = serializers.CharField(source='group.name', read_only=True)
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_config_group", format="html"
|
||||
view_name="v1:_api_config_group", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ConfigGroupHosts
|
||||
model = ConfigGroups
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
@ -38,22 +36,21 @@ class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:device-detail", format="html"
|
||||
view_name="v1:device-detail", format="html"
|
||||
)
|
||||
|
||||
config = serializers.SerializerMethodField('get_device_config')
|
||||
|
||||
groups = DeviceConfigGroupsSerializer(source='configgrouphosts_set', many=True, read_only=True)
|
||||
groups = DeviceConfigGroupsSerializer(source='configgroups_set', many=True, read_only=True)
|
||||
|
||||
def get_device_config(self, device):
|
||||
|
||||
request = self.context.get('request')
|
||||
return request.build_absolute_uri(reverse('API:_api_device_config', args=[device.slug]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_device_config', args=[device.slug]))
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
depth = 1
|
||||
fields = [
|
||||
'id',
|
||||
'is_global',
|
||||
|
@ -7,7 +7,7 @@ from itam.models.device import Software
|
||||
class SoftwareSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:software-detail", format="html"
|
||||
view_name="v1:software-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
63
app/api/serializers/itim/change.py
Normal file
63
app/api/serializers/itim/change.py
Normal file
@ -0,0 +1,63 @@
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket import TicketSerializer
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class ChangeTicketSerializer(
|
||||
TicketSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'estimate',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
# 'planned_start_date',
|
||||
# 'planned_finish_date',
|
||||
# 'real_start_date',
|
||||
# 'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'milestone',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'ticket_type',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
|
||||
project_task = True
|
||||
)
|
63
app/api/serializers/itim/incident.py
Normal file
63
app/api/serializers/itim/incident.py
Normal file
@ -0,0 +1,63 @@
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket import TicketSerializer
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class IncidentTicketSerializer(
|
||||
TicketSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'estimate',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
# 'planned_start_date',
|
||||
# 'planned_finish_date',
|
||||
# 'real_start_date',
|
||||
# 'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'milestone',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'ticket_type',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
|
||||
incident = True
|
||||
)
|
63
app/api/serializers/itim/problem.py
Normal file
63
app/api/serializers/itim/problem.py
Normal file
@ -0,0 +1,63 @@
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket import TicketSerializer
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class ProblemTicketSerializer(
|
||||
TicketSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'estimate',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
# 'planned_start_date',
|
||||
# 'planned_finish_date',
|
||||
# 'real_start_date',
|
||||
# 'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'milestone',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'ticket_type',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
|
||||
problem = True
|
||||
)
|
74
app/api/serializers/project_management/project_milestone.py
Normal file
74
app/api/serializers/project_management/project_milestone.py
Normal file
@ -0,0 +1,74 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from project_management.models.projects import Project
|
||||
from project_management.models.project_milestone import ProjectMilestone
|
||||
|
||||
|
||||
|
||||
class ProjectMilestoneSerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
url = serializers.SerializerMethodField('get_url_project_milestone')
|
||||
|
||||
def get_url_project_milestone(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse('v1:_api_project_milestone-detail',
|
||||
kwargs={
|
||||
'project_id': item.project.id,
|
||||
'pk': item.id
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ProjectMilestone
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'description',
|
||||
'organization',
|
||||
'project',
|
||||
'start_date',
|
||||
'finish_date',
|
||||
'created',
|
||||
'modified',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
self.fields.fields['organization'].read_only = True
|
||||
self.fields.fields['project'].read_only = True
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
project = Project.objects.get(
|
||||
pk = int(self._kwargs['context']['view'].kwargs['project_id'])
|
||||
)
|
||||
|
||||
self._validated_data.update({
|
||||
'organization': project.organization,
|
||||
'project': project
|
||||
})
|
||||
|
||||
return is_valid
|
33
app/api/serializers/project_management/project_state.py
Normal file
33
app/api/serializers/project_management/project_state.py
Normal file
@ -0,0 +1,33 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from project_management.models.project_states import ProjectState
|
||||
|
||||
|
||||
|
||||
class ProjectStateSerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v1:_api_project_state-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ProjectState
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
63
app/api/serializers/project_management/project_task.py
Normal file
63
app/api/serializers/project_management/project_task.py
Normal file
@ -0,0 +1,63 @@
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from api.serializers.core.ticket import TicketSerializer
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class ProjectTaskSerializer(
|
||||
TicketSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Ticket
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'assigned_teams',
|
||||
'assigned_users',
|
||||
'category',
|
||||
'created',
|
||||
'modified',
|
||||
'status',
|
||||
'title',
|
||||
'description',
|
||||
'estimate',
|
||||
'urgency',
|
||||
'impact',
|
||||
'priority',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'ticket_type',
|
||||
'is_deleted',
|
||||
'date_closed',
|
||||
'planned_start_date',
|
||||
'planned_finish_date',
|
||||
'real_start_date',
|
||||
'real_finish_date',
|
||||
'opened_by',
|
||||
'organization',
|
||||
'project',
|
||||
'milestone',
|
||||
'subscribed_teams',
|
||||
'subscribed_users',
|
||||
'ticket_comments',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'ticket_type',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
||||
|
||||
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
|
||||
project_task = True
|
||||
)
|
33
app/api/serializers/project_management/project_type.py
Normal file
33
app/api/serializers/project_management/project_type.py
Normal file
@ -0,0 +1,33 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from project_management.models.project_types import ProjectType
|
||||
|
||||
|
||||
|
||||
class ProjectTypeSerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v1:_api_project_state-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ProjectType
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, instance=None, data=empty, **kwargs):
|
||||
|
||||
super().__init__(instance=instance, data=data, **kwargs)
|
134
app/api/serializers/project_management/projects.py
Normal file
134
app/api/serializers/project_management/projects.py
Normal file
@ -0,0 +1,134 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
|
||||
from project_management.models.projects import Project
|
||||
|
||||
|
||||
|
||||
class ProjectSerializer(
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
percent_completed = serializers.CharField(
|
||||
read_only = True,
|
||||
)
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
def get_url(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("v1:_api_projects-detail", args=[item.pk]))
|
||||
|
||||
|
||||
project_tasks_url = serializers.SerializerMethodField('get_url_project_tasks')
|
||||
|
||||
|
||||
def get_url_project_tasks(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'v1:_api_project_tasks-list',
|
||||
kwargs={
|
||||
'project_id': item.id
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
project_milestone_url = serializers.SerializerMethodField('get_url_project_milestone')
|
||||
|
||||
def get_url_project_milestone(self, item):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'v1:_api_project_milestone-list',
|
||||
kwargs={
|
||||
'project_id': item.id
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Project
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'state',
|
||||
'project_type',
|
||||
'priority',
|
||||
'name',
|
||||
'description',
|
||||
'code',
|
||||
'planned_start_date',
|
||||
'planned_finish_date',
|
||||
'real_start_date',
|
||||
'real_finish_date',
|
||||
'manager_user',
|
||||
'manager_team',
|
||||
'team_members',
|
||||
'project_tasks_url',
|
||||
'project_milestone_url',
|
||||
'percent_completed',
|
||||
'created',
|
||||
'modified',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class ProjectImportSerializer(ProjectSerializer):
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Project
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'state',
|
||||
'project_type',
|
||||
'priority',
|
||||
'name',
|
||||
'description',
|
||||
'code',
|
||||
'planned_start_date',
|
||||
'planned_finish_date',
|
||||
'real_start_date',
|
||||
'real_finish_date',
|
||||
'manager_user',
|
||||
'manager_team',
|
||||
'team_members',
|
||||
'project_tasks_url',
|
||||
'project_milestone_url',
|
||||
'percent_completed',
|
||||
'created',
|
||||
'modified',
|
||||
'external_ref',
|
||||
'external_system',
|
||||
'is_deleted',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'url',
|
||||
]
|
@ -9,7 +9,7 @@ from celery import states
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from api.serializers.inventory import Inventory
|
||||
from itam.serializers.inventory import InventorySerializer
|
||||
|
||||
from itam.models.device import Device, DeviceType, DeviceOperatingSystem, DeviceSoftware
|
||||
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
|
||||
@ -32,8 +32,15 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
logger.info('Begin Processing Inventory')
|
||||
|
||||
data = json.loads(data)
|
||||
data = Inventory(data)
|
||||
if type(data) is str:
|
||||
|
||||
data = json.loads(data)
|
||||
|
||||
data = InventorySerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
data.is_valid()
|
||||
|
||||
organization = Organization.objects.get(id=organization)
|
||||
|
||||
@ -42,13 +49,13 @@ def process_inventory(self, data, organization: int):
|
||||
device_serial_number = None
|
||||
device_uuid = None
|
||||
|
||||
if data.details.serial_number and str(data.details.serial_number).lower() != 'na':
|
||||
if data.validated_data['details']['serial_number'] and str(data.validated_data['details']['serial_number']).lower() != 'na':
|
||||
|
||||
device_serial_number = str(data.details.serial_number)
|
||||
device_serial_number = str(data.validated_data['details']['serial_number'])
|
||||
|
||||
if data.details.uuid and str(data.details.uuid).lower() != 'na':
|
||||
if data.validated_data['details']['uuid'] and str(data.validated_data['details']['uuid']).lower() != 'na':
|
||||
|
||||
device_uuid = str(data.details.uuid)
|
||||
device_uuid = str(data.validated_data['details']['uuid'])
|
||||
|
||||
|
||||
if device_serial_number: # Search for device by serial number.
|
||||
@ -88,13 +95,13 @@ def process_inventory(self, data, organization: int):
|
||||
if not device: # Search for device by Name.
|
||||
|
||||
device = Device.objects.filter(
|
||||
name__iexact=str(data.details.name).lower()
|
||||
name__iexact=str(data.validated_data['details']['name']).lower()
|
||||
)
|
||||
|
||||
if device.exists():
|
||||
|
||||
device = Device.objects.get(
|
||||
name__iexact=str(data.details.name).lower()
|
||||
name__iexact=str(data.validated_data['details']['name']).lower()
|
||||
)
|
||||
|
||||
else:
|
||||
@ -107,7 +114,7 @@ def process_inventory(self, data, organization: int):
|
||||
if not device: # Create the device
|
||||
|
||||
device = Device.objects.create(
|
||||
name = data.details.name,
|
||||
name = data.validated_data['details']['name'],
|
||||
device_type = None,
|
||||
serial_number = device_serial_number,
|
||||
uuid = device_uuid,
|
||||
@ -131,14 +138,14 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
if not device.serial_number and device_serial_number:
|
||||
|
||||
device.serial_number = data.details.serial_number
|
||||
device.serial_number = data.validated_data['details']['serial_number']
|
||||
|
||||
device_edited = True
|
||||
|
||||
|
||||
if str(device.name).lower() != str(data.details.name).lower(): # Update device Name
|
||||
if str(device.name).lower() != str(data.validated_data['details']['name']).lower(): # Update device Name
|
||||
|
||||
device.name = data.details.name
|
||||
device.name = data.validated_data['details']['name']
|
||||
|
||||
device_edited = True
|
||||
|
||||
@ -149,14 +156,14 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
|
||||
operating_system = OperatingSystem.objects.filter(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
is_global = True
|
||||
)
|
||||
|
||||
if operating_system.exists():
|
||||
|
||||
operating_system = OperatingSystem.objects.get(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
is_global = True
|
||||
)
|
||||
|
||||
@ -170,7 +177,7 @@ def process_inventory(self, data, organization: int):
|
||||
if not operating_system:
|
||||
|
||||
operating_system = OperatingSystem.objects.filter(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization
|
||||
)
|
||||
|
||||
@ -178,7 +185,7 @@ def process_inventory(self, data, organization: int):
|
||||
if operating_system.exists():
|
||||
|
||||
operating_system = OperatingSystem.objects.get(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization
|
||||
)
|
||||
|
||||
@ -190,22 +197,22 @@ def process_inventory(self, data, organization: int):
|
||||
if not operating_system:
|
||||
|
||||
operating_system = OperatingSystem.objects.create(
|
||||
name = data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization,
|
||||
is_global = True
|
||||
)
|
||||
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.filter(
|
||||
name=data.operating_system.version_major,
|
||||
operating_system=operating_system
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system
|
||||
)
|
||||
|
||||
if operating_system_version.exists():
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.get(
|
||||
name=data.operating_system.version_major,
|
||||
operating_system=operating_system
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system
|
||||
)
|
||||
|
||||
else:
|
||||
@ -218,7 +225,7 @@ def process_inventory(self, data, organization: int):
|
||||
operating_system_version = OperatingSystemVersion.objects.create(
|
||||
organization = organization,
|
||||
is_global = True,
|
||||
name = data.operating_system.version_major,
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system,
|
||||
)
|
||||
|
||||
@ -241,8 +248,8 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
device_operating_system = DeviceOperatingSystem.objects.create(
|
||||
organization = organization,
|
||||
device=device,
|
||||
version = data.operating_system.version,
|
||||
device = device,
|
||||
version = data.validated_data['os']['version'],
|
||||
operating_system_version = operating_system_version,
|
||||
installdate = timezone.now()
|
||||
)
|
||||
@ -261,9 +268,9 @@ def process_inventory(self, data, organization: int):
|
||||
device_operating_system.save()
|
||||
|
||||
|
||||
if device_operating_system.version != data.operating_system.version:
|
||||
if device_operating_system.version != data.validated_data['os']['version']:
|
||||
|
||||
device_operating_system.version = data.operating_system.version
|
||||
device_operating_system.version = data.validated_data['os']['version']
|
||||
|
||||
device_operating_system.save()
|
||||
|
||||
@ -287,7 +294,7 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
inventoried_software: list = []
|
||||
|
||||
for inventory in list(data.software):
|
||||
for inventory in list(data.validated_data['software']):
|
||||
|
||||
software = None
|
||||
software_category = None
|
||||
@ -295,13 +302,13 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
device_software = None
|
||||
|
||||
software_category = SoftwareCategory.objects.filter( name = inventory.category )
|
||||
software_category = SoftwareCategory.objects.filter( name = inventory['category'] )
|
||||
|
||||
|
||||
if software_category.exists():
|
||||
|
||||
software_category = SoftwareCategory.objects.get(
|
||||
name = inventory.category
|
||||
name = inventory['category']
|
||||
)
|
||||
|
||||
else: # Create Software Category
|
||||
@ -309,16 +316,16 @@ def process_inventory(self, data, organization: int):
|
||||
software_category = SoftwareCategory.objects.create(
|
||||
organization = software_category_organization,
|
||||
is_global = True,
|
||||
name = inventory.category,
|
||||
name = inventory['category'],
|
||||
)
|
||||
|
||||
|
||||
if software_category.name == inventory.category:
|
||||
if software_category.name == inventory['category']:
|
||||
|
||||
if Software.objects.filter( name = inventory.name ).exists():
|
||||
if Software.objects.filter( name = inventory['name'] ).exists():
|
||||
|
||||
software = Software.objects.get(
|
||||
name = inventory.name
|
||||
name = inventory['name']
|
||||
)
|
||||
|
||||
if not software.category:
|
||||
@ -331,16 +338,16 @@ def process_inventory(self, data, organization: int):
|
||||
software = Software.objects.create(
|
||||
organization = software_organization,
|
||||
is_global = True,
|
||||
name = inventory.name,
|
||||
name = inventory['name'],
|
||||
category = software_category,
|
||||
)
|
||||
|
||||
|
||||
if software.name == inventory.name:
|
||||
if software.name == inventory['name']:
|
||||
|
||||
pattern = r"^(\d+:)?(?P<semver>\d+\.\d+(\.\d+)?)"
|
||||
|
||||
semver = re.search(pattern, str(inventory.version), re.DOTALL)
|
||||
semver = re.search(pattern, str(inventory['version']), re.DOTALL)
|
||||
|
||||
|
||||
if semver:
|
||||
@ -348,7 +355,7 @@ def process_inventory(self, data, organization: int):
|
||||
semver = semver['semver']
|
||||
|
||||
else:
|
||||
semver = inventory.version
|
||||
semver = inventory['version']
|
||||
|
||||
|
||||
if SoftwareVersion.objects.filter( name = semver, software = software ).exists():
|
||||
|
249
app/api/tests/abstract/api_fields.py
Normal file
249
app/api/tests/abstract/api_fields.py
Normal file
@ -0,0 +1,249 @@
|
||||
from rest_framework.relations import Hyperlink
|
||||
|
||||
|
||||
|
||||
class APICommonFields:
|
||||
"""Test Cases for fields common to All API responses
|
||||
|
||||
Must contain:
|
||||
- id
|
||||
- display_name
|
||||
- _urls
|
||||
- _urls._self
|
||||
"""
|
||||
|
||||
|
||||
api_data: object
|
||||
""" API Response data """
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_display_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
display_name field must exist
|
||||
"""
|
||||
|
||||
assert 'display_name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_display_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
display_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['display_name']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_urls(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls field must exist
|
||||
"""
|
||||
|
||||
assert '_urls' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_urls(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']) is dict
|
||||
|
||||
|
||||
def test_api_field_exists_urls_self(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls._self field must exist
|
||||
"""
|
||||
|
||||
assert '_self' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_urls(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls._self field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['_self']) is str
|
||||
|
||||
|
||||
|
||||
class APIModelFields(
|
||||
APICommonFields
|
||||
):
|
||||
"""Test Cases for fields common to All API Model responses
|
||||
|
||||
Must contain:
|
||||
- id
|
||||
- display_name
|
||||
- _urls
|
||||
- _urls._self
|
||||
"""
|
||||
|
||||
|
||||
api_data: object
|
||||
""" API Response data """
|
||||
|
||||
|
||||
def test_api_field_exists_model_notes(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
model_notes field must exist
|
||||
"""
|
||||
|
||||
assert 'model_notes' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_model_notes(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
model_notes field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['model_notes']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_created(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
created field must exist
|
||||
"""
|
||||
|
||||
assert 'created' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_created(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
created field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['created']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_modified(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
modified field must exist
|
||||
"""
|
||||
|
||||
assert 'modified' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_modified(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
modified field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['modified']) is str
|
||||
|
||||
|
||||
|
||||
class APITenancyObject(
|
||||
APIModelFields
|
||||
):
|
||||
|
||||
|
||||
api_data: object
|
||||
""" API Response data """
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_organization(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization field must exist
|
||||
"""
|
||||
|
||||
assert 'organization' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_organization(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']) is dict
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_organization_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['organization']
|
||||
|
||||
|
||||
def test_api_field_type_organization_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization.id field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_organization_display_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization.display_name field must exist
|
||||
"""
|
||||
|
||||
assert 'display_name' in self.api_data['organization']
|
||||
|
||||
|
||||
def test_api_field_type_organization_display_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization.display_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']['display_name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_organization_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization.url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data['organization']
|
||||
|
||||
|
||||
def test_api_field_type_organization_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization.url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']['url']) is Hyperlink
|
@ -194,7 +194,7 @@ class APIPermissionAdd:
|
||||
def test_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
513
app/api/tests/abstract/api_permissions_viewset.py
Normal file
513
app/api/tests/abstract/api_permissions_viewset.py
Normal file
@ -0,0 +1,513 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
|
||||
|
||||
class APIPermissionView:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
|
||||
def test_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self):
|
||||
"""Returned results check
|
||||
|
||||
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['organization']['id']) != self.organization.id:
|
||||
|
||||
contains_different_org = True
|
||||
|
||||
assert not contains_different_org
|
||||
|
||||
|
||||
|
||||
|
||||
class APIPermissionAdd:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_list: str
|
||||
""" URL view name of the item list page """
|
||||
|
||||
url_kwargs: dict = None
|
||||
""" URL view kwargs for the item list page """
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
|
||||
def test_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
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')
|
||||
|
||||
|
||||
response = client.put(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
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.no_permissions_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
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.different_organization_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view 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.view_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class APIPermissionChange:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
change_data: dict = None
|
||||
|
||||
|
||||
def test_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class APIPermissionDelete:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
delete_data: dict = None
|
||||
|
||||
|
||||
def test_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
|
||||
|
||||
class APIPermissions(
|
||||
APIPermissionAdd,
|
||||
APIPermissionChange,
|
||||
APIPermissionDelete,
|
||||
APIPermissionView
|
||||
):
|
||||
""" Abstract class containing all API Permission test cases """
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
163
app/api/tests/abstract/api_serializer_viewset.py
Normal file
163
app/api/tests/abstract/api_serializer_viewset.py
Normal file
@ -0,0 +1,163 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
|
||||
|
||||
class SerializerView:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
|
||||
|
||||
def test_returned_serializer_user_view(self):
|
||||
""" Check correct Serializer is returned
|
||||
|
||||
View action for view user must return `ViewSerializer`
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ViewSerializer')
|
||||
|
||||
|
||||
|
||||
class SerializerAdd:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_list: str
|
||||
""" URL view name of the item list page """
|
||||
|
||||
url_kwargs: dict = None
|
||||
""" URL view kwargs for the item list page """
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
|
||||
def test_returned_serializer_user_add(self):
|
||||
""" Check correct Serializer is returned
|
||||
|
||||
Add action for add user must return `ModelSerializer`
|
||||
"""
|
||||
|
||||
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 str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
|
||||
|
||||
|
||||
|
||||
class SerializerChange:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
change_data: dict = None
|
||||
|
||||
|
||||
def test_returned_serializer_user_change(self):
|
||||
""" Check correct Serializer is returned
|
||||
|
||||
Change action for change user must return `ModelSerializer`
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.patch(url, data=self.change_data, content_type='application/json')
|
||||
|
||||
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
|
||||
|
||||
|
||||
|
||||
class SerializerDelete:
|
||||
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
||||
|
||||
app_namespace: str = None
|
||||
""" URL namespace """
|
||||
|
||||
url_name: str
|
||||
""" URL name of the view to test """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" URL kwargs of the item page """
|
||||
|
||||
delete_data: dict = None
|
||||
|
||||
|
||||
def test_returned_serializer_user_delete(self):
|
||||
""" Check correct Serializer is returned
|
||||
|
||||
Delete action for delete user must return `ModelSerializer`
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url)
|
||||
|
||||
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
|
||||
|
||||
|
||||
|
||||
class SerializersTestCases(
|
||||
SerializerAdd,
|
||||
SerializerChange,
|
||||
SerializerDelete,
|
||||
SerializerView
|
||||
):
|
||||
""" Abstract class containing all ViewSet test cases """
|
||||
|
||||
model: object
|
||||
""" Item Model to test """
|
586
app/api/tests/abstract/viewsets.py
Normal file
586
app/api/tests/abstract/viewsets.py
Normal file
@ -0,0 +1,586 @@
|
||||
from api.react_ui_metadata import ReactUIMetadata
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
class AllViewSet:
|
||||
"""Tests specific to the Viewset
|
||||
|
||||
**Dont include these tests directly, see below for correct class**
|
||||
|
||||
Tests are for ALL viewsets.
|
||||
"""
|
||||
|
||||
viewset = None
|
||||
"""ViewSet to Test"""
|
||||
|
||||
|
||||
|
||||
def test_view_attr_allowed_methods_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'allowed_methods')
|
||||
|
||||
|
||||
def test_view_attr_allowed_methods_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must return a value
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.allowed_methods is not None
|
||||
|
||||
|
||||
def test_view_attr_allowed_methods_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must be of type list
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert type(view_set.allowed_methods) is list
|
||||
|
||||
|
||||
def test_view_attr_allowed_methods_values(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` only contains valid values
|
||||
"""
|
||||
|
||||
# Values valid for index views
|
||||
valid_values: list = [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
]
|
||||
|
||||
all_valid: bool = True
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
for method in list(view_set.allowed_methods):
|
||||
|
||||
if method not in valid_values:
|
||||
|
||||
all_valid = False
|
||||
|
||||
assert all_valid
|
||||
|
||||
|
||||
|
||||
def test_view_attr_metadata_class_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `metadata_class` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'metadata_class')
|
||||
|
||||
|
||||
def test_view_attr_metadata_class_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `metadata_class` must return a value
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.metadata_class is not None
|
||||
|
||||
|
||||
def test_view_attr_metadata_class_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `metadata_class` must be metadata class `ReactUIMetadata`
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.metadata_class is ReactUIMetadata
|
||||
|
||||
|
||||
|
||||
def test_view_attr_permission_classes_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `permission_classes` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'permission_classes')
|
||||
|
||||
|
||||
def test_view_attr_permission_classes_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `permission_classes` must return a value
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.permission_classes is not None
|
||||
|
||||
|
||||
def test_view_attr_permission_classes_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `permission_classes` must be list
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert type(view_set.permission_classes) is list
|
||||
|
||||
|
||||
def test_view_attr_permission_classes_value(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `permission_classes` must be metadata class `ReactUIMetadata`
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.permission_classes[0] is OrganizationPermissionAPI
|
||||
|
||||
assert len(view_set.permission_classes) == 1
|
||||
|
||||
|
||||
|
||||
def test_view_attr_view_description_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_description` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'view_description')
|
||||
|
||||
|
||||
def test_view_attr_view_description_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_description` must return a value
|
||||
"""
|
||||
|
||||
assert self.viewset.view_description is not None
|
||||
|
||||
|
||||
def test_view_attr_view_description_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_description` must be of type str
|
||||
"""
|
||||
|
||||
assert type(self.viewset.view_description) is str
|
||||
|
||||
|
||||
|
||||
def test_view_attr_view_name_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'view_name')
|
||||
|
||||
|
||||
def test_view_attr_view_name_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must return a value
|
||||
"""
|
||||
|
||||
assert self.viewset.view_name is not None
|
||||
|
||||
|
||||
def test_view_attr_view_name_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must be of type str
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
type(view_set.view_name) is str
|
||||
)
|
||||
|
||||
|
||||
|
||||
class APIRenderViewSet:
|
||||
|
||||
"""Function ViewSet test
|
||||
|
||||
**Dont include these tests directly, see below for correct class**
|
||||
|
||||
These tests ensure that the data from the ViewSet is present for a
|
||||
HTTP Request
|
||||
"""
|
||||
|
||||
http_options_response_list: dict = None
|
||||
"""The HTTP/Options Response for the ViewSet"""
|
||||
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must exist
|
||||
"""
|
||||
|
||||
assert 'allowed_methods' in self.http_options_response_list.data
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must return a value
|
||||
"""
|
||||
|
||||
assert len(self.http_options_response_list.data['allowed_methods']) > 0
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` must be of type list
|
||||
"""
|
||||
|
||||
assert type(self.http_options_response_list.data['allowed_methods']) is list
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_values(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` only contains valid values
|
||||
"""
|
||||
|
||||
# Values valid for index views
|
||||
valid_values: list = [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
]
|
||||
|
||||
all_valid: bool = True
|
||||
|
||||
for method in list(self.http_options_response_list.data['allowed_methods']):
|
||||
|
||||
if method not in valid_values:
|
||||
|
||||
all_valid = False
|
||||
|
||||
assert all_valid
|
||||
|
||||
|
||||
|
||||
def test_api_render_field_view_description_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `description` must exist
|
||||
"""
|
||||
|
||||
assert 'description' in self.http_options_response_list.data
|
||||
|
||||
|
||||
def test_api_render_field_view_description_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_description` must return a value
|
||||
"""
|
||||
|
||||
assert self.http_options_response_list.data['description'] is not None
|
||||
|
||||
|
||||
def test_api_render_field_view_description_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_description` must be of type str
|
||||
"""
|
||||
|
||||
assert type(self.http_options_response_list.data['description']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_render_field_view_name_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.http_options_response_list.data
|
||||
|
||||
|
||||
def test_api_render_field_view_name_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must return a value
|
||||
"""
|
||||
|
||||
assert self.http_options_response_list.data['name'] is not None
|
||||
|
||||
|
||||
def test_api_render_field_view_name_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must be of type str
|
||||
"""
|
||||
|
||||
assert type(self.http_options_response_list.data['name']) is str
|
||||
|
||||
|
||||
|
||||
class ModelViewSet(AllViewSet):
|
||||
"""Tests for Model Viewsets
|
||||
|
||||
**Dont include these tests directly, see below for correct class**
|
||||
"""
|
||||
|
||||
viewset = None
|
||||
"""ViewSet to Test"""
|
||||
|
||||
|
||||
|
||||
def test_view_attr_documentation_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `documentation` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'documentation')
|
||||
|
||||
|
||||
def test_view_attr_documentation_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `documentation` must be of type str or None.
|
||||
|
||||
this attribute is optional.
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
type(view_set.documentation) is str
|
||||
or type(view_set.documentation) is None
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_view_attr_filterset_fields_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `filterset_fields` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'filterset_fields')
|
||||
|
||||
|
||||
def test_view_attr_filterset_fields_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `filterset_fields` must return a value
|
||||
"""
|
||||
|
||||
assert self.viewset.filterset_fields is not None
|
||||
|
||||
|
||||
def test_view_attr_filterset_fields_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `filterset_fields` must be of type list
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
type(view_set.filterset_fields) is list
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_view_attr_allowed_methods_values(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` only contains valid values
|
||||
"""
|
||||
|
||||
# Values valid for model views
|
||||
valid_values: list = [
|
||||
'DELETE',
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
]
|
||||
|
||||
all_valid: bool = True
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
for method in list(view_set.allowed_methods):
|
||||
|
||||
if method not in valid_values:
|
||||
|
||||
all_valid = False
|
||||
|
||||
assert all_valid
|
||||
|
||||
|
||||
|
||||
def test_view_attr_model_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'model')
|
||||
|
||||
|
||||
def test_view_attr_model_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `model` must return a value
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert view_set.model is not None
|
||||
|
||||
|
||||
|
||||
def test_view_attr_search_fields_exists(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `search_fields` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.viewset, 'search_fields')
|
||||
|
||||
|
||||
def test_view_attr_search_fields_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `search_fields` must return a value
|
||||
"""
|
||||
|
||||
assert self.viewset.search_fields is not None
|
||||
|
||||
|
||||
def test_view_attr_search_fields_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `search_fields` must be of type list
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
type(view_set.search_fields) is list
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_view_attr_view_name_not_empty(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must return a value
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
view_set.view_name is not None
|
||||
or view_set.get_view_name() is not None
|
||||
)
|
||||
|
||||
|
||||
def test_view_attr_view_name_type(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `view_name` must be of type str
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
assert (
|
||||
type(view_set.view_name) is str
|
||||
or type(view_set.get_view_name()) is str
|
||||
)
|
||||
|
||||
|
||||
|
||||
class APIRenderModelViewSet(APIRenderViewSet):
|
||||
"""Tests for Model Viewsets
|
||||
|
||||
**Dont include these tests directly, see below for correct class**
|
||||
"""
|
||||
|
||||
viewset = None
|
||||
"""ViewSet to Test"""
|
||||
|
||||
|
||||
def test_api_render_field_allowed_methods_values(self):
|
||||
"""Attribute Test
|
||||
|
||||
Attribute `allowed_methods` only contains valid values
|
||||
"""
|
||||
|
||||
# Values valid for model views
|
||||
valid_values: list = [
|
||||
'DELETE',
|
||||
'GET',
|
||||
'HEAD',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'PUT',
|
||||
]
|
||||
|
||||
all_valid: bool = True
|
||||
|
||||
for method in list(self.http_options_response_list.data['allowed_methods']):
|
||||
|
||||
if method not in valid_values:
|
||||
|
||||
all_valid = False
|
||||
|
||||
assert all_valid
|
||||
|
||||
|
||||
|
||||
class ViewSetCommon(
|
||||
AllViewSet,
|
||||
APIRenderViewSet
|
||||
):
|
||||
""" Tests for Non-Model Viewsets
|
||||
|
||||
**Include this class directly into Non-Model ViewSets**
|
||||
|
||||
Args:
|
||||
AllViewSet (class): Tests for all Viewsets.
|
||||
APIRenderViewSet (class): Tests to check API Rendering to ensure data present.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ViewSetModel(
|
||||
ModelViewSet,
|
||||
APIRenderModelViewSet
|
||||
):
|
||||
"""Tests for model ViewSets
|
||||
|
||||
**Include this class directly into Model ViewSets**
|
||||
|
||||
Args:
|
||||
ModelViewSet (class): Tests for Model Viewsets, includes `AllViewSet` tests.
|
||||
APIRenderModelViewSet (class): Tests to check API rendering to ensure data is present, includes `APIRenderViewSet` tests.
|
||||
"""
|
||||
|
||||
pass
|
@ -160,7 +160,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -182,7 +182,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -201,7 +201,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -220,7 +220,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -239,7 +239,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -395,7 +395,7 @@ class InventoryAPI(TestCase):
|
||||
""" Successful inventory upload returns 200 for existing device"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
@ -409,7 +409,7 @@ class InventoryAPI(TestCase):
|
||||
""" Incorrectly formated inventory upload returns 400 """
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
mod_inventory = self.inventory.copy()
|
||||
|
||||
@ -502,7 +502,8 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
|
||||
|
||||
Device.objects.create(
|
||||
name='random device name',
|
||||
serial_number='serial_number_123'
|
||||
serial_number='serial_number_123',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
@ -537,7 +538,7 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
|
||||
process_inventory(json.dumps(self.inventory), organization.id)
|
||||
|
||||
|
||||
self.device = Device.objects.get(name=self.inventory['details']['name'])
|
||||
self.device = Device.objects.get(name=self.inventory['details']['name'], organization = organization)
|
||||
|
||||
self.operating_system = OperatingSystem.objects.get(name=self.inventory['os']['name'])
|
||||
|
||||
@ -778,7 +779,8 @@ class InventoryAPIDifferentNameUUIDMatch(TestCase):
|
||||
|
||||
Device.objects.create(
|
||||
name='random device name',
|
||||
uuid='123-456-789'
|
||||
uuid='123-456-789',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
|
@ -201,7 +201,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
response = client.put(url, data=self.inventory, content_type='application/json')
|
||||
@ -218,7 +218,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
@ -236,7 +236,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
@ -254,7 +254,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
@ -272,7 +272,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
|
42
app/api/tests/unit/test_index_viewset.py
Normal file
42
app/api/tests/unit/test_index_viewset.py
Normal file
@ -0,0 +1,42 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetCommon
|
||||
|
||||
from api.viewsets.index import Index
|
||||
|
||||
|
||||
class HomeViewset(
|
||||
TestCase,
|
||||
ViewSetCommon
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
|
||||
route_name = 'API:_api_v2_home'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_add", password="password", is_superuser=True)
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.route_name + '-list')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
self.http_options_response_list = client.options(url)
|
@ -5,6 +5,25 @@ from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
from .views import access, config, index
|
||||
|
||||
from api.views.settings import permissions
|
||||
from api.views.settings import index as settings
|
||||
|
||||
from api.views import assistance, itim, project_management
|
||||
from api.views.assistance import request_ticket
|
||||
from api.views.core import (
|
||||
ticket_categories,
|
||||
ticket_comment_categories,
|
||||
ticket_comments as core_ticket_comments
|
||||
)
|
||||
from api.views.itim import change_ticket, incident_ticket, problem_ticket
|
||||
from api.views.project_management import (
|
||||
projects,
|
||||
project_milestone,
|
||||
project_state,
|
||||
project_type,
|
||||
project_task
|
||||
)
|
||||
|
||||
from .views.itam import software, config as itam_config
|
||||
from .views.itam.device import DeviceViewSet
|
||||
from .views.itam import inventory
|
||||
@ -13,15 +32,42 @@ from .views.itam import inventory
|
||||
app_name = "API"
|
||||
|
||||
|
||||
router = DefaultRouter()
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
|
||||
router.register('', index.Index, basename='_api_home')
|
||||
|
||||
router.register('assistance/request', request_ticket.View, basename='_api_assistance_request')
|
||||
router.register('assistance/request/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_assistance_request_ticket_comments')
|
||||
|
||||
router.register('device', DeviceViewSet, basename='device')
|
||||
|
||||
router.register('itim/change', change_ticket.View, basename='_api_itim_change')
|
||||
router.register('itim/change/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_change_ticket_comments')
|
||||
|
||||
router.register('itim/incident', incident_ticket.View, basename='_api_itim_incident')
|
||||
router.register('itim/incident/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_incident_ticket_comments')
|
||||
|
||||
router.register('itim/problem', problem_ticket.View, basename='_api_itim_problem')
|
||||
router.register('itim/problem/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_problem_ticket_comments')
|
||||
|
||||
router.register('project_management/projects', projects.View, basename='_api_projects')
|
||||
router.register('project_management/projects/(?P<project_id>[0-9]+)/milestones', project_milestone.View, basename='_api_project_milestone')
|
||||
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks', project_task.View, basename='_api_project_tasks')
|
||||
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_project_tasks_comments')
|
||||
|
||||
router.register('settings/ticket_categories', ticket_categories.View, basename='_api_ticket_category')
|
||||
|
||||
router.register('settings/project_state', project_state.View, basename='_api_project_state')
|
||||
router.register('settings/project_type', project_type.View, basename='_api_project_type')
|
||||
router.register('settings/ticket_comment_categories', ticket_comment_categories.View, basename='_api_ticket_comment_category')
|
||||
|
||||
router.register('software', software.SoftwareViewSet, basename='software')
|
||||
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path("assistance", assistance.index.Index.as_view(), name="_api_assistance"),
|
||||
|
||||
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
|
||||
|
||||
path("configuration/", config.ConfigGroupsList.as_view(), name='_api_config_groups'),
|
||||
@ -29,6 +75,8 @@ urlpatterns = [
|
||||
|
||||
path("device/inventory", inventory.Collect.as_view(), name="_api_device_inventory"),
|
||||
|
||||
path("itim", itim.index.Index.as_view(), name="_api_itim"),
|
||||
|
||||
path("organization/", access.OrganizationList.as_view(), name='_api_orgs'),
|
||||
path("organization/<int:pk>/", access.OrganizationDetail.as_view(), name='_api_organization'),
|
||||
path("organization/<int:organization_id>/team", access.TeamList.as_view(), name='_api_organization_teams'),
|
||||
@ -36,6 +84,11 @@ urlpatterns = [
|
||||
path("organization/<int:organization_id>/team/<int:group_ptr_id>/permissions", access.TeamPermissionDetail.as_view(), name='_api_team_permission'),
|
||||
path("organization/team/", access.TeamList.as_view(), name='_api_teams'),
|
||||
|
||||
path("project_management", project_management.index.Index.as_view(), name="_api_project_management"),
|
||||
|
||||
path("settings", settings.View.as_view(), name='_settings'),
|
||||
path("settings/permissions", permissions.View.as_view(), name='_settings_permissions'),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||
|
196
app/api/urls_v2.py
Normal file
196
app/api/urls_v2.py
Normal file
@ -0,0 +1,196 @@
|
||||
from django.urls import path
|
||||
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from api.viewsets import (
|
||||
index as v2
|
||||
)
|
||||
|
||||
from app.viewsets.base import (
|
||||
index as base_index_v2,
|
||||
content_type as content_type_v2,
|
||||
permisson as permission_v2,
|
||||
user as user_v2
|
||||
)
|
||||
|
||||
from access.viewsets import (
|
||||
index as access_v2,
|
||||
organization as organization_v2,
|
||||
team as team_v2,
|
||||
team_user as team_user_v2
|
||||
)
|
||||
|
||||
from assistance.viewsets import (
|
||||
index as assistance_index_v2,
|
||||
knowledge_base as knowledge_base_v2,
|
||||
knowledge_base_category as knowledge_base_category_v2,
|
||||
request as request_ticket_v2,
|
||||
)
|
||||
|
||||
from config_management.viewsets import (
|
||||
index as config_management_v2,
|
||||
config_group as config_group_v2,
|
||||
config_group_software as config_group_software_v2
|
||||
)
|
||||
|
||||
from core.viewsets import (
|
||||
celery_log as celery_log_v2,
|
||||
history as history_v2,
|
||||
manufacturer as manufacturer_v2,
|
||||
notes as notes_v2,
|
||||
ticket_category,
|
||||
ticket_comment,
|
||||
ticket_comment_category,
|
||||
ticket_linked_item,
|
||||
related_ticket,
|
||||
|
||||
)
|
||||
|
||||
from itam.viewsets import (
|
||||
index as itam_index_v2,
|
||||
device as device_v2,
|
||||
device_model as device_model_v2,
|
||||
device_type as device_type_v2,
|
||||
device_software as device_software_v2,
|
||||
device_operating_system,
|
||||
inventory,
|
||||
operating_system as operating_system_v2,
|
||||
operating_system_version as operating_system_version_v2,
|
||||
software as software_v2,
|
||||
software_category as software_category_v2,
|
||||
software_version as software_version_v2,
|
||||
)
|
||||
|
||||
from itim.viewsets import (
|
||||
index as itim_v2,
|
||||
change,
|
||||
cluster as cluster_v2,
|
||||
cluster_type as cluster_type_v2,
|
||||
incident,
|
||||
port as port_v2,
|
||||
problem,
|
||||
service as service_v2,
|
||||
service_device as service_device_v2
|
||||
)
|
||||
|
||||
from project_management.viewsets import (
|
||||
index as project_management_v2,
|
||||
project as project_v2,
|
||||
project_milestone as project_milestone_v2,
|
||||
project_state as project_state_v2,
|
||||
project_task,
|
||||
project_type as project_type_v2,
|
||||
)
|
||||
|
||||
from settings.viewsets import (
|
||||
app_settings as app_settings_v2,
|
||||
external_link as external_link_v2,
|
||||
index as settings_index_v2,
|
||||
user_settings as user_settings_v2
|
||||
)
|
||||
|
||||
app_name = "API"
|
||||
|
||||
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
|
||||
|
||||
router.register('', v2.Index, basename='_api_v2_home')
|
||||
|
||||
router.register('access', access_v2.Index, basename='_api_v2_access_home')
|
||||
router.register('access/organization', organization_v2.ViewSet, basename='_api_v2_organization')
|
||||
router.register('access/organization/(?P<organization_id>[0-9]+)/team', team_v2.ViewSet, basename='_api_v2_organization_team')
|
||||
router.register('access/organization/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user', team_user_v2.ViewSet, basename='_api_v2_organization_team_user')
|
||||
|
||||
|
||||
router.register('assistance', assistance_index_v2.Index, basename='_api_v2_assistance_home')
|
||||
router.register('assistance/knowledge_base', knowledge_base_v2.ViewSet, basename='_api_v2_knowledge_base')
|
||||
router.register('assistance/ticket/request', request_ticket_v2.ViewSet, basename='_api_v2_ticket_request')
|
||||
|
||||
|
||||
router.register('base', base_index_v2.Index, basename='_api_v2_base_home')
|
||||
router.register('base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
|
||||
router.register('base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
|
||||
router.register('base/user', user_v2.ViewSet, basename='_api_v2_user')
|
||||
|
||||
|
||||
router.register('config_management', config_management_v2.Index, basename='_api_v2_config_management_home')
|
||||
router.register('config_management/group', config_group_v2.ViewSet, basename='_api_v2_config_group')
|
||||
router.register('config_management/group/(?P<parent_group>[0-9]+)/child_group', config_group_v2.ViewSet, basename='_api_v2_config_group_child')
|
||||
router.register('config_management/group/(?P<config_group_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_config_group_notes')
|
||||
router.register('config_management/group/(?P<config_group_id>[0-9]+)/software', config_group_software_v2.ViewSet, basename='_api_v2_config_group_software')
|
||||
|
||||
|
||||
router.register('core/(?P<model_class>.+)/(?P<model_id>[0-9]+)/history', history_v2.ViewSet, basename='_api_v2_model_history')
|
||||
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments', ticket_comment.ViewSet, basename='_api_v2_ticket_comment')
|
||||
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads', ticket_comment.ViewSet, basename='_api_v2_ticket_comment_threads')
|
||||
router.register('core/ticket/(?P<ticket_id>[0-9]+)/linked_item', ticket_linked_item.ViewSet, basename='_api_v2_ticket_linked_item')
|
||||
router.register('core/ticket/(?P<ticket_id>[0-9]+)/related_ticket', related_ticket.ViewSet, basename='_api_v2_ticket_related')
|
||||
router.register('core/(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket', ticket_linked_item.ViewSet, basename='_api_v2_item_tickets')
|
||||
|
||||
|
||||
router.register('itam', itam_index_v2.Index, basename='_api_v2_itam_home')
|
||||
router.register('itam/device', device_v2.ViewSet, basename='_api_v2_device')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/operating_system', device_operating_system.ViewSet, basename='_api_v2_device_operating_system')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/software', device_software_v2.ViewSet, basename='_api_v2_device_software')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/service', service_device_v2.ViewSet, basename='_api_v2_service_device')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_device_notes')
|
||||
router.register('itam/inventory', inventory.ViewSet, basename='_api_v2_inventory')
|
||||
router.register('itam/operating_system', operating_system_v2.ViewSet, basename='_api_v2_operating_system')
|
||||
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/installs', device_operating_system.ViewSet, basename='_api_v2_operating_system_installs')
|
||||
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_operating_system_notes')
|
||||
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/version', operating_system_version_v2.ViewSet, basename='_api_v2_operating_system_version')
|
||||
router.register('itam/software', software_v2.ViewSet, basename='_api_v2_software')
|
||||
router.register('itam/software/(?P<software_id>[0-9]+)/installs', device_software_v2.ViewSet, basename='_api_v2_software_installs')
|
||||
router.register('itam/software/(?P<software_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_software_notes')
|
||||
router.register('itam/software/(?P<software_id>[0-9]+)/version', software_version_v2.ViewSet, basename='_api_v2_software_version')
|
||||
|
||||
|
||||
router.register('itim', itim_v2.Index, basename='_api_v2_itim_home')
|
||||
router.register('itim/ticket/change', change.ViewSet, basename='_api_v2_ticket_change')
|
||||
router.register('itim/cluster', cluster_v2.ViewSet, basename='_api_v2_cluster')
|
||||
router.register('itim/cluster/(?P<cluster_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_notes')
|
||||
router.register('itim/ticket/incident', incident.ViewSet, basename='_api_v2_ticket_incident')
|
||||
router.register('itim/ticket/problem', problem.ViewSet, basename='_api_v2_ticket_problem')
|
||||
router.register('itim/service', service_v2.ViewSet, basename='_api_v2_service')
|
||||
router.register('itim/service/(?P<service_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_service_notes')
|
||||
|
||||
|
||||
router.register('project_management', project_management_v2.Index, basename='_api_v2_project_management_home')
|
||||
router.register('project_management/project', project_v2.ViewSet, basename='_api_v2_project')
|
||||
router.register('project_management/project/(?P<project_id>[0-9]+)/milestone', project_milestone_v2.ViewSet, basename='_api_v2_project_milestone')
|
||||
router.register('project_management/project/(?P<project_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_project_notes')
|
||||
router.register('project_management/project/(?P<project_id>[0-9]+)/project_task', project_task.ViewSet, basename='_api_v2_ticket_project_task')
|
||||
|
||||
|
||||
router.register('settings', settings_index_v2.Index, basename='_api_v2_settings_home')
|
||||
router.register('settings/app_settings', app_settings_v2.ViewSet, basename='_api_v2_app_settings')
|
||||
router.register('settings/celery_log', celery_log_v2.ViewSet, basename='_api_v2_celery_log')
|
||||
router.register('settings/cluster_type', cluster_type_v2.ViewSet, basename='_api_v2_cluster_type')
|
||||
router.register('settings/cluster_type/(?P<cluster_type_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_type_notes')
|
||||
router.register('settings/device_model', device_model_v2.ViewSet, basename='_api_v2_device_model')
|
||||
router.register('settings/device_type', device_type_v2.ViewSet, basename='_api_v2_device_type')
|
||||
router.register('settings/external_link', external_link_v2.ViewSet, basename='_api_v2_external_link')
|
||||
router.register('settings/knowledge_base_category', knowledge_base_category_v2.ViewSet, basename='_api_v2_knowledge_base_category')
|
||||
router.register('settings/manufacturer', manufacturer_v2.ViewSet, basename='_api_v2_manufacturer')
|
||||
router.register('settings/manufacturer/(?P<manufacturer_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_manufacturer_notes')
|
||||
router.register('settings/port', port_v2.ViewSet, basename='_api_v2_port')
|
||||
router.register('settings/port/(?P<port_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_port_notes')
|
||||
router.register('settings/project_state', project_state_v2.ViewSet, basename='_api_v2_project_state')
|
||||
router.register('settings/project_type', project_type_v2.ViewSet, basename='_api_v2_project_type')
|
||||
router.register('settings/software_category', software_category_v2.ViewSet, basename='_api_v2_software_category')
|
||||
router.register('settings/ticket_category', ticket_category.ViewSet, basename='_api_v2_ticket_category')
|
||||
router.register('settings/ticket_comment_category', ticket_comment_category.ViewSet, basename='_api_v2_ticket_comment_category')
|
||||
router.register('settings/user_settings', user_settings_v2.ViewSet, basename='_api_v2_user_settings')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
|
||||
path('docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
47
app/api/v2/tests/unit/abstract/test_view_set_unit.py
Normal file
47
app/api/v2/tests/unit/abstract/test_view_set_unit.py
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
||||
class ViewSetAttributesUnit:
|
||||
""" Unit Tests For View Set attributes.
|
||||
|
||||
These tests ensure that View sets contian the required attributesthat are
|
||||
used by the API .
|
||||
"""
|
||||
|
||||
|
||||
def test_attribute_exists_page_layout(self):
|
||||
"""Attrribute Test, Exists
|
||||
|
||||
Ensure attribute `page_layout` exists
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_attribute_type_page_layout(self):
|
||||
"""Attrribute Test, Type
|
||||
|
||||
Ensure attribute `page_layout` is of type `list`
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_attribute_not_callable_page_layout(self):
|
||||
"""Attrribute Test, Not Callable
|
||||
|
||||
Attribute must be a property
|
||||
|
||||
Ensure attribute `page_layout` is not callable.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
# other tests required
|
||||
# - filterset_fields
|
||||
# - metadata_class
|
||||
# - search_fields
|
||||
# - documentation
|
||||
# - model_documentation or is in `model.documentation`
|
@ -12,7 +12,7 @@ from access.models import Organization, Team
|
||||
from api.serializers.access import OrganizationSerializer, OrganizationListSerializer, TeamSerializer, TeamPermissionSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch Organizations",
|
||||
@ -34,7 +34,7 @@ class OrganizationList(generics.ListAPIView):
|
||||
return "Organizations"
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Get An Organization",
|
||||
@ -61,7 +61,7 @@ class OrganizationDetail(generics.RetrieveUpdateAPIView):
|
||||
return "Organization"
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
@extend_schema_view(
|
||||
post=extend_schema(
|
||||
summary = "Create a Team",
|
||||
@ -97,7 +97,7 @@ class TeamList(generics.ListCreateAPIView):
|
||||
return "Organization Teams"
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch a Team",
|
||||
@ -149,7 +149,7 @@ class TeamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
lookup_field = 'group_ptr_id'
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch a teams permissions",
|
||||
|
1
app/api/views/assistance/__init__.py
Normal file
1
app/api/views/assistance/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .index import *
|
37
app/api/views/assistance/index.py
Normal file
37
app/api/views/assistance/index.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from rest_framework import generics, permissions, routers, views
|
||||
# from rest_framework.decorators import api_view
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class Index(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "Assistance"
|
||||
|
||||
def get_view_description(self, html=False) -> str:
|
||||
text = "Assistance Module"
|
||||
if html:
|
||||
return mark_safe(f"<p>{text}</p>")
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
body: dict = {
|
||||
'requests': reverse('v1:_api_assistance_request-list', request=request)
|
||||
}
|
||||
|
||||
return Response(body)
|
78
app/api/views/assistance/request_ticket.py
Normal file
78
app/api/views/assistance/request_ticket.py
Normal file
@ -0,0 +1,78 @@
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from api.serializers.assistance.request import RequestTicketSerializer
|
||||
from api.views.core.tickets import View
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(View):
|
||||
|
||||
_ticket_type:str = 'request'
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Create a ticket',
|
||||
description = """This model includes all of the ticket types.
|
||||
Due to this not all fields will be available and what fields are available
|
||||
depends upon the comment type. see
|
||||
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
|
||||
""",
|
||||
request = RequestTicketSerializer,
|
||||
responses = {
|
||||
201: OpenApiResponse(
|
||||
response = RequestTicketSerializer,
|
||||
),
|
||||
}
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch all tickets',
|
||||
description = """This model includes all of the ticket comment types.
|
||||
Due to this not all fields will be available and what fields are available
|
||||
depends upon the comment type. see
|
||||
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
|
||||
""",
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='Success',
|
||||
response = RequestTicketSerializer
|
||||
)
|
||||
}
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch the selected ticket',
|
||||
description = """This model includes all of the ticket comment types.
|
||||
Due to this not all fields will be available and what fields are available
|
||||
depends upon the comment type. see
|
||||
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
|
||||
""",
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='Success',
|
||||
response = RequestTicketSerializer
|
||||
)
|
||||
}
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
|
||||
if self.detail:
|
||||
return "Request Ticket"
|
||||
|
||||
return 'Request Tickets'
|
@ -8,7 +8,7 @@ from api.views.mixin import OrganizationPermissionAPI
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch Config groups",
|
||||
@ -31,6 +31,7 @@ class ConfigGroupsList(generics.ListAPIView):
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Get A Config Group",
|
||||
|
79
app/api/views/core/ticket_categories.py
Normal file
79
app/api/views/core/ticket_categories.py
Normal file
@ -0,0 +1,79 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.serializers.core.ticket_category import TicketCategory, TicketCategorySerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = TicketCategory.objects.all()
|
||||
|
||||
serializer_class = TicketCategorySerializer
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Create a ticket category',
|
||||
request = TicketCategorySerializer,
|
||||
responses = {
|
||||
201: OpenApiResponse(description='Ticket category created', response=TicketCategorySerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch all of a tickets category',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
|
||||
}
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch the selected ticket category',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
|
||||
}
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Update a ticket category',
|
||||
methods=["PUT"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Ticket comment updated', response=TicketCategorySerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def update(self, request, *args, **kwargs):
|
||||
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
if self.detail:
|
||||
return "Ticket Category"
|
||||
|
||||
return 'Ticket Categories'
|
79
app/api/views/core/ticket_comment_categories.py
Normal file
79
app/api/views/core/ticket_comment_categories.py
Normal file
@ -0,0 +1,79 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.serializers.core.ticket_comment_category import TicketCommentCategory, TicketCommentCategorySerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = TicketCommentCategory.objects.all()
|
||||
|
||||
serializer_class = TicketCommentCategorySerializer
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Create a ticket comment category',
|
||||
request = TicketCommentCategorySerializer,
|
||||
responses = {
|
||||
201: OpenApiResponse(description='Ticket category created', response=TicketCommentCategorySerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch all of the ticket comment categories',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
|
||||
}
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch the selected ticket comment category',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
|
||||
}
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Update a ticket comment category',
|
||||
methods=["PUT"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentCategorySerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def update(self, request, *args, **kwargs):
|
||||
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
if self.detail:
|
||||
return "Ticket Comment Category"
|
||||
|
||||
return 'Ticket Comment Categories'
|
102
app/api/views/core/ticket_comments.py
Normal file
102
app/api/views/core/ticket_comments.py
Normal file
@ -0,0 +1,102 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.serializers.core.ticket_comment import TicketCommentSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from core.models.ticket.ticket_comment import TicketComment
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = TicketComment.objects.all()
|
||||
|
||||
serializer_class = TicketCommentSerializer
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Create a ticket comment',
|
||||
description = """This model includes all of the ticket comment types.
|
||||
Due to this not all fields will be available and what fields are available
|
||||
depends upon the comment type.
|
||||
""",
|
||||
request = TicketCommentSerializer,
|
||||
responses = {
|
||||
201: OpenApiResponse(description='Ticket comment created', response=TicketCommentSerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch all of a tickets comments',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
|
||||
}
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Fetch the selected ticket Comment',
|
||||
methods=["GET"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
|
||||
}
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Update a ticket Comment',
|
||||
description = """This model includes all of the ticket comment types.
|
||||
Due to this not all fields will be available and what fields are available
|
||||
depends upon the comment type.
|
||||
""",
|
||||
methods=["PUT"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentSerializer),
|
||||
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
|
||||
}
|
||||
)
|
||||
def update(self, request, *args, **kwargs):
|
||||
|
||||
return super().update(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if 'ticket_id' in self.kwargs:
|
||||
|
||||
self.queryset = self.queryset.filter(ticket=self.kwargs['ticket_id']).order_by('created')
|
||||
|
||||
if 'pk' in self.kwargs:
|
||||
|
||||
self.queryset = self.queryset.filter(pk = self.kwargs['pk'])
|
||||
|
||||
return self.queryset
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
if self.detail:
|
||||
return "Ticket Comment"
|
||||
|
||||
return 'Ticket Comments'
|
164
app/api/views/core/tickets.py
Normal file
164
app/api/views/core/tickets.py
Normal file
@ -0,0 +1,164 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.serializers.assistance.request import RequestTicketSerializer
|
||||
from api.serializers.itim.change import ChangeTicketSerializer
|
||||
from api.serializers.itim.incident import IncidentTicketSerializer
|
||||
from api.serializers.itim.problem import ProblemTicketSerializer
|
||||
from api.serializers.project_management.project_task import ProjectTaskSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
filterset_fields = [
|
||||
'external_system',
|
||||
'external_ref',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'title',
|
||||
'description',
|
||||
]
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
def get_dynamic_permissions(self):
|
||||
|
||||
if self.action == 'create':
|
||||
|
||||
action_keyword = 'add'
|
||||
|
||||
elif self.action == 'destroy':
|
||||
|
||||
action_keyword = 'delete'
|
||||
|
||||
elif self.action == 'list':
|
||||
|
||||
action_keyword = 'view'
|
||||
|
||||
elif self.action == 'partial_update':
|
||||
|
||||
action_keyword = 'change'
|
||||
|
||||
elif self.action == 'retrieve':
|
||||
|
||||
action_keyword = 'view'
|
||||
|
||||
elif self.action == 'update':
|
||||
|
||||
action_keyword = 'change'
|
||||
|
||||
elif self.action is None:
|
||||
|
||||
action_keyword = 'view'
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError('unable to determin the action_keyword')
|
||||
|
||||
self.permission_required = [
|
||||
'core.' + action_keyword + '_ticket_' + self._ticket_type,
|
||||
]
|
||||
|
||||
return super().get_permission_required()
|
||||
|
||||
|
||||
# queryset = Ticket.objects.all()
|
||||
queryset = None
|
||||
|
||||
model = Ticket
|
||||
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
if self._ticket_type == 'change':
|
||||
|
||||
self.serializer_class = ChangeTicketSerializer
|
||||
|
||||
self._ticket_type_value = Ticket.TicketType.CHANGE.value
|
||||
|
||||
elif self._ticket_type == 'incident':
|
||||
|
||||
self.serializer_class = IncidentTicketSerializer
|
||||
self._ticket_type_value = Ticket.TicketType.INCIDENT.value
|
||||
|
||||
elif self._ticket_type == 'problem':
|
||||
|
||||
self.serializer_class = ProblemTicketSerializer
|
||||
self._ticket_type_value = Ticket.TicketType.PROBLEM.value
|
||||
|
||||
elif self._ticket_type == 'request':
|
||||
|
||||
self.serializer_class = RequestTicketSerializer
|
||||
self._ticket_type_value = Ticket.TicketType.REQUEST.value
|
||||
|
||||
elif self._ticket_type == 'project_task':
|
||||
|
||||
self.serializer_class = ProjectTaskSerializer
|
||||
self._ticket_type_value = Ticket.TicketType.PROJECT_TASK.value
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError('unable to determin the serializer_class')
|
||||
|
||||
return super().get_serializer(*args, **kwargs)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self._ticket_type == 'change':
|
||||
|
||||
ticket_type = self.model.TicketType.CHANGE.value
|
||||
|
||||
elif self._ticket_type == 'incident':
|
||||
|
||||
ticket_type = self.model.TicketType.INCIDENT.value
|
||||
|
||||
elif self._ticket_type == 'problem':
|
||||
|
||||
ticket_type = self.model.TicketType.PROBLEM.value
|
||||
|
||||
elif self._ticket_type == 'request':
|
||||
|
||||
ticket_type = self.model.TicketType.REQUEST.value
|
||||
|
||||
elif self._ticket_type == 'project_task':
|
||||
|
||||
ticket_type = self.model.TicketType.REQUEST.value
|
||||
|
||||
# return self.queryset.filter(
|
||||
# project = self.kwargs['project_id']
|
||||
# )
|
||||
|
||||
else:
|
||||
|
||||
raise ValueError('Unknown ticket type. kwarg `ticket_type` must be set')
|
||||
|
||||
|
||||
if not self.queryset:
|
||||
|
||||
queryset = Ticket.objects.all()
|
||||
|
||||
queryset = queryset.filter(
|
||||
ticket_type = ticket_type
|
||||
)
|
||||
|
||||
if self._ticket_type == 'project_task':
|
||||
|
||||
queryset = queryset.filter(
|
||||
project = self.kwargs['project_id']
|
||||
)
|
||||
|
||||
self.queryset = queryset
|
||||
|
||||
|
||||
return self.queryset
|
@ -1,21 +1,27 @@
|
||||
# from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from rest_framework import generics, permissions, routers, viewsets
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
|
||||
class Index(viewsets.ViewSet):
|
||||
|
||||
# permission_required = 'access.view_organization'
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "API Index"
|
||||
return "API"
|
||||
|
||||
def get_view_description(self, html=False) -> str:
|
||||
text = "My REST API"
|
||||
text = "Centurion ERP Rest API"
|
||||
if html:
|
||||
return mark_safe(f"<p>{text}</p>")
|
||||
else:
|
||||
@ -23,12 +29,18 @@ class Index(viewsets.ViewSet):
|
||||
|
||||
|
||||
def list(self, request, pk=None):
|
||||
return Response(
|
||||
{
|
||||
|
||||
API: dict = {
|
||||
# "teams": reverse("_api_teams", request=request),
|
||||
"devices": reverse("API:device-list", request=request),
|
||||
"config_groups": reverse("API:_api_config_groups", request=request),
|
||||
"organizations": reverse("API:_api_orgs", request=request),
|
||||
"software": reverse("API:software-list", request=request),
|
||||
'assistance': reverse("v1:_api_assistance", request=request),
|
||||
"devices": reverse("v1:device-list", request=request),
|
||||
"config_groups": reverse("v1:_api_config_groups", request=request),
|
||||
'itim': reverse("v1:_api_itim", request=request),
|
||||
"organizations": reverse("v1:_api_orgs", request=request),
|
||||
'project_management': reverse("v1:_api_project_management", request=request),
|
||||
"settings": reverse('v1:_settings', request=request),
|
||||
"software": reverse("v1:software-list", request=request),
|
||||
'v2': reverse("v2:_api_v2_home-list", request=request)
|
||||
}
|
||||
)
|
||||
|
||||
return Response( API )
|
||||
|
@ -1,11 +1,14 @@
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
from rest_framework import views
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class View(views.APIView):
|
||||
|
||||
def get(self, request, slug):
|
||||
|
@ -1,9 +1,10 @@
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
from rest_framework.response import Response
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
@ -13,7 +14,7 @@ from api.views.mixin import OrganizationPermissionAPI
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
@ -24,6 +25,46 @@ class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
serializer_class = DeviceSerializer
|
||||
|
||||
@extend_schema(
|
||||
summary = 'Create a device',
|
||||
description="""Add a new device to the ITAM database.
|
||||
If you attempt to create a device and a device with a matching name and uuid or name and serial number
|
||||
is found within the database, it will not re-create it. The device will be returned within the message body.
|
||||
""",
|
||||
methods=["POST"],
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Device allready exists', response=DeviceSerializer),
|
||||
201: OpenApiResponse(description='Device created', response=DeviceSerializer),
|
||||
400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing create permissions'),
|
||||
}
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
current_device = []
|
||||
|
||||
if 'uuid' in self.request.POST:
|
||||
|
||||
current_device = self.serializer_class.Meta.model.objects.filter(
|
||||
organization = int(self.request.POST['organization']),
|
||||
uuid = str(self.request.POST['uuid'])
|
||||
)
|
||||
|
||||
if 'serial_number' in self.request.POST and len(current_device) == 0:
|
||||
|
||||
current_device = self.serializer_class.Meta.model.objects.filter(
|
||||
organization = int(self.request.POST['organization']),
|
||||
serial_number = str(self.request.POST['serial_number'])
|
||||
)
|
||||
|
||||
if len(current_device) == 1:
|
||||
|
||||
instance = current_device.get()
|
||||
serializer = self.get_serializer(instance)
|
||||
return Response(serializer.data)
|
||||
|
||||
return super().create(request, *args, **kwargs)
|
||||
|
||||
|
||||
@extend_schema( description='Fetch devices that are from the users assigned organization(s)', methods=["GET"])
|
||||
def list(self, request):
|
||||
|
@ -1,11 +1,10 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError, PermissionDenied
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, views
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
@ -34,6 +33,7 @@ class InventoryPermissions(OrganizationPermissionAPI):
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class Collect(OrganizationPermissionAPI, views.APIView):
|
||||
|
||||
queryset = Device.objects.all()
|
||||
@ -91,12 +91,13 @@ this setting populated, no device will be created and the endpoint will return H
|
||||
|
||||
if not self.permission_check(request=request, view=self, obj=device):
|
||||
|
||||
raise Http404
|
||||
raise PermissionDenied()
|
||||
|
||||
task = process_inventory.delay(request.body, self.default_organization.id)
|
||||
|
||||
response_data: dict = {"task_id": f"{task.id}"}
|
||||
|
||||
|
||||
except PermissionDenied as e:
|
||||
|
||||
status = Http.Status.FORBIDDEN
|
||||
@ -105,7 +106,7 @@ this setting populated, no device will be created and the endpoint will return H
|
||||
except ValidationError as e:
|
||||
|
||||
status = Http.Status.BAD_REQUEST
|
||||
response_data = e.message
|
||||
response_data = e.detail
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
@ -11,7 +13,7 @@ from api.views.mixin import OrganizationPermissionAPI
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True)
|
||||
class SoftwareViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user