diff --git a/Pipfile b/Pipfile index 2eaec3d..fec2e6f 100644 --- a/Pipfile +++ b/Pipfile @@ -19,6 +19,8 @@ tox = "*" mock = "*" django = ">=3.0.7" six = ">=1.13.0" +geoip2 = "" +maxminddb = "*" [requires] python_version = ">=3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 7d06c38..edf561e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "cf3cae422030c786d98443dfc385314223293ab9eab4e468a959679a3133466b" + "sha256": "2c919b14a91b884b0fdd066263d6f1e7bfd6015696f9590c6a819eefb2a629e0" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,49 @@ ] }, "default": { + "aiohttp": { + "hashes": [ + "sha256:0b795072bb1bf87b8620120a6373a3c61bfcb8da7e5c2377f4bb23ff4f0b62c9", + "sha256:0d438c8ca703b1b714e82ed5b7a4412c82577040dadff479c08405e2a715564f", + "sha256:16a3cb5df5c56f696234ea9e65e227d1ebe9c18aa774d36ff42f532139066a5f", + "sha256:1edfd82a98c5161497bbb111b2b70c0813102ad7e0aa81cbeb34e64c93863005", + "sha256:2406dc1dda01c7f6060ab586e4601f18affb7a6b965c50a8c90ff07569cf782a", + "sha256:2858b2504c8697beb9357be01dc47ef86438cc1cb36ecb6991796d19475faa3e", + "sha256:2a7b7640167ab536c3cb90cfc3977c7094f1c5890d7eeede8b273c175c3910fd", + "sha256:3228b7a51e3ed533f5472f54f70fd0b0a64c48dc1649a0f0e809bec312934d7a", + "sha256:328b552513d4f95b0a2eea4c8573e112866107227661834652a8984766aa7656", + "sha256:39f4b0a6ae22a1c567cb0630c30dd082481f95c13ca528dc501a7766b9c718c0", + "sha256:3b0036c978cbcc4a4512278e98e3e6d9e6b834dc973206162eddf98b586ef1c6", + "sha256:3ea8c252d8df5e9166bcf3d9edced2af132f4ead8ac422eac723c5781063709a", + "sha256:41608c0acbe0899c852281978492f9ce2c6fbfaf60aff0cefc54a7c4516b822c", + "sha256:59d11674964b74a81b149d4ceaff2b674b3b0e4d0f10f0be1533e49c4a28408b", + "sha256:5e479df4b2d0f8f02133b7e4430098699450e1b2a826438af6bec9a400530957", + "sha256:684850fb1e3e55c9220aad007f8386d8e3e477c4ec9211ae54d968ecdca8c6f9", + "sha256:6ccc43d68b81c424e46192a778f97da94ee0630337c9bbe5b2ecc9b0c1c59001", + "sha256:6d42debaf55450643146fabe4b6817bb2a55b23698b0434107e892a43117285e", + "sha256:710376bf67d8ff4500a31d0c207b8941ff4fba5de6890a701d71680474fe2a60", + "sha256:756ae7efddd68d4ea7d89c636b703e14a0c686688d42f588b90778a3c2fc0564", + "sha256:77149002d9386fae303a4a162e6bce75cc2161347ad2ba06c2f0182561875d45", + "sha256:78e2f18a82b88cbc37d22365cf8d2b879a492faedb3f2975adb4ed8dfe994d3a", + "sha256:7d9b42127a6c0bdcc25c3dcf252bb3ddc70454fac593b1b6933ae091396deb13", + "sha256:8389d6044ee4e2037dca83e3f6994738550f6ee8cfb746762283fad9b932868f", + "sha256:9c1a81af067e72261c9cbe33ea792893e83bc6aa987bfbd6fdc1e5e7b22777c4", + "sha256:c1e0920909d916d3375c7a1fdb0b1c78e46170e8bb42792312b6eb6676b2f87f", + "sha256:c68fdf21c6f3573ae19c7ee65f9ff185649a060c9a06535e9c3a0ee0bbac9235", + "sha256:c733ef3bdcfe52a1a75564389bad4064352274036e7e234730526d155f04d914", + "sha256:c9c58b0b84055d8bc27b7df5a9d141df4ee6ff59821f922dd73155861282f6a3", + "sha256:d03abec50df423b026a5aa09656bd9d37f1e6a49271f123f31f9b8aed5dc3ea3", + "sha256:d2cfac21e31e841d60dc28c0ec7d4ec47a35c608cb8906435d47ef83ffb22150", + "sha256:dcc119db14757b0c7bce64042158307b9b1c76471e655751a61b57f5a0e4d78e", + "sha256:df3a7b258cc230a65245167a202dd07320a5af05f3d41da1488ba0fa05bc9347", + "sha256:df48a623c58180874d7407b4d9ec06a19b84ed47f60a3884345b1a5099c1818b", + "sha256:e1b95972a0ae3f248a899cdbac92ba2e01d731225f566569311043ce2226f5e7", + "sha256:f326b3c1bbfda5b9308252ee0dcb30b612ee92b0e105d4abec70335fab5b1245", + "sha256:f411cb22115cb15452d099fec0ee636b06cf81bfb40ed9c02d30c8dc2bc2e3d1" + ], + "markers": "python_version >= '3.6'", + "version": "==3.7.3" + }, "appdirs": { "hashes": [ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", @@ -25,24 +68,34 @@ }, "asgiref": { "hashes": [ - "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", - "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" + "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", + "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" ], - "version": "==3.2.10" + "markers": "python_version >= '3.5'", + "version": "==3.3.1" + }, + "async-timeout": { + "hashes": [ + "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", + "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + ], + "markers": "python_full_version >= '3.5.3'", + "version": "==3.0.1" }, "attrs": { "hashes": [ - "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", - "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" + "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", + "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" ], - "version": "==20.2.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.3.0" }, "certifi": { "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" ], - "version": "==2020.6.20" + "version": "==2020.11.8" }, "chardet": { "hashes": [ @@ -88,15 +141,16 @@ "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636", "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==5.3" }, "coveralls": { "hashes": [ - "sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6", - "sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1" + "sha256:2301a19500b06649d2ec4f2858f9c69638d7699a4c63027c5d53daba666147cc", + "sha256:b990ba1f7bc4288e63340be0433698c1efe8217f78c689d254c2540af3d38617" ], "index": "pypi", - "version": "==2.1.2" + "version": "==2.2.0" }, "distlib": { "hashes": [ @@ -107,11 +161,11 @@ }, "django": { "hashes": [ - "sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc", - "sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4" + "sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927", + "sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f" ], "index": "pypi", - "version": "==3.1.2" + "version": "==3.1.3" }, "django-environ": { "hashes": [ @@ -123,11 +177,10 @@ }, "djangorestframework": { "hashes": [ - "sha256:5c5071fcbad6dce16f566d492015c829ddb0df42965d488b878594aabc3aed21", - "sha256:d54452aedebb4b650254ca092f9f4f5df947cb1de6ab245d817b08b4f4156249" + "sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7" ], "index": "pypi", - "version": "==3.12.1" + "version": "==3.12.2" }, "docopt": { "hashes": [ @@ -158,19 +211,36 @@ "index": "pypi", "version": "==3.7.0" }, + "geoip2": { + "hashes": [ + "sha256:57d8d15de2527e0697bbef44fc16812bba709f03a07ef99297bd56c1df3b1efd", + "sha256:707025542ef076bd8fd80e97138bebdb7812527b2a007d141a27ad98b0370fff" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==4.1.0" + }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "iniconfig": { "hashes": [ - "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437", - "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69" + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" ], - "version": "==1.0.1" + "version": "==1.1.1" + }, + "maxminddb": { + "hashes": [ + "sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" + ], + "index": "pypi", + "version": "==2.0.3" }, "mccabe": { "hashes": [ @@ -187,11 +257,55 @@ "index": "pypi", "version": "==4.0.2" }, + "multidict": { + "hashes": [ + "sha256:060d68ae3e674c913ec41a464916f12c4d7ff17a3a9ebbf37ba7f2c681c2b33e", + "sha256:06f39f0ddc308dab4e5fa282d145f90cd38d7ed75390fc83335636909a9ec191", + "sha256:17847fede1aafdb7e74e01bb34ab47a1a1ea726e8184c623c45d7e428d2d5d34", + "sha256:1cd102057b09223b919f9447c669cf2efabeefb42a42ae6233f25ffd7ee31a79", + "sha256:20cc9b2dd31761990abff7d0e63cd14dbfca4ebb52a77afc917b603473951a38", + "sha256:2576e30bbec004e863d87216bc34abe24962cc2e964613241a1c01c7681092ab", + "sha256:2ab9cad4c5ef5c41e1123ed1f89f555aabefb9391d4e01fd6182de970b7267ed", + "sha256:359ea00e1b53ceef282232308da9d9a3f60d645868a97f64df19485c7f9ef628", + "sha256:3e61cc244fd30bd9fdfae13bdd0c5ec65da51a86575ff1191255cae677045ffe", + "sha256:43c7a87d8c31913311a1ab24b138254a0ee89142983b327a2c2eab7a7d10fea9", + "sha256:4a3f19da871befa53b48dd81ee48542f519beffa13090dc135fffc18d8fe36db", + "sha256:4df708ef412fd9b59b7e6c77857e64c1f6b4c0116b751cb399384ec9a28baa66", + "sha256:59182e975b8c197d0146a003d0f0d5dc5487ce4899502061d8df585b0f51fba2", + "sha256:6128d2c0956fd60e39ec7d1c8f79426f0c915d36458df59ddd1f0cff0340305f", + "sha256:6168839491a533fa75f3f5d48acbb829475e6c7d9fa5c6e245153b5f79b986a3", + "sha256:62abab8088704121297d39c8f47156cb8fab1da731f513e59ba73946b22cf3d0", + "sha256:653b2bbb0bbf282c37279dd04f429947ac92713049e1efc615f68d4e64b1dbc2", + "sha256:6566749cd78cb37cbf8e8171b5cd2cbfc03c99f0891de12255cf17a11c07b1a3", + "sha256:76cbdb22f48de64811f9ce1dd4dee09665f84f32d6a26de249a50c1e90e244e0", + "sha256:8efcf070d60fd497db771429b1c769a3783e3a0dd96c78c027e676990176adc5", + "sha256:8fa4549f341a057feec4c3139056ba73e17ed03a506469f447797a51f85081b5", + "sha256:9380b3f2b00b23a4106ba9dd022df3e6e2e84e1788acdbdd27603b621b3288df", + "sha256:9ed9b280f7778ad6f71826b38a73c2fdca4077817c64bc1102fdada58e75c03c", + "sha256:a7b8b5bd16376c8ac2977748bd978a200326af5145d8d0e7f799e2b355d425b6", + "sha256:af271c2540d1cd2a137bef8d95a8052230aa1cda26dd3b2c73d858d89993d518", + "sha256:b561e76c9e21402d9a446cdae13398f9942388b9bff529f32dfa46220af54d00", + "sha256:b82400ef848bbac6b9035a105ac6acaa1fb3eea0d164e35bbb21619b88e49fed", + "sha256:b98af08d7bb37d3456a22f689819ea793e8d6961b9629322d7728c4039071641", + "sha256:c58e53e1c73109fdf4b759db9f2939325f510a8a5215135330fe6755921e4886", + "sha256:cbabfc12b401d074298bfda099c58dfa5348415ae2e4ec841290627cb7cb6b2e", + "sha256:d4a6fb98e9e9be3f7d70fd3e852369c00a027bd5ed0f3e8ade3821bcad257408", + "sha256:d99da85d6890267292065e654a329e1d2f483a5d2485e347383800e616a8c0b1", + "sha256:e58db0e0d60029915f7fc95a8683fa815e204f2e1990f1fb46a7778d57ca8c35", + "sha256:e5bf89fe57f702a046c7ec718fe330ed50efd4bcf74722940db2eb0919cddb1c", + "sha256:f612e8ef8408391a4a3366e3508bab8ef97b063b4918a317cb6e6de4415f01af", + "sha256:f65a2442c113afde52fb09f9a6276bbc31da71add99dc76c3adf6083234e07c6", + "sha256:fa0503947a99a1be94f799fac89d67a5e20c333e78ddae16e8534b151cdc588a" + ], + "markers": "python_version >= '3.5'", + "version": "==5.0.2" + }, "packaging": { "hashes": [ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.4" }, "pluggy": { @@ -199,6 +313,7 @@ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, "py": { @@ -206,6 +321,7 @@ "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.9.0" }, "pycodestyle": { @@ -213,6 +329,7 @@ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.6.0" }, "pyflakes": { @@ -220,6 +337,7 @@ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.2.0" }, "pyparsing": { @@ -227,15 +345,16 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9", - "sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92" + "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe", + "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e" ], "index": "pypi", - "version": "==6.1.1" + "version": "==6.1.2" }, "pytest-cov": { "hashes": [ @@ -247,25 +366,26 @@ }, "pytest-django": { "hashes": [ - "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6", - "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4" + "sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2", + "sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f" ], "index": "pypi", - "version": "==3.10.0" + "version": "==4.1.0" }, "pytz": { "hashes": [ - "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", - "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" + "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268", + "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd" ], - "version": "==2020.1" + "version": "==2020.4" }, "requests": { "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", + "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" ], - "version": "==2.24.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.25.0" }, "six": { "hashes": [ @@ -277,39 +397,94 @@ }, "sqlparse": { "hashes": [ - "sha256:0523026398aea9c8b5f7a4a6d5c0829c285b4fbd960c17b5967a369342e21e01", - "sha256:d59e473424ae7470778fa4dd0dd7bb666ff324f0a6106c29deb5946ea5367f04" + "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", + "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" ], - "version": "==0.4.0" + "markers": "python_version >= '3.5'", + "version": "==0.4.1" }, "toml": { "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==0.10.1" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" }, "tox": { "hashes": [ - "sha256:e6318f404aff16522ff5211c88cab82b39af121735a443674e4e2e65f4e4637b", - "sha256:eb629ddc60e8542fd4a1956b2462e3b8771d49f1ff630cecceacaa0fbfb7605a" + "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2", + "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6" ], "index": "pypi", - "version": "==3.20.0" + "version": "==3.20.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "version": "==3.7.4.3" }, "urllib3": { "hashes": [ - "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", - "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", + "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" ], - "version": "==1.25.10" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.2" }, "virtualenv": { "hashes": [ - "sha256:35ecdeb58cfc2147bb0706f7cdef69a8f34f1b81b6d49568174e277932908b8f", - "sha256:a5e0d253fe138097c6559c906c528647254f437d1019af9d5a477b09bfa7300f" - ], - "version": "==20.0.33" + "sha256:07cff122e9d343140366055f31be4dcd61fd598c69d11cd33a9d9c8df4546dd7", + "sha256:e0aac7525e880a429764cefd3aaaff54afb5d9f25c82627563603f5d7de5a6e5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.2.1" + }, + "yarl": { + "hashes": [ + "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", + "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", + "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", + "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", + "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", + "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", + "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", + "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", + "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", + "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", + "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", + "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", + "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", + "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", + "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", + "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", + "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", + "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", + "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", + "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", + "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", + "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", + "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", + "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", + "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", + "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", + "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", + "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", + "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", + "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", + "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", + "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", + "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", + "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", + "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", + "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", + "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" + ], + "markers": "python_version >= '3.6'", + "version": "==1.6.3" } }, "develop": { @@ -339,6 +514,7 @@ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.4" }, "pluggy": { @@ -346,6 +522,7 @@ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, "py": { @@ -353,6 +530,7 @@ "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.9.0" }, "pyparsing": { @@ -360,6 +538,7 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "six": { @@ -372,18 +551,19 @@ }, "toml": { "hashes": [ - "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", - "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "version": "==0.10.1" + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" }, "tox": { "hashes": [ - "sha256:e6318f404aff16522ff5211c88cab82b39af121735a443674e4e2e65f4e4637b", - "sha256:eb629ddc60e8542fd4a1956b2462e3b8771d49f1ff630cecceacaa0fbfb7605a" + "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2", + "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6" ], "index": "pypi", - "version": "==3.20.0" + "version": "==3.20.1" }, "tox-pyenv": { "hashes": [ @@ -395,10 +575,11 @@ }, "virtualenv": { "hashes": [ - "sha256:35ecdeb58cfc2147bb0706f7cdef69a8f34f1b81b6d49568174e277932908b8f", - "sha256:a5e0d253fe138097c6559c906c528647254f437d1019af9d5a477b09bfa7300f" + "sha256:07cff122e9d343140366055f31be4dcd61fd598c69d11cd33a9d9c8df4546dd7", + "sha256:e0aac7525e880a429764cefd3aaaff54afb5d9f25c82627563603f5d7de5a6e5" ], - "version": "==20.0.33" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==20.2.1" } } } diff --git a/README.md b/README.md index 202e8d8..7b6afb8 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ drf-api-tracking provides a Django model and DRF view mixin that work together t `view` | Target VIEW of the request, e.g., `"views.api.ApiView"` | CharField `view_method` | Target METHOD of the VIEW of the request, e.g., `"get"` | CharField `remote_addr` | IP address where the request originated (X_FORWARDED_FOR if available, REMOTE_ADDR if not), e.g., `"127.0.0.1"` | GenericIPAddressField +`request_city` | City where the reuqest originated | CharField +`request_country` | Country where the reuqest originated | CharField `host` | Originating host of the request, e.g., `"example.com"` | URLField `method` | HTTP method, e.g., `"GET"` | CharField `query_params` | Dictionary of request query parameters, as text | TextField diff --git a/requirements.txt b/requirements.txt index 9cec52a..912181e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ Django>=1.11 djangorestframework>=3.5 six>=1.14.0 +geoip2>=4.1.0 # Test requirements pytest-django diff --git a/rest_framework_tracking/admin.py b/rest_framework_tracking/admin.py index 0137fb4..5838f61 100644 --- a/rest_framework_tracking/admin.py +++ b/rest_framework_tracking/admin.py @@ -24,7 +24,7 @@ class APIRequestLogAdmin(admin.ModelAdmin): if app_settings.ADMIN_LOG_READONLY: readonly_fields = ('user', 'username_persistent', 'requested_at', 'response_ms', 'path', 'view', 'view_method', - 'remote_addr', 'host', 'method', 'query_params', + 'remote_addr', 'request_city', 'request_country', 'host', 'method', 'query_params', 'data', 'response', 'errors', 'status_code') def changelist_view(self, request, extra_context=None): diff --git a/rest_framework_tracking/base_mixins.py b/rest_framework_tracking/base_mixins.py index 03443a1..3bdd39e 100644 --- a/rest_framework_tracking/base_mixins.py +++ b/rest_framework_tracking/base_mixins.py @@ -5,11 +5,17 @@ from django.db import connection from django.utils.timezone import now +from django.contrib.gis.geoip2 import GeoIP2 +from django.conf import settings + from .app_settings import app_settings logger = logging.getLogger(__name__) +# create an instance of GeoIP2 if GEOIP_PATH was set in settings.py +geo_location = GeoIP2() if hasattr(settings, 'GEOIP_PATH') else None + class BaseLoggingMixin(object): """Mixin to log requests""" @@ -74,10 +80,17 @@ def finalize_response(self, request, response, *args, **kwargs): rendered_content = response.rendered_content else: rendered_content = response.getvalue() + + ip_address = self._get_ip_address(request) + + # update request city and country + request_location_dict = {} + if geo_location: + request_location_dict = self._get_request_location(ip_address) self.log.update( { - "remote_addr": self._get_ip_address(request), + "remote_addr": ip_address, "view": self._get_view_name(request), "view_method": self._get_view_method(request), "path": self._get_path(request), @@ -91,10 +104,13 @@ def finalize_response(self, request, response, *args, **kwargs): "response_ms": self._get_response_ms(), "response": self._clean_data(rendered_content), "status_code": response.status_code, + **request_location_dict } ) if self._clean_data(request.query_params.dict()) == {}: self.log.update({"query_params": self.log["data"]}) + + try: self.handle_log() except Exception: @@ -154,7 +170,7 @@ def _get_view_name(self, request): def _get_view_method(self, request): """Get view method.""" if hasattr(self, "action"): - return self.action if self.action else None + return self.action or None return request.method.lower() def _get_user(self, request): @@ -173,6 +189,19 @@ def _get_response_ms(self): response_ms = int(response_timedelta.total_seconds() * 1000) return max(response_ms, 0) + def _get_request_location(self, ip_address): + try: + geo_location_data = geo_location.city(ip_address) + request_city = geo_location_data["city"] + request_country = geo_location_data["country_name"] + + return { + "request_city": request_city, + "request_country": request_country, + } + except Exception: + return {} + def should_log(self, request, response): """ Method that should return a value that evaluated to True if the request should be logged. @@ -221,7 +250,7 @@ def _clean_data(self, data): value = ast.literal_eval(value) except (ValueError, SyntaxError): pass - if isinstance(value, list) or isinstance(value, dict): + if isinstance(value, (list, dict)): data[key] = self._clean_data(value) if key.lower() in SENSITIVE_FIELDS: data[key] = self.CLEANED_SUBSTITUTE diff --git a/rest_framework_tracking/base_models.py b/rest_framework_tracking/base_models.py index c8eead1..ea3b133 100644 --- a/rest_framework_tracking/base_models.py +++ b/rest_framework_tracking/base_models.py @@ -37,6 +37,8 @@ class BaseAPIRequestLog(models.Model): db_index=True, ) remote_addr = models.GenericIPAddressField() + request_city = models.CharField(max_length=255, null=True, blank=True) + request_country = models.CharField(max_length=255, null=True, blank=True) host = models.URLField() method = models.CharField(max_length=10) query_params = models.TextField(null=True, blank=True) diff --git a/rest_framework_tracking/migrations/0011_auto_20201123_1321.py b/rest_framework_tracking/migrations/0011_auto_20201123_1321.py new file mode 100644 index 0000000..e283264 --- /dev/null +++ b/rest_framework_tracking/migrations/0011_auto_20201123_1321.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.3 on 2020-11-23 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rest_framework_tracking', '0010_auto_20200609_1404'), + ] + + operations = [ + migrations.AddField( + model_name='apirequestlog', + name='request_city', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='apirequestlog', + name='request_country', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/tests/test_mixins.py b/tests/test_mixins.py index c4e50ea..ad66975 100644 --- a/tests/test_mixins.py +++ b/tests/test_mixins.py @@ -485,6 +485,21 @@ def test_custom_log_handler(self): self.client.post('/custom-log-handler') self.assertEqual(APIRequestLog.objects.all().count(), 1) + def test_log_request_city(self): + request = APIRequestFactory().get('/logging') + request.META['REMOTE_ADDR'] = '127.0.0.9' + + MockLoggingView.as_view()(request).render() + log = APIRequestLog.objects.first() + self.assertEqual(log.request_city, None) + + def test_log_request_country(self): + request = APIRequestFactory().get('/logging') + request.META['REMOTE_ADDR'] = '127.0.0.9' + + MockLoggingView.as_view()(request).render() + log = APIRequestLog.objects.first() + self.assertEqual(log.request_country, None) @override_settings(DATA_UPLOAD_MAX_MEMORY_SIZE=1) def test_decode_request_body_setting(self): content_type = "multipart/form-data; boundary=_"