aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Pipfile1
-rw-r--r--Pipfile.lock235
-rw-r--r--pydis_site/apps/api/serializers.py48
-rw-r--r--pydis_site/apps/api/tests/test_users.py116
-rw-r--r--pydis_site/apps/api/viewsets/bot/user.py108
5 files changed, 378 insertions, 130 deletions
diff --git a/Pipfile b/Pipfile
index d23d28e6..27a2a452 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,7 +9,6 @@ django-environ = "~=0.4.5"
django-filter = "~=2.1.0"
django-hosts = "~=4.0"
djangorestframework = "~=3.11.0"
-djangorestframework-bulk = "~=0.2.1"
psycopg2-binary = "~=2.8"
django-simple-bulma = "~=1.2"
whitenoise = "~=5.0"
diff --git a/Pipfile.lock b/Pipfile.lock
index b8c85d33..65b9c154 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "ad586b840e82b4ae87eed1af70adde0b2c8b7f862a832c4cfa87748b97add3bd"
+ "sha256": "4ecc64deaa82df654479986c9c9569721a66296725e51272608a8294ac562af2"
},
"pipfile-spec": 6,
"requires": {
@@ -56,11 +56,11 @@
},
"django": {
"hashes": [
- "sha256:96fbe04e8ba0df289171e7f6970e0ff8b472bf4f909ed9e0e5beccbac7e1dbbe",
- "sha256:c22b4cd8e388f8219dc121f091e53a8701f9f5bca9aa132b5254263cab516215"
+ "sha256:2d14be521c3ae24960e5e83d4575e156a8c479a75c935224b671b1c6e66eddaf",
+ "sha256:313d0b8f96685e99327785cc600a5178ca855f8e6f4ed162e671e8c3cf749739"
],
"index": "pypi",
- "version": "==3.0.9"
+ "version": "==3.0.10"
},
"django-allauth": {
"hashes": [
@@ -71,9 +71,10 @@
},
"django-classy-tags": {
"hashes": [
- "sha256:ad6a25fc2b58a098f00d86bd5e5dad47922f5ca4e744bc3cccb7b4be5bc35eb1"
+ "sha256:25eb4f95afee396148683bfb4811b83b3f5729218d73ad0a3399271a6f9fcc49",
+ "sha256:d59d98bdf96a764dcf7a2929a86439d023b283a9152492811c7e44fc47555bc9"
],
- "version": "==1.0.0"
+ "version": "==2.0.0"
},
"django-environ": {
"hashes": [
@@ -123,9 +124,10 @@
},
"django-sekizai": {
"hashes": [
- "sha256:e2f6e666d4dd9d3ecc27284acb85ef709e198014f5d5af8c6d54ed04c2d684d9"
+ "sha256:5c5e16845d37ce822fc655ce79ec02715191b3d03330b550997bcb842cf24fdf",
+ "sha256:e829f09b0d6bf01ee5cde05de1fb3faf2fbc5df66dc4dc280fbaac224ca4336f"
],
- "version": "==1.1.0"
+ "version": "==2.0.0"
},
"django-simple-bulma": {
"hashes": [
@@ -143,13 +145,6 @@
"index": "pypi",
"version": "==3.11.1"
},
- "djangorestframework-bulk": {
- "hashes": [
- "sha256:39230d8379acebd86d313df6c9150cafecb636eae1d097c30a26389ab9fee5b1"
- ],
- "index": "pypi",
- "version": "==0.2.1"
- },
"gitdb": {
"hashes": [
"sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
@@ -160,11 +155,11 @@
},
"gitpython": {
"hashes": [
- "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858",
- "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"
+ "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912",
+ "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910"
],
"index": "pypi",
- "version": "==3.1.7"
+ "version": "==3.1.8"
},
"idna": {
"hashes": [
@@ -184,21 +179,21 @@
},
"libsass": {
"hashes": [
- "sha256:107c409524c6a4ed14410fa9dafa9ee59c6bd3ecae75d73af749ab2b75685726",
- "sha256:3bc0d68778b30b5fa83199e18795314f64b26ca5871e026343e63934f616f7f7",
- "sha256:5c8ff562b233734fbc72b23bb862cc6a6f70b1e9bf85a58422aa75108b94783b",
- "sha256:74f6fb8da58179b5d86586bc045c16d93d55074bc7bb48b6354a4da7ac9f9dfd",
- "sha256:7555d9b24e79943cfafac44dbb4ca7e62105c038de7c6b999838c9ff7b88645d",
- "sha256:794f4f4661667263e7feafe5cc866e3746c7c8a9192b2aa9afffdadcbc91c687",
- "sha256:8cf72552b39e78a1852132e16b706406bc76029fe3001583284ece8d8752a60a",
- "sha256:98f6dee9850b29e62977a963e3beb3cfeb98b128a267d59d2c3d675e298c8d57",
- "sha256:a43f3830d83ad9a7f5013c05ce239ca71744d0780dad906587302ac5257bce60",
- "sha256:b077261a04ba1c213e932943208471972c5230222acb7fa97373e55a40872cbb",
- "sha256:b7452f1df274b166dc22ee2e9154c4adca619bcbbdf8041a7aa05f372a1dacbc",
- "sha256:e6a547c0aa731dcb4ed71f198e814bee0400ce04d553f3f12a53bc3a17f2a481",
- "sha256:fd19c8f73f70ffc6cbcca8139da08ea9a71fc48e7dfc4bb236ad88ab2d6558f1"
- ],
- "version": "==0.20.0"
+ "sha256:1521d2a8d4b397c6ec90640a1f6b5529077035efc48ef1c2e53095544e713d1b",
+ "sha256:1b2d415bbf6fa7da33ef46e549db1418498267b459978eff8357e5e823962d35",
+ "sha256:25ebc2085f5eee574761ccc8d9cd29a9b436fc970546d5ef08c6fa41eb57dff1",
+ "sha256:2ae806427b28bc1bb7cb0258666d854fcf92ba52a04656b0b17ba5e190fb48a9",
+ "sha256:4a246e4b88fd279abef8b669206228c92534d96ddcd0770d7012088c408dff23",
+ "sha256:553e5096414a8d4fb48d0a48f5a038d3411abe254d79deac5e008516c019e63a",
+ "sha256:697f0f9fa8a1367ca9ec6869437cb235b1c537fc8519983d1d890178614a8903",
+ "sha256:a8fd4af9f853e8bf42b1425c5e48dd90b504fa2e70d7dac5ac80b8c0a5a5fe85",
+ "sha256:c9411fec76f480ffbacc97d8188322e02a5abca6fc78e70b86a2a2b421eae8a2",
+ "sha256:daa98a51086d92aa7e9c8871cf1a8258124b90e2abf4697852a3dca619838618",
+ "sha256:e0e60836eccbf2d9e24ec978a805cd6642fa92515fbd95e3493fee276af76f8a",
+ "sha256:e64ae2587f1a683e831409aad03ba547c245ef997e1329fffadf7a866d2510b8",
+ "sha256:f6852828e9e104d2ce0358b73c550d26dd86cc3a69439438c3b618811b9584f5"
+ ],
+ "version": "==0.20.1"
},
"markdown": {
"hashes": [
@@ -226,73 +221,75 @@
},
"pillow": {
"hashes": [
- "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f",
- "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d",
+ "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f",
+ "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8",
+ "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad",
+ "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f",
"sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae",
+ "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d",
+ "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5",
"sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b",
+ "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8",
+ "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233",
"sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6",
- "sha256:9c87ef410a58dd54b92424ffd7e28fd2ec65d2f7fc02b76f5e9b2067e355ebf6",
+ "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727",
+ "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f",
+ "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38",
"sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4",
- "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9",
- "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233",
+ "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626",
+ "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d",
+ "sha256:9c87ef410a58dd54b92424ffd7e28fd2ec65d2f7fc02b76f5e9b2067e355ebf6",
"sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6",
- "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce",
- "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38",
"sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63",
- "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d",
- "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f",
- "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8",
- "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d",
- "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad",
- "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f",
- "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626",
- "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a",
- "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727",
- "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8",
- "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f",
- "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5",
+ "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f",
"sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41",
"sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1",
- "sha256:e901964262a56d9ea3c2693df68bc9860b8bdda2b04768821e4c44ae797de117"
+ "sha256:e901964262a56d9ea3c2693df68bc9860b8bdda2b04768821e4c44ae797de117",
+ "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d",
+ "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9",
+ "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a",
+ "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"
],
"markers": "python_version >= '3.5'",
"version": "==7.2.0"
},
"psycopg2-binary": {
"hashes": [
- "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac",
- "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a",
- "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5",
- "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04",
- "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1",
- "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5",
- "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce",
- "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434",
- "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9",
- "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057",
- "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98",
- "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522",
- "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505",
- "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa",
- "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3",
- "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f",
- "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4",
- "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4",
- "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266",
- "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66",
- "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38",
- "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3",
- "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389",
- "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab",
- "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb",
- "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6",
- "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d",
- "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162",
- "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e",
- "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"
- ],
- "index": "pypi",
- "version": "==2.8.5"
+ "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
+ "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
+ "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
+ "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
+ "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
+ "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
+ "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
+ "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
+ "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
+ "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
+ "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
+ "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
+ "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
+ "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2",
+ "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd",
+ "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859",
+ "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1",
+ "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25",
+ "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152",
+ "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf",
+ "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f",
+ "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729",
+ "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71",
+ "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66",
+ "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4",
+ "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449",
+ "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da",
+ "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a",
+ "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c",
+ "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb",
+ "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4",
+ "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"
+ ],
+ "index": "pypi",
+ "version": "==2.8.6"
},
"pygments": {
"hashes": [
@@ -307,7 +304,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==2.4.7"
},
"python3-openid": {
@@ -378,18 +375,18 @@
},
"sentry-sdk": {
"hashes": [
- "sha256:d359609e23ec9360b61e5ffdfa417e2f6bca281bfb869608c98c169c7e64acd5",
- "sha256:e12eb1c2c01cd9e9cfe70608dbda4ef451f37ef0b7cbb92e5d43f87c341d6334"
+ "sha256:0af429c221670e602f960fca85ca3f607c85510a91f11e8be8f742a978127f78",
+ "sha256:a088a1054673c6a19ea590045c871c38da029ef743b61a07bfee95e9f3c060f7"
],
"index": "pypi",
- "version": "==0.16.5"
+ "version": "==0.17.3"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.15.0"
},
"smmap": {
@@ -466,11 +463,11 @@
},
"attrs": {
"hashes": [
- "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a",
- "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"
+ "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
+ "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==20.1.0"
+ "version": "==20.2.0"
},
"bandit": {
"hashes": [
@@ -551,11 +548,11 @@
},
"flake8-annotations": {
"hashes": [
- "sha256:7816a5d8f65ffdf37b8e21e5b17e0fd1e492aa92638573276de066e889a22b26",
- "sha256:8d18db74a750dd97f40b483cc3ef80d07d03f687525bad8fd83365dcd3bfd414"
+ "sha256:09fe1aa3f40cb8fef632a0ab3614050a7584bb884b6134e70cf1fc9eeee642fa",
+ "sha256:5bda552f074fd6e34276c7761756fa07d824ffac91ce9c0a8555eb2bc5b92d7a"
],
"index": "pypi",
- "version": "==2.3.0"
+ "version": "==2.4.0"
},
"flake8-bandit": {
"hashes": [
@@ -628,19 +625,19 @@
},
"gitpython": {
"hashes": [
- "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858",
- "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"
+ "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912",
+ "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910"
],
"index": "pypi",
- "version": "==3.1.7"
+ "version": "==3.1.8"
},
"identify": {
"hashes": [
- "sha256:69c4769f085badafd0e04b1763e847258cbbf6d898e8678ebffc91abdb86f6c6",
- "sha256:d6ae6daee50ba1b493e9ca4d36a5edd55905d2cf43548fdc20b2a14edef102e7"
+ "sha256:009f92ba753c467a99f6fd3eb395412cbc34077dd5a64313b62ba04297f2ab8e",
+ "sha256:0868312cb7402b48cf44fe3f568259f804ef4e983c143d11bf7a51ca311ebc34"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==1.4.28"
+ "version": "==1.5.0"
},
"importlib-metadata": {
"hashes": [
@@ -660,16 +657,18 @@
},
"nodeenv": {
"hashes": [
- "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"
+ "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9",
+ "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"
],
- "version": "==1.4.0"
+ "version": "==1.5.0"
},
"pbr": {
"hashes": [
- "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
- "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
+ "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea",
+ "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"
],
- "version": "==5.4.5"
+ "markers": "python_version >= '2.6'",
+ "version": "==5.5.0"
},
"pep8-naming": {
"hashes": [
@@ -681,11 +680,11 @@
},
"pre-commit": {
"hashes": [
- "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915",
- "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626"
+ "sha256:810aef2a2ba4f31eed1941fc270e72696a1ad5590b9751839c90807d0fff6b9a",
+ "sha256:c54fd3e574565fe128ecc5e7d2f91279772ddb03f8729645fa812fe809084a70"
],
"index": "pypi",
- "version": "==2.6.0"
+ "version": "==2.7.1"
},
"pycodestyle": {
"hashes": [
@@ -697,11 +696,11 @@
},
"pydocstyle": {
"hashes": [
- "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
- "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
+ "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325",
+ "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"
],
"markers": "python_version >= '3.5'",
- "version": "==5.0.2"
+ "version": "==5.1.1"
},
"pyflakes": {
"hashes": [
@@ -733,7 +732,7 @@
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.15.0"
},
"smmap": {
@@ -753,11 +752,11 @@
},
"stevedore": {
"hashes": [
- "sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5",
- "sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633"
+ "sha256:a34086819e2c7a7f86d5635363632829dab8014e5fd7be2454c7cba84ac7514e",
+ "sha256:ddc09a744dc224c84ec8e8efcb70595042d21c97c76df60daee64c9ad53bc7ee"
],
"markers": "python_version >= '3.6'",
- "version": "==3.2.0"
+ "version": "==3.2.1"
},
"toml": {
"hashes": [
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 90bd6f91..ae57b307 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -1,7 +1,13 @@
"""Converters from Django models to data interchange formats and back."""
-from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField, ValidationError
+from django.db.models.query import QuerySet
+from rest_framework.serializers import (
+ IntegerField,
+ ListSerializer,
+ ModelSerializer,
+ PrimaryKeyRelatedField,
+ ValidationError
+)
from rest_framework.validators import UniqueTogetherValidator
-from rest_framework_bulk import BulkSerializerMixin
from .models import (
BotSetting,
@@ -249,15 +255,51 @@ class RoleSerializer(ModelSerializer):
fields = ('id', 'name', 'colour', 'permissions', 'position')
-class UserSerializer(BulkSerializerMixin, ModelSerializer):
+class UserListSerializer(ListSerializer):
+ """List serializer for User model to handle bulk updates."""
+
+ def create(self, validated_data: list) -> list:
+ """Override create method to optimize django queries."""
+ present_users = User.objects.all()
+ new_users = []
+ present_user_ids = [user.id for user in present_users]
+
+ for user_dict in validated_data:
+ if user_dict["id"] in present_user_ids:
+ raise ValidationError({"id": "User already exists."})
+ new_users.append(User(**user_dict))
+
+ return User.objects.bulk_create(new_users)
+
+ def update(self, instance: QuerySet, validated_data: list) -> list:
+ """
+ Override update method to support bulk updates.
+
+ ref:https://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-update
+ """
+ instance_mapping = {user.id: user for user in instance}
+ data_mapping = {item['id']: item for item in validated_data}
+
+ updated = []
+ for user_id, data in data_mapping.items():
+ user = instance_mapping.get(user_id)
+ updated.append(self.child.update(user, data))
+
+ return updated
+
+
+class UserSerializer(ModelSerializer):
"""A class providing (de-)serialization of `User` instances."""
+ id = IntegerField(min_value=0)
+
class Meta:
"""Metadata defined for the Django REST Framework."""
model = User
fields = ('id', 'name', 'discriminator', 'roles', 'in_guild')
depth = 1
+ list_serializer_class = UserListSerializer
class NominationSerializer(ModelSerializer):
diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py
index 4c0f6e27..a54e1189 100644
--- a/pydis_site/apps/api/tests/test_users.py
+++ b/pydis_site/apps/api/tests/test_users.py
@@ -45,6 +45,13 @@ class CreationTests(APISubdomainTestCase):
position=1
)
+ cls.user = User.objects.create(
+ id=11,
+ name="Name doesn't matter.",
+ discriminator=1122,
+ in_guild=True
+ )
+
def test_accepts_valid_data(self):
url = reverse('bot:user-list', host='api')
data = {
@@ -115,6 +122,115 @@ class CreationTests(APISubdomainTestCase):
response = self.client.post(url, data=data)
self.assertEqual(response.status_code, 400)
+ def test_returns_400_for_user_recreation(self):
+ """Return 400 if User is already present in database."""
+ url = reverse('bot:user-list', host='api')
+ data = [{
+ 'id': 11,
+ 'name': 'You saw nothing.',
+ 'discriminator': 112,
+ 'in_guild': True
+ }]
+ response = self.client.post(url, data=data)
+ self.assertEqual(response.status_code, 400)
+
+
+class MultiPatchTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.role_developer = Role.objects.create(
+ id=159,
+ name="Developer",
+ colour=2,
+ permissions=0b01010010101,
+ position=10,
+ )
+ cls.user_1 = User.objects.create(
+ id=1,
+ name="Patch test user 1.",
+ discriminator=1111,
+ in_guild=True
+ )
+ cls.user_2 = User.objects.create(
+ id=2,
+ name="Patch test user 2.",
+ discriminator=2222,
+ in_guild=True
+ )
+
+ def test_multiple_users_patch(self):
+ url = reverse("bot:user-bulk-patch", host="api")
+ data = [
+ {
+ "id": 1,
+ "name": "User 1 patched!",
+ "discriminator": 1010,
+ "roles": [self.role_developer.id],
+ "in_guild": False
+ },
+ {
+ "id": 2,
+ "name": "User 2 patched!"
+ }
+ ]
+
+ response = self.client.patch(url, data=data)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()[0], data[0])
+
+ user_2 = User.objects.get(id=2)
+ self.assertEqual(user_2.name, data[1]["name"])
+
+ def test_returns_400_for_missing_user_id(self):
+ url = reverse("bot:user-bulk-patch", host="api")
+ data = [
+ {
+ "name": "I am ghost user!",
+ "discriminator": 1010,
+ "roles": [self.role_developer.id],
+ "in_guild": False
+ },
+ {
+ "name": "patch me? whats my id?"
+ }
+ ]
+ response = self.client.patch(url, data=data)
+ self.assertEqual(response.status_code, 400)
+
+ def test_returns_404_for_not_found_user(self):
+ url = reverse("bot:user-bulk-patch", host="api")
+ data = [
+ {
+ "id": 1,
+ "name": "User 1 patched again!!!",
+ "discriminator": 1010,
+ "roles": [self.role_developer.id],
+ "in_guild": False
+ },
+ {
+ "id": 22503405,
+ "name": "User unknown not patched!"
+ }
+ ]
+ response = self.client.patch(url, data=data)
+ self.assertEqual(response.status_code, 404)
+
+ def test_returns_400_for_bad_data(self):
+ url = reverse("bot:user-bulk-patch", host="api")
+ data = [
+ {
+ "id": 1,
+ "in_guild": "Catch me!"
+ },
+ {
+ "id": 2,
+ "discriminator": "find me!"
+ }
+ ]
+
+ response = self.client.patch(url, data=data)
+ self.assertEqual(response.status_code, 400)
+
class UserModelTests(APISubdomainTestCase):
@classmethod
diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
index 9571b3d7..d015fe71 100644
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -1,21 +1,37 @@
+from rest_framework import status
+from rest_framework.decorators import action
+from rest_framework.pagination import PageNumberPagination
+from rest_framework.request import Request
+from rest_framework.response import Response
+from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
-from rest_framework_bulk import BulkCreateModelMixin
from pydis_site.apps.api.models.bot.user import User
from pydis_site.apps.api.serializers import UserSerializer
-class UserViewSet(BulkCreateModelMixin, ModelViewSet):
+class UserListPagination(PageNumberPagination):
+ """Custom pagination class for the User Model."""
+
+ page_size = 10000
+ page_size_query_param = "page_size"
+
+
+class UserViewSet(ModelViewSet):
"""
View providing CRUD operations on Discord users through the bot.
## Routes
### GET /bot/users
- Returns all users currently known.
+ Returns all users currently known with pagination.
#### Response format
- >>> [
- ... {
+ >>> {
+ ... 'count': 95000,
+ ... 'next': "http://api.pythondiscord.com/bot/users?page=2",
+ ... 'previous': None,
+ ... 'results': [
+ ... {
... 'id': 409107086526644234,
... 'name': "Python",
... 'discriminator': 4329,
@@ -26,8 +42,13 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
... 458226699344019457
... ],
... 'in_guild': True
- ... }
- ... ]
+ ... },
+ ... ]
+ ... }
+
+ #### Query Parameters
+ - page_size: Number of Users in one page.
+ - page: Page number
#### Status codes
- 200: returned on success
@@ -118,4 +139,75 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
"""
serializer_class = UserSerializer
- queryset = User.objects
+ queryset = User.objects.all()
+ pagination_class = UserListPagination
+
+ def get_serializer(self, *args, **kwargs) -> ModelSerializer:
+ """Set Serializer many attribute to True if request body contains a list."""
+ if isinstance(kwargs.get('data', {}), list):
+ kwargs['many'] = True
+
+ return super().get_serializer(*args, **kwargs)
+
+ @action(detail=False, methods=["PATCH"], name='user-bulk-patch')
+ def bulk_patch(self, request: Request) -> Response:
+ """
+ Update multiple User objects in a single request.
+
+ ## Route
+ ### PATCH /bot/users/bulk_patch
+ Update all users with the IDs.
+ `id` field is mandatory, rest are optional.
+
+ #### Request body
+ >>> [
+ ... {
+ ... 'id': int,
+ ... 'name': str,
+ ... 'discriminator': int,
+ ... 'roles': List[int],
+ ... 'in_guild': bool
+ ... },
+ ... {
+ ... 'id': int,
+ ... 'name': str,
+ ... 'discriminator': int,
+ ... 'roles': List[int],
+ ... 'in_guild': bool
+ ... },
+ ... ]
+
+ #### Status codes
+ - 200: Returned on success.
+ - 400: if the request body was invalid, see response body for details.
+ """
+ queryset = self.get_queryset()
+ try:
+ object_ids = [item["id"] for item in request.data]
+ except KeyError:
+ # user ID not provided in request body.
+ resp = {
+ "Error": "User ID not provided."
+ }
+ return Response(resp, status=status.HTTP_400_BAD_REQUEST)
+
+ filtered_instances = queryset.filter(id__in=object_ids)
+
+ if filtered_instances.count() != len(object_ids):
+ # If all user objects passed in request.body are not present in the database.
+ resp = {
+ "Error": "User object not found."
+ }
+ return Response(resp, status=status.HTTP_404_NOT_FOUND)
+
+ serializer = self.get_serializer(
+ instance=filtered_instances,
+ data=request.data,
+ many=True,
+ partial=True
+ )
+
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_200_OK)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)