diff --git a/Pipfile.lock b/Pipfile.lock index ef9c21b..c661f51 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -60,11 +60,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.12.7" }, "cffi": { "hashes": [ @@ -137,11 +137,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", - "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" + "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", + "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "markers": "python_full_version >= '3.6.0'", + "version": "==2.1.1" }, "click": { "hashes": [ @@ -156,7 +156,7 @@ "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.2'", + "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'", "version": "==0.3.0" }, "click-plugins": { @@ -175,30 +175,34 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" + "sha256:0e70da4bdff7601b0ef48e6348339e490ebfb0cbe638e083c9c41fb49f00c8bd", + "sha256:10652dd7282de17990b88679cb82f832752c4e8237f0c714be518044269415db", + "sha256:175c1a818b87c9ac80bb7377f5520b7f31b3ef2a0004e2420319beadedb67290", + "sha256:1d7e632804a248103b60b16fb145e8df0bc60eed790ece0d12efe8cd3f3e7744", + "sha256:1f13ddda26a04c06eb57119caf27a524ccae20533729f4b1e4a69b54e07035eb", + "sha256:2ec2a8714dd005949d4019195d72abed84198d877112abb5a27740e217e0ea8d", + "sha256:2fa36a7b2cc0998a3a4d5af26ccb6273f3df133d61da2ba13b3286261e7efb70", + "sha256:2fb481682873035600b5502f0015b664abc26466153fab5c6bc92c1ea69d478b", + "sha256:3178d46f363d4549b9a76264f41c6948752183b3f587666aff0555ac50fd7876", + "sha256:4367da5705922cf7070462e964f66e4ac24162e22ab0a2e9d31f1b270dd78083", + "sha256:4eb85075437f0b1fd8cd66c688469a0c4119e0ba855e3fef86691971b887caf6", + "sha256:50a1494ed0c3f5b4d07650a68cd6ca62efe8b596ce743a5c94403e6f11bf06c1", + "sha256:53049f3379ef05182864d13bb9686657659407148f901f3f1eee57a733fb4b00", + "sha256:6391e59ebe7c62d9902c24a4d8bcbc79a68e7c4ab65863536127c8a9cd94043b", + "sha256:67461b5ebca2e4c2ab991733f8ab637a7265bb582f07c7c88914b5afb88cb95b", + "sha256:78e47e28ddc4ace41dd38c42e6feecfdadf9c3be2af389abbfeef1ff06822285", + "sha256:80ca53981ceeb3241998443c4964a387771588c4e4a5d92735a493af868294f9", + "sha256:8a4b2bdb68a447fadebfd7d24855758fe2d6fecc7fed0b78d190b1af39a8e3b0", + "sha256:8e45653fb97eb2f20b8c96f9cd2b3a0654d742b47d638cf2897afbd97f80fa6d", + "sha256:998cd19189d8a747b226d24c0207fdaa1e6658a1d3f2494541cb9dfbf7dcb6d2", + "sha256:a10498349d4c8eab7357a8f9aa3463791292845b79597ad1b98a543686fb1ec8", + "sha256:b4cad0cea995af760f82820ab4ca54e5471fc782f70a007f31531957f43e9dee", + "sha256:bfe6472507986613dc6cc00b3d492b2f7564b02b3b3682d25ca7f40fa3fd321b", + "sha256:c9e0d79ee4c56d841bd4ac6e7697c8ff3c8d6da67379057f29e66acffcd1e9a7", + "sha256:ca57eb3ddaccd1112c18fc80abe41db443cc2e9dcb1917078e02dfa010a4f353", + "sha256:ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c" ], - "version": "==37.0.4" + "version": "==38.0.4" }, "defusedxml": { "hashes": [ @@ -208,14 +212,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.7.1" }, - "deprecated": { - "hashes": [ - "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", - "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.2.13" - }, "django": { "hashes": [ "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a", @@ -260,11 +256,11 @@ }, "django-celery-beat": { "hashes": [ - "sha256:ba24f86ed956ba0ee2b43237a49303ebf5aa7e0fb0ccb7e0542b7e31a463dcba", - "sha256:ff694e1d30f84de3d2b78822c90005c6c7c1462768e845ae162af71a6a46f1eb" + "sha256:58efe9460e4373a241c2b3d839518f29a28ae19bc80a8dba20da204c7ea50613", + "sha256:a20b7a7857daaa2eea1f4cfefb2a965456800216f5763c6cf918c2e21372b3c8" ], "index": "pypi", - "version": "==2.3.0" + "version": "==2.4.0" }, "django-celery-results": { "hashes": [ @@ -276,11 +272,11 @@ }, "django-compressor": { "hashes": [ - "sha256:1db91b6d04293636a68bd1328dc7bb90d636b0295f67b1cc6d4fa102b9fd25f6", - "sha256:b4fe15cc23bf39420b37cb0030572bd0971104ca1ec3764f502c0f179e576dff" + "sha256:61f313852b4c8d4ef2534cda3d2366f45ca3e399b3cbe10590e516cc6b45542d", + "sha256:8ece621d2a98f6c6635480cb8b3701db890a99f793f95ca20cb00abc194d331d" ], "index": "pypi", - "version": "==4.0" + "version": "==4.1" }, "django-filter": { "hashes": [ @@ -314,11 +310,11 @@ }, "django-render-block": { "hashes": [ - "sha256:a01bfdb839e2f6b3f88a99021597484392bbd15d084f9a796e3e5658bae800f4", - "sha256:fbdd8be56cefcfd794756a2e62117cc031f9c5de3ef4bb53e9a3f877a359a1a7" + "sha256:1768832be78476c36627b3e3a8e7d9eb0e2bc46b84daf2c51958e2f674173c40", + "sha256:3dde0d2abde9381685659d2ed0761b8bddf7741c48eaadec9e5b50a6c7850247" ], - "markers": "python_version >= '3.6'", - "version": "==0.9.1" + "markers": "python_version >= '3.7'", + "version": "==0.9.2" }, "django-setup-cli": { "hashes": [ @@ -338,19 +334,18 @@ }, "django-storages": { "hashes": [ - "sha256:204a99f218b747c46edbfeeb1310d357f83f90fa6a6024d8d0a3f422570cee84", - "sha256:a475edb2f0f04c4f7e548919a751ecd50117270833956ed5bd585c0575d2a5e7" + "sha256:3540b45618b04be2c867c0982e8d2bd8e34f84dae922267fcebe4691fb93daf0", + "sha256:b3d98ecc09f1b1627c2b2cf430964322ce4e08617dbf9b4236c16a32875a1e0b" ], "index": "pypi", - "version": "==1.12.3" + "version": "==1.13.1" }, "django-templated-email": { "hashes": [ - "sha256:49d61840ec551e640adaf341146e94d6f9058ae01df964480850bf988046e5eb", - "sha256:bf1b68ffe6c8794c0c50e2ce20e3a166c6d511b3879abbd3cf059a3fc2fe2e60" + "sha256:432430293b0be35495faa23fcd8d60b33ff59d9a10b3366d7e01f75d9ff843f2" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.0.1" }, "django-timezone-field": { "hashes": [ @@ -370,11 +365,11 @@ }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.3" + "version": "==3.4" }, "kombu": { "hashes": [ @@ -386,80 +381,84 @@ }, "lxml": { "hashes": [ - "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", - "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", - "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", - "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", - "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", - "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", - "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", - "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", - "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", - "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", - "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", - "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", - "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", - "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", - "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", - "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", - "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", - "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", - "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", - "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", - "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", - "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", - "sha256:49a866923e69bc7da45a0565636243707c22752fc38f6b9d5c8428a86121022c", - "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", - "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", - "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", - "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", - "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", - "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", - "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", - "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", - "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", - "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", - "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", - "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", - "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", - "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", - "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", - "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", - "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", - "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", - "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", - "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", - "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", - "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", - "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", - "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", - "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", - "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", - "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", - "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", - "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", - "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", - "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", - "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", - "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", - "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", - "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", - "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", - "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", - "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", - "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", - "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", - "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", - "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", - "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", - "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", - "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", - "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", - "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", - "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" + "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7", + "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726", + "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03", + "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140", + "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a", + "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05", + "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03", + "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419", + "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4", + "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e", + "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67", + "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50", + "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894", + "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf", + "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947", + "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1", + "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd", + "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92", + "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3", + "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457", + "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74", + "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf", + "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1", + "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4", + "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975", + "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5", + "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe", + "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7", + "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1", + "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2", + "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409", + "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f", + "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f", + "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5", + "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24", + "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e", + "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4", + "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a", + "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c", + "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de", + "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f", + "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b", + "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7", + "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a", + "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c", + "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9", + "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e", + "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab", + "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941", + "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5", + "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45", + "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7", + "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892", + "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746", + "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c", + "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53", + "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe", + "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184", + "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38", + "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df", + "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9", + "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b", + "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2", + "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0", + "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda", + "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b", + "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5", + "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380", + "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33", + "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8", + "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1", + "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889", + "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9", + "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f", + "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.9.1" + "version": "==4.9.2" }, "measurement": { "hashes": [ @@ -476,19 +475,11 @@ }, "oauthlib": { "hashes": [ - "sha256:23a8208d75b902797ea29fd31fa80a15ed9dc2c6c16fe73f5d346f83f6fa27a2", - "sha256:6db33440354787f9b7f3a6dbd4febf5d0f93758354060e802f6c06cb493022fe" + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" ], "markers": "python_version >= '3.6'", - "version": "==3.2.0" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" + "version": "==3.2.2" }, "paypal-checkout-serversdk": { "hashes": [ @@ -508,137 +499,155 @@ }, "pillow": { "hashes": [ - "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", - "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", - "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", - "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", - "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", - "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", - "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", - "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", - "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", - "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", - "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", - "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", - "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", - "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", - "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", - "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", - "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", - "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", - "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", - "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", - "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", - "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", - "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", - "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", - "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", - "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", - "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", - "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", - "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", - "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", - "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", - "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", - "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", - "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", - "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", - "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", - "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", - "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", - "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", - "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", - "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", - "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", - "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", - "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", - "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", - "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", - "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", - "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", - "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", - "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", - "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", - "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", - "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", - "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", - "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", - "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", - "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", - "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" + "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040", + "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8", + "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65", + "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2", + "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627", + "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07", + "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef", + "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535", + "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c", + "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc", + "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3", + "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1", + "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c", + "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa", + "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32", + "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502", + "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4", + "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f", + "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812", + "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636", + "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20", + "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c", + "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91", + "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe", + "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b", + "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad", + "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9", + "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72", + "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4", + "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de", + "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29", + "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee", + "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c", + "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7", + "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11", + "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c", + "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c", + "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448", + "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b", + "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20", + "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228", + "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd", + "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699", + "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b", + "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2", + "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4", + "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c", + "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f", + "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2", + "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c", + "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3", + "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193", + "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48", + "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02", + "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8", + "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e", + "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f", + "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b", + "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74", + "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb", + "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0" ], "index": "pypi", - "version": "==9.2.0" + "version": "==9.3.0" }, "prompt-toolkit": { "hashes": [ - "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", - "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" + "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63", + "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.30" + "version": "==3.0.36" }, "psycopg2-binary": { "hashes": [ - "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7", - "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76", - "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa", - "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9", - "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004", - "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1", - "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094", - "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57", - "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af", - "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554", - "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232", - "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c", - "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b", - "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834", - "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2", - "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71", - "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460", - "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e", - "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4", - "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d", - "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d", - "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9", - "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f", - "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063", - "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478", - "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092", - "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c", - "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce", - "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1", - "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65", - "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e", - "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4", - "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029", - "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33", - "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39", - "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53", - "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307", - "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42", - "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35", - "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8", - "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb", - "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae", - "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e", - "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f", - "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba", - "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24", - "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca", - "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb", - "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef", - "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42", - "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1", - "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667", - "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272", - "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281", - "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e", - "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd" + "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50", + "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425", + "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f", + "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0", + "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460", + "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41", + "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85", + "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd", + "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0", + "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd", + "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147", + "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c", + "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903", + "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba", + "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632", + "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577", + "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c", + "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7", + "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867", + "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2", + "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9", + "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff", + "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a", + "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302", + "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1", + "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79", + "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835", + "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42", + "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e", + "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61", + "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32", + "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68", + "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1", + "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60", + "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8", + "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b", + "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a", + "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec", + "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5", + "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2", + "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16", + "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5", + "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6", + "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1", + "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503", + "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b", + "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d", + "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28", + "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4", + "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5", + "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5", + "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e", + "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f", + "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636", + "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d", + "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64", + "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb", + "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882", + "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720", + "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896", + "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267", + "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7", + "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f", + "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91", + "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c", + "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24", + "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee", + "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d", + "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b", + "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935", + "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69" ], "index": "pypi", - "version": "==2.9.3" + "version": "==2.9.5" }, "pycparser": { "hashes": [ @@ -652,27 +661,19 @@ "crypto" ], "hashes": [ - "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf", - "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba" + "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd", + "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14" ], - "markers": "python_version >= '3.6'", - "version": "==2.4.0" + "markers": "python_version >= '3.7'", + "version": "==2.6.0" }, "pyopenssl": { "hashes": [ - "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", - "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" + "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968", + "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e" ], "markers": "python_version >= '3.6'", - "version": "==22.0.0" - }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" + "version": "==22.1.0" }, "python-crontab": { "hashes": [ @@ -698,10 +699,10 @@ }, "python-stdnum": { "hashes": [ - "sha256:374e2b5e13912ccdbf50b0b23fca2c3e0531174805c32d74e145f37756328340", - "sha256:a46e6cf9652807314d369b654b255c86a59f93d18be2834f3d567ed1a346c547" + "sha256:bcc763d9c49ae23da5d2b7a686d5fd1deec9d9051341160a10d1ac723a26bec0", + "sha256:d7f2a3c7ef4635c957b9cbdd9b1993d1f6ee3a2959f03e172c45440d99f296eb" ], - "version": "==1.17" + "version": "==1.18" }, "python3-openid": { "hashes": [ @@ -712,13 +713,14 @@ }, "pytz": { "hashes": [ - "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", - "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", + "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" ], - "version": "==2022.1" + "version": "==2022.6" }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -730,26 +732,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -783,18 +791,18 @@ }, "redis": { "hashes": [ - "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54", - "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880" + "sha256:7b8c87d19c45d3f1271b124858d2a5c13160c4e74d4835e28273400fa34d5228", + "sha256:cae3ee5d1f57d8caf534cd8764edf3163c77e073bdd74b6f54a87ffafdc5e7d9" ], "index": "pypi", - "version": "==4.3.4" + "version": "==4.4.0" }, "requests": { "hashes": [ "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" ], - "markers": "python_version >= '3.7' and python_version < '4.0'", + "markers": "python_version >= '3.7' and python_version < '4'", "version": "==2.28.1" }, "requests-oauthlib": { @@ -832,19 +840,19 @@ }, "sentry-sdk": { "hashes": [ - "sha256:6f460da98b730d671510af18f119f96d01e3ba027ac0e985871abb3aede1c514", - "sha256:95fd321f583dfcfaf279a0b2cdc83d8d28c8b7cca4d2959fc4539bb4fecb56a0" + "sha256:3c9bc64025976842c1103cd75d45cff94a7c0cc48fa07770d07a09d2ab8dac30", + "sha256:dc0fe6ef2f77a3853b399c75c97d87be7666098817c1c314f8fcdf68a6865914" ], "index": "pypi", - "version": "==1.7.2" + "version": "==1.12.0" }, "setuptools": { "hashes": [ - "sha256:0d33c374d41c7863419fc8f6c10bfe25b7b498aa34164d135c622e52580c6b16", - "sha256:c04b44a57a6265fe34a4a444e965884716d34bae963119a76353434d6f18e450" + "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54", + "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75" ], "markers": "python_version >= '3.7'", - "version": "==63.2.0" + "version": "==65.6.3" }, "six": { "hashes": [ @@ -856,43 +864,43 @@ }, "sqlparse": { "hashes": [ - "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", - "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" + "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", + "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" ], "markers": "python_version >= '3.5'", - "version": "==0.4.2" + "version": "==0.4.3" }, "stripe": { "hashes": [ - "sha256:08f74cae6619d4a7d78f8162ff72bc3e9c913f53ec96ecd5ddc7d823c2e79ddd", - "sha256:69d5bf4611624a503bcec84a61b1f2a2b874bfc828432e4fd75cd120bcc3efef" + "sha256:50f69a5fc9cd6f7629861b80ea70a3c6da0c76401ddc159474f2d45fab9215d6", + "sha256:bdc8c5ee8e6b53c4767bbe720752cedb8af91db46ad96e511edd0fb74964ac5d" ], "index": "pypi", - "version": "==3.5.0" + "version": "==5.0.0" }, "sympy": { "hashes": [ - "sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b", - "sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130" + "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf", + "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658" ], - "markers": "python_version >= '3.7'", - "version": "==1.10.1" + "markers": "python_version >= '3.8'", + "version": "==1.11.1" }, "tzdata": { "hashes": [ - "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", - "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" + "sha256:2b88858b0e3120792a3c0635c23daf36a7d7eeeca657c323da299d2094402a0d", + "sha256:fe5f866eddd8b96e9fcba978f8e503c909b19ea7efda11e52e39494bad3a7bfa" ], "markers": "python_version >= '2'", - "version": "==2022.1" + "version": "==2022.7" }, "urllib3": { "hashes": [ - "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", - "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", - "version": "==1.26.10" + "markers": "python_version >= '3.6'", + "version": "==1.26.13" }, "usps-api": { "hashes": [ @@ -916,76 +924,6 @@ ], "version": "==0.2.5" }, - "wrapt": { - "hashes": [ - "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", - "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", - "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", - "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", - "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", - "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", - "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", - "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", - "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", - "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", - "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", - "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", - "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", - "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", - "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", - "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", - "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", - "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", - "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", - "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", - "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", - "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", - "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", - "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", - "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", - "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", - "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", - "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", - "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", - "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", - "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", - "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", - "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", - "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", - "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", - "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", - "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", - "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", - "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", - "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", - "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", - "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", - "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", - "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", - "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", - "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", - "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", - "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", - "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", - "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", - "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", - "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", - "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", - "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", - "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", - "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", - "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", - "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", - "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", - "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", - "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", - "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", - "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", - "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.14.1" - }, "xmltodict": { "hashes": [ "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56", @@ -1014,115 +952,19 @@ }, "attrs": { "hashes": [ - "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", - "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", + "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.4.0" + "markers": "python_version >= '3.5'", + "version": "==22.1.0" }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", + "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" - }, - "cffi": { - "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "version": "==1.15.1" - }, - "cryptography": { - "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" - ], - "version": "==37.0.4" + "version": "==2022.12.7" }, "django": { "hashes": [ @@ -1134,27 +976,35 @@ }, "django-debug-toolbar": { "hashes": [ - "sha256:89a52128309eb4da12738801ff0c202d2ff8730d1c3225fac6acf630c303e661", - "sha256:97965f2630692de316ea0c1ca5bfa81660d7ba13146dbc6be2059cf55b35d0e5" + "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27", + "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.8.1" + }, + "exceptiongroup": { + "hashes": [ + "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828", + "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec" + ], + "markers": "python_version < '3.11'", + "version": "==1.0.4" }, "h11": { "hashes": [ - "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", - "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" ], - "markers": "python_version >= '3.6'", - "version": "==0.13.0" + "markers": "python_version >= '3.7'", + "version": "==0.14.0" }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.3" + "version": "==3.4" }, "outcome": { "hashes": [ @@ -1166,26 +1016,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", - "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" + "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053", + "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610" ], "index": "pypi", - "version": "==2.8.0" - }, - "pycparser": { - "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" - ], - "version": "==2.21" - }, - "pyopenssl": { - "hashes": [ - "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", - "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" - ], - "markers": "python_version >= '3.6'", - "version": "==22.0.0" + "version": "==2.10.0" }, "pysocks": { "hashes": [ @@ -1197,18 +1032,19 @@ }, "selenium": { "hashes": [ - "sha256:f67402b8f973aaa98d9c55b8f9aa63532009cd1859b2222a8b9800354942d8bc" + "sha256:06a1c7d9f313130b21c3218ddd8852070d0e7419afdd31f96160cd576555a5ce", + "sha256:3aefa14a28a42e520550c1cd0f29cf1d566328186ea63aa9a3e01fb265b5894d" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.7.2" }, "sniffio": { "hashes": [ - "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", - "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" ], - "markers": "python_version >= '3.5'", - "version": "==1.2.0" + "markers": "python_version >= '3.7'", + "version": "==1.3.0" }, "sortedcontainers": { "hashes": [ @@ -1219,19 +1055,19 @@ }, "sqlparse": { "hashes": [ - "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", - "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" + "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", + "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" ], "markers": "python_version >= '3.5'", - "version": "==0.4.2" + "version": "==0.4.3" }, "trio": { "hashes": [ - "sha256:4dc0bf9d5cc78767fc4516325b6d80cc0968705a31d0eec2ecd7cdda466265b0", - "sha256:523f39b7b69eef73501cebfe1aafd400a9aad5b03543a0eded52952488ff1c13" + "sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf", + "sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0" ], "markers": "python_version >= '3.7'", - "version": "==0.21.0" + "version": "==0.22.0" }, "trio-websocket": { "hashes": [ @@ -1243,19 +1079,19 @@ }, "urllib3": { "hashes": [ - "sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec", - "sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6" + "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", + "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4.0'", - "version": "==1.26.10" + "markers": "python_version >= '3.6'", + "version": "==1.26.13" }, "wsproto": { "hashes": [ - "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b", - "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8" + "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", + "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736" ], - "markers": "python_version >= '3.7'", - "version": "==1.1.0" + "markers": "python_full_version >= '3.7.0'", + "version": "==1.2.0" } } } diff --git a/src/accounts/apps.py b/src/accounts/apps.py index 35cfd1f..734d6a1 100644 --- a/src/accounts/apps.py +++ b/src/accounts/apps.py @@ -5,7 +5,7 @@ class AccountsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'accounts' - # def ready(self): - # from .signals import ( - # user_saved - # ) + def ready(self): + from .signals import ( + user_saved + ) diff --git a/src/accounts/models.py b/src/accounts/models.py index 583ef01..f800495 100644 --- a/src/accounts/models.py +++ b/src/accounts/models.py @@ -1,3 +1,4 @@ +import stripe from django.db import models from django.urls import reverse from django.contrib.auth.models import AbstractUser @@ -17,6 +18,18 @@ class Address(models.Model): ) postal_code = models.CharField(max_length=20, blank=True) + def as_stripe_dict(self): + return { + 'name': f'{self.first_name} {self.last_name}', + 'address': { + 'line1': self.street_address_1, + 'line2': self.street_address_2, + 'city': self.city, + 'state': self.state, + 'postal_code': self.postal_code + } + } + def __str__(self): return f""" {self.first_name} {self.last_name} @@ -45,3 +58,13 @@ class User(AbstractUser): Address, related_name="+", null=True, blank=True, on_delete=models.SET_NULL ) stripe_id = models.CharField(max_length=255, blank=True) + + def get_or_create_stripe_id(self): + if not self.stripe_id: + response = stripe.Customer.create( + name=self.first_name + ' ' + self.last_name, + email=self.email + ) + self.stripe_id = response['id'] + self.save() + return self.stripe_id diff --git a/src/accounts/signals.py b/src/accounts/signals.py index 97ac32e..02a14db 100644 --- a/src/accounts/signals.py +++ b/src/accounts/signals.py @@ -8,15 +8,11 @@ from django.conf import settings from .models import Address, User logger = logging.getLogger(__name__) +stripe.api_key = settings.STRIPE_API_KEY @receiver(post_save, sender=User, dispatch_uid='user_saved') def user_saved(sender, instance, created, **kwargs): logger.info('User was saved') if created or not instance.stripe_id: - stripe.api_key = settings.STRIPE_API_KEY - response = stripe.Customer.create( - name=instance.first_name + instance.last_name - ) - instance.stripe_id = response['id'] - instance.save() + instance.get_or_create_stripe_id() diff --git a/src/accounts/utils.py b/src/accounts/utils.py index de0c7e7..8154408 100644 --- a/src/accounts/utils.py +++ b/src/accounts/utils.py @@ -4,7 +4,7 @@ from .models import Address, User from .tasks import send_account_created_email -def get_or_create_customer(request, form, shipping_address): +def get_or_create_customer(request, shipping_address): address, a_created = Address.objects.get_or_create( first_name=shipping_address['first_name'], last_name=shipping_address['last_name'], diff --git a/src/core/__init__.py b/src/core/__init__.py index 6ca70b1..efb4c27 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -190,21 +190,3 @@ class CoffeeGrind: (PERCOLATOR, 'Percolator'), (CAFE_STYLE, 'BLTC cafe pour over') ] - - -def build_usps_rate_request(weight, container, zip_destination): - service = ShippingContainer.get_shipping_service_from_container(container) - return \ - { - 'service': service, - 'zip_origination': settings.DEFAULT_ZIP_ORIGINATION, - 'zip_destination': zip_destination, - 'pounds': weight, - 'ounces': '0', - 'container': container, - 'width': '', - 'length': '', - 'height': '', - 'girth': '', - 'machinable': 'TRUE' - } diff --git a/src/core/admin.py b/src/core/admin.py index 324c630..9263d80 100644 --- a/src/core/admin.py +++ b/src/core/admin.py @@ -11,6 +11,7 @@ from .models import ( ShippingRate, Order, Transaction, + Subscription, OrderLine, ) @@ -24,4 +25,5 @@ admin.site.register(Coupon) admin.site.register(ShippingRate) admin.site.register(Order) admin.site.register(Transaction) +admin.site.register(Subscription) admin.site.register(OrderLine) diff --git a/src/core/apps.py b/src/core/apps.py index 6cb4229..cf4aa6c 100644 --- a/src/core/apps.py +++ b/src/core/apps.py @@ -7,7 +7,6 @@ class CoreConfig(AppConfig): def ready(self): from .signals import ( - # variant_saved, order_created, transaction_created, order_line_post_save, diff --git a/src/core/migrations/0016_alter_productvariant_stripe_id.py b/src/core/migrations/0016_alter_productvariant_stripe_id.py new file mode 100644 index 0000000..447a863 --- /dev/null +++ b/src/core/migrations/0016_alter_productvariant_stripe_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.2 on 2022-11-28 23:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0015_alter_order_coupon_amount'), + ] + + operations = [ + migrations.AlterField( + model_name='productvariant', + name='stripe_id', + field=models.CharField(blank=True, db_index=True, max_length=255), + ), + ] diff --git a/src/core/migrations/0017_stripeprice.py b/src/core/migrations/0017_stripeprice.py new file mode 100644 index 0000000..406e127 --- /dev/null +++ b/src/core/migrations/0017_stripeprice.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0.2 on 2022-12-02 18:38 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0016_alter_productvariant_stripe_id'), + ] + + operations = [ + migrations.CreateModel( + name='StripePrice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stripe_id', models.CharField(blank=True, db_index=True, max_length=255)), + ('unit_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)), + ('interval', models.CharField(choices=[('day', 'Days'), ('week', 'Weeks'), ('month', 'Month'), ('year', 'Year')], default='month', max_length=16)), + ('interval_count', models.PositiveIntegerField(default=1)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('variant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stripe_prices', to='core.productvariant')), + ], + ), + ] diff --git a/src/core/migrations/0018_subscriptionitem_subscription_is_active_and_more.py b/src/core/migrations/0018_subscriptionitem_subscription_is_active_and_more.py new file mode 100644 index 0000000..bf0e9af --- /dev/null +++ b/src/core/migrations/0018_subscriptionitem_subscription_is_active_and_more.py @@ -0,0 +1,58 @@ +# Generated by Django 4.0.2 on 2022-12-04 00:55 + +from django.db import migrations, models +import django.db.models.deletion +import django_measurement.models +import measurement.measures.mass + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0017_stripeprice'), + ] + + operations = [ + migrations.CreateModel( + name='SubscriptionItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('product_name', models.CharField(max_length=255)), + ('quantity', models.PositiveIntegerField()), + ('weight', django_measurement.models.MeasurementField(blank=True, measurement=measurement.measures.mass.Mass, null=True)), + ], + ), + migrations.AddField( + model_name='subscription', + name='is_active', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='subscription', + name='options', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='subscription', + name='schedule', + field=models.CharField(choices=[('1 week', 'Every week'), ('2 weeks', 'Every 2 weeks'), ('1 month', 'Every month')], default='1 month', max_length=255), + ), + migrations.AddField( + model_name='subscription', + name='size', + field=models.CharField(choices=[(12, '12 oz ($10.80)'), (16, '16 oz ($14.40)'), (75, '5 lbs ($67.50)')], default=16, max_length=255), + ), + migrations.AlterField( + model_name='subscription', + name='stripe_id', + field=models.CharField(blank=True, db_index=True, max_length=255), + ), + migrations.DeleteModel( + name='StripePrice', + ), + migrations.AddField( + model_name='subscriptionitem', + name='subscription', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.subscription'), + ), + ] diff --git a/src/core/migrations/0019_subscriptionproduct_alter_subscription_size_and_more.py b/src/core/migrations/0019_subscriptionproduct_alter_subscription_size_and_more.py new file mode 100644 index 0000000..aeab606 --- /dev/null +++ b/src/core/migrations/0019_subscriptionproduct_alter_subscription_size_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 4.0.2 on 2022-12-04 01:05 + +from django.db import migrations, models +import django.db.models.deletion +import django_measurement.models +import measurement.measures.mass + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0018_subscriptionitem_subscription_is_active_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='SubscriptionProduct', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stripe_id', models.CharField(blank=True, db_index=True, max_length=255)), + ('product_name', models.CharField(max_length=255)), + ('quantity', models.PositiveIntegerField()), + ('weight', django_measurement.models.MeasurementField(blank=True, measurement=measurement.measures.mass.Mass, null=True)), + ], + ), + migrations.AlterField( + model_name='subscription', + name='size', + field=models.CharField(choices=[('12 oz', '12 oz ($10.80)'), ('16 oz', '16 oz ($14.40)'), ('5 lb', '5 lbs ($67.50)')], default='16 oz', max_length=255), + ), + migrations.DeleteModel( + name='SubscriptionItem', + ), + migrations.AddField( + model_name='subscriptionproduct', + name='subscription', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.subscription'), + ), + ] diff --git a/src/core/migrations/0020_subscriptionitem_alter_subscription_schedule_and_more.py b/src/core/migrations/0020_subscriptionitem_alter_subscription_schedule_and_more.py new file mode 100644 index 0000000..d0726ec --- /dev/null +++ b/src/core/migrations/0020_subscriptionitem_alter_subscription_schedule_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.0.2 on 2022-12-04 01:52 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0019_subscriptionproduct_alter_subscription_size_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='SubscriptionItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stripe_id', models.CharField(blank=True, db_index=True, max_length=255)), + ('product_name', models.CharField(max_length=255)), + ('quantity', models.PositiveIntegerField()), + ], + ), + migrations.AlterField( + model_name='subscription', + name='schedule', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='subscription', + name='size', + field=models.CharField(max_length=255), + ), + migrations.DeleteModel( + name='SubscriptionProduct', + ), + migrations.AddField( + model_name='subscriptionitem', + name='subscription', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.subscription'), + ), + ] diff --git a/src/core/migrations/0021_remove_subscription_options_and_more.py b/src/core/migrations/0021_remove_subscription_options_and_more.py new file mode 100644 index 0000000..6da94c4 --- /dev/null +++ b/src/core/migrations/0021_remove_subscription_options_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 4.0.2 on 2022-12-04 17:53 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0020_subscriptionitem_alter_subscription_schedule_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscription', + name='options', + ), + migrations.RemoveField( + model_name='subscription', + name='schedule', + ), + migrations.RemoveField( + model_name='subscription', + name='size', + ), + migrations.AddField( + model_name='subscription', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='subscription', + name='items', + field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(blank=True, null=True), default=list, size=None), + ), + migrations.AddField( + model_name='subscription', + name='metadata', + field=models.JSONField(blank=True, null=True), + ), + migrations.AddField( + model_name='subscription', + name='stripe_price_id', + field=models.CharField(blank=True, db_index=True, max_length=255), + ), + migrations.AddField( + model_name='subscription', + name='total_quantity', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='subscription', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='subscription', + name='customer', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscriptions', to=settings.AUTH_USER_MODEL), + ), + migrations.DeleteModel( + name='SubscriptionItem', + ), + ] diff --git a/src/core/migrations/0022_remove_subscription_stripe_price_id_and_more.py b/src/core/migrations/0022_remove_subscription_stripe_price_id_and_more.py new file mode 100644 index 0000000..39cc7b9 --- /dev/null +++ b/src/core/migrations/0022_remove_subscription_stripe_price_id_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.2 on 2022-12-04 18:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0021_remove_subscription_options_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscription', + name='stripe_price_id', + ), + migrations.RemoveField( + model_name='subscription', + name='total_quantity', + ), + ] diff --git a/src/core/migrations/0023_subscription_total_weight.py b/src/core/migrations/0023_subscription_total_weight.py new file mode 100644 index 0000000..5ca57d6 --- /dev/null +++ b/src/core/migrations/0023_subscription_total_weight.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.2 on 2022-12-11 18:08 + +import core.weight +from django.db import migrations +import django_measurement.models +import measurement.measures.mass + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0022_remove_subscription_stripe_price_id_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='subscription', + name='total_weight', + field=django_measurement.models.MeasurementField(blank=True, default=core.weight.zero_weight, measurement=measurement.measures.mass.Mass, null=True), + ), + ] diff --git a/src/core/migrations/0024_subscription_shipping_address.py b/src/core/migrations/0024_subscription_shipping_address.py new file mode 100644 index 0000000..2cdc94a --- /dev/null +++ b/src/core/migrations/0024_subscription_shipping_address.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.2 on 2022-12-16 22:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0003_user_stripe_id'), + ('core', '0023_subscription_total_weight'), + ] + + operations = [ + migrations.AddField( + model_name='subscription', + name='shipping_address', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='accounts.address'), + ), + ] diff --git a/src/core/migrations/0025_productvariant_core_produc_stripe__6a1141_idx_and_more.py b/src/core/migrations/0025_productvariant_core_produc_stripe__6a1141_idx_and_more.py new file mode 100644 index 0000000..e71068e --- /dev/null +++ b/src/core/migrations/0025_productvariant_core_produc_stripe__6a1141_idx_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.2 on 2022-12-18 21:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0024_subscription_shipping_address'), + ] + + operations = [ + migrations.AddIndex( + model_name='productvariant', + index=models.Index(fields=['stripe_id'], name='core_produc_stripe__6a1141_idx'), + ), + migrations.AddIndex( + model_name='subscription', + index=models.Index(fields=['stripe_id'], name='core_subscr_stripe__08018b_idx'), + ), + ] diff --git a/src/core/migrations/0026_orderline_product.py b/src/core/migrations/0026_orderline_product.py new file mode 100644 index 0000000..744b777 --- /dev/null +++ b/src/core/migrations/0026_orderline_product.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.2 on 2022-12-18 21:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0025_productvariant_core_produc_stripe__6a1141_idx_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='orderline', + name='product', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_lines', to='core.product'), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 5bad9e0..19504f5 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -1,4 +1,5 @@ import logging +import json from decimal import Decimal from PIL import Image from measurement.measures import Weight @@ -24,9 +25,9 @@ from . import ( TransactionStatus, OrderStatus, ShippingProvider, - ShippingContainer, - build_usps_rate_request + ShippingContainer ) +from .usps import build_usps_rate_request from .weight import WeightUnits, zero_weight logger = logging.getLogger(__name__) @@ -197,44 +198,9 @@ class ProductVariant(models.Model): class Meta: ordering = ['sorting', 'weight'] - - -class StripePrice: - # When this model is updated, it will query stripe and update all of the instances of this model - DAY = 'day' - WEEK = 'week' - MONTH = 'month' - YEAR = 'year' - INTERVAL_CHOICES = [ - (DAY, 'Days'), - (WEEK, 'Weeks'), - (MONTH, 'Month'), - (YEAR, 'Year'), - ] - - stripe_id = models.CharField(max_length=255, blank=True, db_index=True) - variant = models.ForeignKey( - ProductVariant, - related_name='stripe_prices', - on_delete=models.CASCADE, - blank=True, - null=True, - ) - unit_amount = models.DecimalField( - max_digits=settings.DEFAULT_MAX_DIGITS, - decimal_places=settings.DEFAULT_DECIMAL_PLACES, - blank=True, - null=True, - ) - interval = models.CharField( - max_length=16, - choices=INTERVAL_CHOICES, - default=MONTH - ) - interval_count = models.PositiveIntegerField(default=1) - - created_at = models.DateTimeField(auto_now_add=True, editable=False) - updated_at = models.DateTimeField(auto_now=True) + indexes = [ + models.Index(fields=['stripe_id']) + ] class ProductOption(models.Model): @@ -476,6 +442,13 @@ class OrderLine(models.Model): editable=False, on_delete=models.CASCADE ) + product = models.ForeignKey( + Product, + related_name='order_lines', + on_delete=models.SET_NULL, + blank=True, + null=True, + ) variant = models.ForeignKey( ProductVariant, related_name='order_lines', @@ -538,14 +511,60 @@ class TrackingNumber(models.Model): return self.tracking_id +class SubscriptionManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(is_active=True) + + class Subscription(models.Model): - stripe_id = models.CharField(max_length=255, blank=True) - customer = models.OneToOneField( + stripe_id = models.CharField(max_length=255, blank=True, db_index=True) + customer = models.ForeignKey( User, - related_name='subscription', + related_name='subscriptions', on_delete=models.SET_NULL, null=True ) + shipping_address = models.ForeignKey( + Address, + related_name='+', + editable=False, + null=True, + on_delete=models.SET_NULL + ) + items = ArrayField( + models.JSONField(blank=True, null=True), + default=list + ) + metadata = models.JSONField(blank=True, null=True) + is_active = models.BooleanField(default=False) + total_weight = MeasurementField( + measurement=Weight, + unit_choices=WeightUnits.CHOICES, + default=zero_weight, + blank=True, + null=True + ) + + created_at = models.DateTimeField(auto_now_add=True, editable=False) + updated_at = models.DateTimeField(auto_now=True) + + def create_order(self, data_object): + pass + + def format_metadata(self): + metadata = {} + for key, value in self.metadata.items(): + metadata[key] = json.dumps(value) + metadata['subscription_pk'] = self.pk + return metadata + + def get_absolute_url(self): + return reverse('storefront:subscription-detail', kwargs={'pk': self.pk}) + + class Meta: + indexes = [ + models.Index(fields=['stripe_id']) + ] class SiteSettings(SingletonBase): diff --git a/src/core/shipping.py b/src/core/shipping.py new file mode 100644 index 0000000..c8ec28c --- /dev/null +++ b/src/core/shipping.py @@ -0,0 +1,84 @@ +import logging +from decimal import Decimal +from django.conf import settings +from django.db.models import Q + +from measurement.measures import Weight + +from core.usps import USPSApi +from core.exceptions import USPSPostageError, ShippingAddressError +from core.models import ( + ShippingRate, + SiteSettings, +) +from core import ( + ShippingService, + ShippingProvider, + ShippingContainer +) +from core.usps import build_usps_rate_request + +logger = logging.getLogger(__name__) + + +def get_shipping_container_choices_from_weight(weight): + is_selectable = Q( + is_selectable=True + ) + min_weight_matched = Q( + min_order_weight__lte=weight) | Q( + min_order_weight__isnull=True + ) + max_weight_matched = Q( + max_order_weight__gte=weight) | Q( + max_order_weight__isnull=True + ) + containers = ShippingRate.objects.filter( + is_selectable & min_weight_matched & max_weight_matched + ) + return containers + + +def get_shipping_container_from_choices(choices): + if len(choices) == 0: + return SiteSettings.load().default_shipping_rate.container + return choices[0].container + + +def get_shipping_cost(total_weight, postal_code): + if not total_weight > Weight(lb=0): + return Decimal('0.00') + + container = get_shipping_container_from_choices( + get_shipping_container_choices_from_weight(total_weight) + ) + + usps_rate_request = build_usps_rate_request( + str(total_weight.lb), container, str(postal_code) + ) + + usps = USPSApi(settings.USPS_USER_ID, test=settings.DEBUG) + + try: + validation = usps.get_rate(usps_rate_request) + except ConnectionError as e: + raise e( + 'Could not connect to USPS, try again.' + ) + + logger.info(validation.result) + try: + postage = dict( + validation.result['RateV4Response']['Package']['Postage'] + ) + except KeyError: + raise USPSPostageError( + 'Could not retrieve postage.' + ) + + if usps_rate_request['service'] == ShippingContainer.PRIORITY: + shipping_cost = Decimal(postage['Rate']) + elif usps_rate_request['service'] == ShippingContainer.PRIORITY_COMMERCIAL: + shipping_cost = Decimal(postage['CommercialRate']) + + return shipping_cost diff --git a/src/core/signals.py b/src/core/signals.py index bdd99f4..a39901b 100644 --- a/src/core/signals.py +++ b/src/core/signals.py @@ -17,35 +17,7 @@ from .tasks import ( ) logger = logging.getLogger(__name__) - - -# @receiver(post_save, sender=ProductVariant, dispatch_uid='variant_created') -# def variant_saved(sender, instance, created, **kwargs): -# logger.info('Product was saved') -# if created or not instance.stripe_id: -# stripe.api_key = settings.STRIPE_API_KEY -# prod_response = stripe.Product.create( -# name=instance.product.name + ': ' + instance.name, -# description=instance.product.description -# ) -# price_response = stripe.Price.create( -# unit_amount=int(instance.price * 100), -# currency=settings.DEFAULT_CURRENCY, -# product=prod_response['id'] -# ) -# instance.stripe_id = prod_response['id'] -# instance.stripe_price_id = price_response['id'] -# instance.save() -# else: -# stripe.Product.modify( -# instance.stripe_id, -# name=instance.product.name + ': ' + instance.name, -# description=instance.product.description -# ) -# stripe.Price.modify( -# instance.stripe_price_id, -# unit_amount=int(instance.price * 100) -# ) +stripe.api_key = settings.STRIPE_API_KEY @receiver(post_save, sender=Order, dispatch_uid="order_created") @@ -102,7 +74,9 @@ def order_line_post_save(sender, instance, created, **kwargs): pk=instance.order.pk )[0] - order.status = get_order_status(order.total_quantity_fulfilled, order.total_quantity_ordered) + order.status = get_order_status( + order.total_quantity_fulfilled, order.total_quantity_ordered + ) order.save() # order.update( diff --git a/src/core/subscription_utils.py b/src/core/subscription_utils.py new file mode 100644 index 0000000..c38a9cc --- /dev/null +++ b/src/core/subscription_utils.py @@ -0,0 +1,84 @@ +import locale +locale.setlocale(locale.LC_ALL, '') + +data_object['subscription'] + + +def convert_int_to_currency(price): + return locale.currency(int(price) / 100) + + +def convert_int_to_decimal(price): + return Decimal(str(price)[:-2] + '.' + str(price)[-2:]) + + +def find_shipping_cost(data): + for x in data: + if x['description'] == 'Shipping': + return convert_int_to_currency(x['amount']) + break + else: + continue + + +def format_product(data, unit_price): + return { + 'product': Product.objects.get(pk=data['pk']), + 'quantity': data['quantity'] + } + + +def find_products(data): + for x in data: + if 'products_and_quantities' in x['metadata']: + return map(format_product, x['metadata']['products_and_quantities']) + break + else: + continue + + +shipping_cost = None +unit_price = None +items = None +customer_note = '' + + +def deserialize_subscription(data): + for x in data: + if 'products_and_quantities' in x['metadata']: + customer_note = f"Grind: {x['metadata']['grind']}" + unit_price = convert_int_to_decimal(x['price']['unit_amount']) + items = map(format_product, x['metadata']['products_and_quantities']) + + if x['description'] == 'Shipping': + shipping_cost = convert_int_to_decimal(x['amount']) + continue + + +# shipping_cost = find_shipping_cost(data_object['lines']['data']) +# items = find_products(data_object['lines']['data']) +# unit_price = find_unit_price(data_object['lines']['data']) + +deserialize_subscription(data_object['lines']['data']) + +order = Order.objects.create( + customer=, + status=, + billing_address=, + shipping_address=, + subtotal_amount=, + shipping_total=, + total_amount=data_object['total'], + weight= +) + +order.lines.add( + [OrderLine( + product=item['product'], + quantity=item['quantity'], + customer_note='Grind: ', + unit_price=unit_price + ) for item in items] +) + +order.save() diff --git a/src/core/usps.py b/src/core/usps.py index 0e42e71..15053fb 100644 --- a/src/core/usps.py +++ b/src/core/usps.py @@ -4,6 +4,8 @@ import xmltodict from lxml import etree from usps import USPSApi as USPSApiBase +from django.conf import settings +from . import ShippingContainer class USPSApi(USPSApiBase): @@ -37,3 +39,21 @@ class Rate: etree.SubElement(package, 'Machinable').text = request['machinable'] self.result = usps.send_request('rate', xml) + + +def build_usps_rate_request(weight, container, zip_destination): + service = ShippingContainer.get_shipping_service_from_container(container) + return \ + { + 'service': service, + 'zip_origination': settings.DEFAULT_ZIP_ORIGINATION, + 'zip_destination': zip_destination, + 'pounds': weight, + 'ounces': '0', + 'container': container, + 'width': '', + 'length': '', + 'height': '', + 'girth': '', + 'machinable': 'TRUE' + } diff --git a/src/dashboard/templates/dashboard/subscription/list.html b/src/dashboard/templates/dashboard/subscription/list.html new file mode 100644 index 0000000..80b9d7a --- /dev/null +++ b/src/dashboard/templates/dashboard/subscription/list.html @@ -0,0 +1,45 @@ +{% extends "dashboard.html" %} +{% load static %} + +{% block content %} +
+
+

Subscriptions

+
+
+
+ Date + Customer + Total +
+ {% for subscription in subscription_list %} + + {{subscription.created_at|date:"D, M j Y"}} + {{subscription.customer.get_full_name}} + ${{subscription.total_amount}} + + {% empty %} + No subscriptions + {% endfor %} +
+
+ +
+
+{% endblock content %} diff --git a/src/dashboard/templates/dashboard/variant_form.html b/src/dashboard/templates/dashboard/variant_form.html index 44107d8..0de7b98 100644 --- a/src/dashboard/templates/dashboard/variant_form.html +++ b/src/dashboard/templates/dashboard/variant_form.html @@ -4,7 +4,9 @@

Update variant

- Delete +
+ Delete +
diff --git a/src/dashboard/urls.py b/src/dashboard/urls.py index 2b0b180..4faef0c 100644 --- a/src/dashboard/urls.py +++ b/src/dashboard/urls.py @@ -248,4 +248,35 @@ urlpatterns = [ name='customer-update' ), ])), + + # Subscriptions + path('subscriptions/', include([ + path( + '', + views.SubscriptionListView.as_view(), + name='subscription-list' + ), + # path( + # 'new/', + # views.SubscriptionCreateView.as_view(), + # name='subscription-create' + # ), + # path('/', include([ + # path( + # '', + # views.SubscriptionDetailView.as_view(), + # name='subscription-detail' + # ), + # path( + # 'update/', + # views.SubscriptionUpdateView.as_view(), + # name='subscription-update' + # ), + # path( + # 'delete/', + # views.SubscriptionDeleteView.as_view(), + # name='subscription-delete' + # ), + # ])), + ])), ] diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 48146ed..9b01d1d 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -39,6 +39,7 @@ from core.models import ( Transaction, TrackingNumber, Coupon, + Subscription, SiteSettings ) @@ -582,3 +583,31 @@ class CustomerUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): def get_success_url(self): return reverse('dashboard:customer-detail', kwargs={'pk': self.object.pk}) + + +class SubscriptionListView(LoginRequiredMixin, ListView): + model = Subscription + template_name = 'dashboard/subscription/list.html' + + +class SubscriptionCreateView(SuccessMessageMixin, CreateView): + model = Subscription + success_message = 'Subscription created.' + template_name_suffix = '_create_form' + fields = '__all__' + + +class SubscriptionDetailView(DetailView): + model = Subscription + + +class SubscriptionUpdateView(SuccessMessageMixin, UpdateView): + model = Subscription + success_message = 'Subscription saved.' + fields = '__all__' + + +class SubscriptionDeleteView(SuccessMessageMixin, DeleteView): + model = Subscription + success_message = 'Subscription deleted.' + success_url = reverse_lazy('subscription-list') diff --git a/src/ptcoffee/config.py b/src/ptcoffee/config.py index 8d6e813..55edbde 100644 --- a/src/ptcoffee/config.py +++ b/src/ptcoffee/config.py @@ -32,6 +32,7 @@ SENTRY_ENV = os.environ.get('SENTRY_ENV', 'development') FACEBOOK_PIXEL_ID = os.environ.get('FACEBOOK_PIXEL_ID', '') STRIPE_API_KEY = os.environ.get('STRIPE_API_KEY', '') +STRIPE_PUBLISHABLE_KEY = os.environ.get('STRIPE_PUBLISHABLE_KEY', '') PAYPAL_CLIENT_ID = os.environ.get('PAYPAL_CLIENT_ID', '') PAYPAL_SECRET_ID = os.environ.get('PAYPAL_SECRET_ID', '') diff --git a/src/static/images/recurrent.png b/src/static/images/recurrent.png new file mode 100644 index 0000000..48f0f6f Binary files /dev/null and b/src/static/images/recurrent.png differ diff --git a/src/static/scripts/subscriptions.js b/src/static/scripts/subscriptions.js index d541d08..b883041 100644 --- a/src/static/scripts/subscriptions.js +++ b/src/static/scripts/subscriptions.js @@ -1,27 +1,72 @@ -class Subscription { - static TWELVE_OZ - static SIXTEEN_OZ - static FIVE_LBS +class SubscriptionForm { + form + stripe_price_input + total_quantity_input + products_and_quantities + weight_per_item = '0' + weight_unit = 'lb' + products + max_quantity = 20 - static TWELVE_SHIPPING - static SIXTEEN_SHIPPING - static FIVE_SHIPPING + constructor(form) { + this.form = document.querySelector(form) + this.stripe_price_input = this.form.querySelector('input[name=stripe_price_id]') + this.products = this.form.querySelectorAll('input[name^=product_]') + this.total_quantity_input = this.form.querySelector('input[name=total_quantity]') + this.total_weight_input = this.form.querySelector('input[name=total_weight]') + this.products_and_quantities = this.form.querySelector('input[name=products_and_quantities]') - constructor(element, output) { - this.TWELVE_OZ = '12' - this.SIXTEEN_OZ = '16' - this.FIVE_LBS = '75' - this.TWELVE_SHIPPING = 7 - this.SIXTEEN_SHIPPING = 5 - this.FIVE_SHIPPING = 1 + this.connect() + } - this.element = element - this.output = this.element.querySelector('.output') - this.shippingDiscount = 10 - this.price = this.element.querySelector('select[name=size]') - this.products = this.element.querySelectorAll('input[name^=product]') - this.element.addEventListener('change', this.render.bind(this)) - this.render() + connect() { + this.form.addEventListener('change', this.change.bind(this)) + const formData = new FormData(this.form) + for (const input of formData) { + console.log(input) + } + } + + change(event) { + if (event.target.name == 'stripe_product') { + this.stripe_price_input.value = event.target.parentElement.querySelector('select[name=stripe_price]').value + const values = event.target.dataset.weight.split(':') + this.weight_per_item = values[0] + this.weight_unit = values[1] + } else if (event.target.name == 'stripe_price') { + this.stripe_price_input.value = event.target.value + } else if (event.target.name.includes('product_')) { + const selected = Array.from(this.products).filter(item => item.value > 0) + this.total_quantity_input.value = this.total_qty + this.products_and_quantities.value = JSON.stringify( + selected.map(item => { + return {'pk': item.dataset.id, 'name': item.name.slice(8), 'quantity': Number(item.value)} + }) + ) + } + + this.total_weight_input.value = this.total_weight + + this.checkMaxQuantity() + + console.log(`${this.stripe_price_input.name}: ${this.stripe_price_input.value}`) + console.log(`${this.total_weight_input.name}: ${this.total_weight_input.value}`) + console.log(`${this.total_quantity_input.name}: ${this.total_quantity_input.value}`) + console.log(`${this.products_and_quantities.name}: ${this.products_and_quantities.value}`) + } + + checkMaxQuantity() { + if (this.total_qty < this.max_quantity) { + Array.from(this.products).map(input => input.max = this.max_quantity) + } else { + Array.from(this.products).map(input => { + if (input.value == '') { + input.max = 0 + } else { + input.max = input.value + } + }) + } } get total_qty() { @@ -30,112 +75,12 @@ class Subscription { }, 0) } - get hasFreeShipping() { - switch(this.price.value) { - case this.TWELVE_OZ: - if (parseInt(this.total_qty) >= this.TWELVE_SHIPPING) { - return true - } else { - return false - } - break - case this.SIXTEEN_OZ: - if (parseInt(this.total_qty) >= this.SIXTEEN_SHIPPING) { - return true - } else { - return false - } - break - case this.FIVE_LBS: - if (parseInt(this.total_qty) >= this.FIVE_SHIPPING) { - return true - } else { - return false - } - break - default: - throw 'Something is wrong with the price' - } - } - - get countToFreeShipping() { - switch(this.price.value) { - case this.TWELVE_OZ: - return this.TWELVE_SHIPPING - this.total_qty - break - case this.SIXTEEN_OZ: - return this.SIXTEEN_SHIPPING - this.total_qty - break - case this.FIVE_LBS: - return this.FIVE_SHIPPING - break - default: - throw 'Something is wrong with the price' - break - } - } - - get shippingStatus() { - let items = 0 - - if (this.hasFreeShipping) { - return 'You have free shipping!' - } else { - return `Add ${this.countToFreeShipping} more item(s) for free shipping!` - } - - } - - get totalRetailPrice() { - let totalPrice = Array.from(this.products).reduce((total, current) => { - return total + (Number(this.price.value) * current.value); - }, 0); - - return new Intl.NumberFormat('en-US', { - currency: 'USD', - style: 'currency', - }).format(totalPrice) - } - - get totalPrice() { - let totalPrice = Array.from(this.products).reduce((total, current) => { - return total + (Number(this.price.value) * current.value); - }, 0); - - let percentage = (this.shippingDiscount / 100) * totalPrice - - - return new Intl.NumberFormat('en-US', { - currency: 'USD', - style: 'currency', - }).format(totalPrice - percentage) - } - - render() { - this.output.querySelector('.retail-price').innerText = this.totalRetailPrice - this.output.querySelector('.price').innerText = this.totalPrice - this.output.querySelector('.shipping').innerText = this.shippingStatus - } - - add_item(item) { - this.items.push(item) - return this.items - } - - createSubscription() { - fetch('/create-subscription', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - priceId: priceId, - customerId: customerId, - }), - }) + get total_weight() { + const weight = this.total_qty * Number(this.weight_per_item) + return `${weight}:${this.weight_unit}` } } - -const subCreateFromEl = document.querySelector('.subscription-create-form') -const sub = new Subscription(subCreateFromEl) +document.addEventListener('DOMContentLoaded', () => { + new SubscriptionForm('.subscription-create-form') +}) diff --git a/src/static/scripts/subscriptions_old.js b/src/static/scripts/subscriptions_old.js new file mode 100644 index 0000000..4aaa143 --- /dev/null +++ b/src/static/scripts/subscriptions_old.js @@ -0,0 +1,154 @@ +class SubscriptionForm { + TWELVE_OZ = '12' + SIXTEEN_OZ = '16' + FIVE_LBS = '75' + + TWELVE_SHIPPING = 7 + SIXTEEN_SHIPPING = 5 + FIVE_SHIPPING = 1 + max_quantity = 20 + + form = null + products = null + output = null + price = null + productsAndQuantities = null + + constructor(form, output) { + + this.form = form + this.productsAndQuantities = this.form.querySelector('[name=products_quantities]') + this.output = this.form.querySelector('.output') + this.shippingDiscount = 10 + this.price = this.form.querySelector('select[name=size]') + this.products = this.form.querySelectorAll('input[name^=product_]') + this.form.addEventListener('change', this.render.bind(this)) + this.render() + } + + get total_qty() { + return Array.from(this.products).reduce((total, current) => { + return total + Number(current.value) + }, 0) + } + + get hasFreeShipping() { + switch(this.price.value) { + case this.TWELVE_OZ: + if (parseInt(this.total_qty) >= this.TWELVE_SHIPPING) { + return true + } else { + return false + } + break + case this.SIXTEEN_OZ: + if (parseInt(this.total_qty) >= this.SIXTEEN_SHIPPING) { + return true + } else { + return false + } + break + case this.FIVE_LBS: + if (parseInt(this.total_qty) >= this.FIVE_SHIPPING) { + return true + } else { + return false + } + break + default: + throw 'Something is wrong with the price' + } + } + + get countToFreeShipping() { + switch(this.price.value) { + case this.TWELVE_OZ: + return this.TWELVE_SHIPPING - this.total_qty + break + case this.SIXTEEN_OZ: + return this.SIXTEEN_SHIPPING - this.total_qty + break + case this.FIVE_LBS: + return this.FIVE_SHIPPING + break + default: + throw 'Something is wrong with the price' + break + } + } + + get shippingStatus() { + let items = 0 + + if (this.hasFreeShipping) { + return 'You have free shipping!' + } else { + return `Add ${this.countToFreeShipping} more item(s) for free shipping!` + } + } + + get totalRetailPrice() { + let totalPrice = Array.from(this.products).reduce((total, current) => { + return total + (Number(this.price.value) * current.value); + }, 0); + + return new Intl.NumberFormat('en-US', { + currency: 'USD', + style: 'currency', + }).format(totalPrice) + } + + get totalPrice() { + let totalPrice = Array.from(this.products).reduce((total, current) => { + return total + (Number(this.price.value) * current.value); + }, 0); + + let percentage = (this.shippingDiscount / 100) * totalPrice + + return new Intl.NumberFormat('en-US', { + currency: 'USD', + style: 'currency', + }).format(totalPrice - percentage) + } + + render(event) { + this.output.querySelector('.retail-price').innerText = this.totalRetailPrice + this.output.querySelector('.price').innerText = this.totalPrice + this.output.querySelector('.shipping').innerText = this.shippingStatus + this.updateSelected() + if (this.total_qty < this.max_quantity) { + Array.from(this.products).map(input => input.max = this.max_quantity) + } else { + Array.from(this.products).map(input => { + if (input.value == '') { + input.max = 0 + } else { + input.max = input.value + } + }) + } + } + + updateSelected() { + const selected = Array.from(this.products).filter(item => item.value > 0) + this.productsAndQuantities.value = selected.map(item => `${item.name.slice(8)}:${item.value}`).join(',') + console.log(this.productsAndQuantities.value) + } + + createSubscription() { + fetch('/create-subscription', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + priceId: priceId, + customerId: customerId, + }), + }) + } +} + +document.addEventListener('DOMContentLoaded', () => { + new SubscriptionForm(document.querySelector('.subscription-create-form')) +}) diff --git a/src/static/styles/main.css b/src/static/styles/main.css index 5107539..0fa2aa1 100644 --- a/src/static/styles/main.css +++ b/src/static/styles/main.css @@ -210,6 +210,13 @@ input[type=submit]:hover, background-color: var(--yellow-alt-color); } +button:disabled, +input[type=submit]:disabled, +.action-button:disabled { + background-color: var(--yellow-alt-color); + cursor: no-drop; +} + .errorlist { background-color: var(--red-color); color: white; @@ -756,25 +763,53 @@ article + article { } -.subscription-create-form { - display: grid; - grid-template-columns: 2fr 1fr; +.subscription-authenticate { + text-align: center; +} + +.subscription-create-form section { + margin-bottom: 3rem; +} + + +.subscription-coffee { + display: flex; gap: 2rem; + overflow-x: scroll; + padding: 1rem; + border-right: var(--default-border); + border-left: var(--default-border); } -.product__subscription-list { - display: grid; - grid-template-columns: repeat(2, 1fr); +.subscription-coffee div { + text-align: center; +} + +.subscription-coffee img { + max-height: 300px; +} + +.subscription-products { + display: flex; + gap: 2rem; + margin-bottom: 1rem; justify-items: center; - gap: 6rem; - overflow-y: scroll; - max-height: 50rem; - border-bottom: var(--default-border); - padding: 0 2rem 2rem; } -.product__subscription-list div { - /*max-width: 10rem;*/ +.subscription-products > div { + border: var(--default-border); + width: 100%; + box-sizing: border-box; + padding: 1rem; + text-align: center; +} + +.subscription-products .product-prices { + visibility: hidden; +} + +.subscription-products input[type=radio]:checked ~ .product-prices { + visibility: visible; } diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 4f72b81..2c7654e 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -24,10 +24,9 @@ from core import ( ShippingService, ShippingProvider, ShippingContainer, - CoffeeGrind, - build_usps_rate_request + CoffeeGrind ) - +from core.usps import build_usps_rate_request from .forms import CartItemUpdateForm from .payments import CreateOrder @@ -134,10 +133,6 @@ class Cart: } def deserialize(self, data): - # Transform old cart - if type(data) is list: - return - try: self.coupon = Coupon.objects.get(code=data.get('coupon_code')) except Coupon.DoesNotExist: @@ -220,16 +215,19 @@ class Cart: if item.variant.product in self.coupon.products.all(): yield item.total_price - def get_shipping_container_choices(self): + def get_shipping_container_choices(self, total_weight=None): + if total_weight is None: + total_weight = self.total_weight + is_selectable = Q( is_selectable=True ) min_weight_matched = Q( - min_order_weight__lte=self.total_weight) | Q( + min_order_weight__lte=total_weight) | Q( min_order_weight__isnull=True ) max_weight_matched = Q( - max_order_weight__gte=self.total_weight) | Q( + max_order_weight__gte=total_weight) | Q( max_order_weight__isnull=True ) containers = ShippingRate.objects.filter( diff --git a/src/storefront/forms.py b/src/storefront/forms.py index 94886b9..d1d4454 100644 --- a/src/storefront/forms.py +++ b/src/storefront/forms.py @@ -10,8 +10,9 @@ from django.core.exceptions import ValidationError from localflavor.us.us_states import USPS_CHOICES from usps import USPSApi, Address from captcha.fields import CaptchaField +from django_measurement.forms import MeasurementField -from core.models import Order, ProductVariant +from core.models import Order, ProductVariant, Subscription from core import CoffeeGrind, ShippingContainer logger = logging.getLogger(__name__) @@ -39,21 +40,6 @@ class CartItemUpdateForm(forms.Form): quantity = forms.IntegerField(min_value=1, max_value=20, initial=1) -class AddToSubscriptionForm(forms.Form): - SEVEN_DAYS = 7 - FOURTEEN_DAYS = 14 - THIRTY_DAYS = 30 - SCHEDULE_CHOICES = [ - (SEVEN_DAYS, 'Every 7 days'), - (FOURTEEN_DAYS, 'Every 14 days'), - (THIRTY_DAYS, 'Every 30 days'), - ] - - quantity = forms.IntegerField(min_value=1, initial=1) - grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES) - schedule = forms.ChoiceField(choices=SCHEDULE_CHOICES) - - class AddressForm(forms.Form): full_name = forms.CharField() email = forms.EmailField() @@ -134,53 +120,20 @@ class CouponApplyForm(forms.Form): code = forms.CharField(label='Coupon code') -class ContactForm(forms.Form): - GOOGLE = 'Google Search' - SHOP = 'The coffee shop' - WOM = 'Word of mouth' - PRODUCT = 'Coffee Bag' - STORE = 'Store' - OTHER = 'Other' - - REFERAL_CHOICES = [ - (GOOGLE, 'Google Search'), - (SHOP, '"Better Living Through Coffee" coffee shop'), - (WOM, 'Friend/Relative'), - (PRODUCT, 'Our Coffee Bag'), - (STORE, 'PT Food Coop/other store'), - (OTHER, 'Other (please describe below)'), +class SubscriptionForm(forms.Form): + GRIND_CHOICES = [ + ('Whole Beans', 'Whole Beans'), + ('Espresso', 'Espresso'), + ('Cone Drip', 'Cone Drip'), + ('Basket Drip', 'Basket Drip'), + ('French Press', 'French Press'), + ('Stovetop Espresso (Moka Pot)', 'Stovetop Espresso (Moka Pot)'), + ('AeroPress', 'AeroPress'), + ('Percolator', 'Percolator'), + ('BLTC cafe pour over', 'BLTC cafe pour over') ] - - full_name = forms.CharField() - email_address = forms.EmailField() - referal = forms.ChoiceField( - label='How did you find our website?', - choices=REFERAL_CHOICES - ) - subject = forms.CharField() - message = forms.CharField(widget=forms.Textarea) - captcha = CaptchaField() - - -class SubscriptionCreateForm(forms.Form): - SEVEN_DAYS = 7 - FOURTEEN_DAYS = 14 - THIRTY_DAYS = 30 - SCHEDULE_CHOICES = [ - (SEVEN_DAYS, 'Every 7 days'), - (FOURTEEN_DAYS, 'Every 14 days'), - (THIRTY_DAYS, 'Every 30 days'), - ] - - TWELVE_OZ = 12 - SIXTEEN_OZ = 16 - FIVE_LBS = 75 - SIZE_CHOICES = [ - (TWELVE_OZ, '12 oz ($10.80)'), - (SIXTEEN_OZ, '16 oz ($14.40)'), - (FIVE_LBS, '5 lbs ($67.50)'), - ] - - grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES) - schedule = forms.ChoiceField(choices=SCHEDULE_CHOICES) - size = forms.ChoiceField(choices=SIZE_CHOICES) + grind = forms.ChoiceField(choices=GRIND_CHOICES, label='') + products_and_quantities = forms.CharField(widget=forms.HiddenInput()) + stripe_price_id = forms.CharField(widget=forms.HiddenInput()) + total_quantity = forms.IntegerField(widget=forms.HiddenInput()) + total_weight = forms.CharField(widget=forms.HiddenInput()) diff --git a/src/storefront/templates/storefront/sub_payment.html b/src/storefront/templates/storefront/sub_payment.html deleted file mode 100644 index b489747..0000000 --- a/src/storefront/templates/storefront/sub_payment.html +++ /dev/null @@ -1,112 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block head %} - - - -{% endblock %} - -{% block content %} -
-

Subscriptions

-

SUBSCRIBE AND SAVE

-
-
-
- -
- -
- -
- -
- -
-
- -{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/address.html b/src/storefront/templates/storefront/subscription/address.html new file mode 100644 index 0000000..888e9d9 --- /dev/null +++ b/src/storefront/templates/storefront/subscription/address.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load static %} + +{% block head_title %}Subscription | {% endblock %} + +{% block content %} +
+
+

Subscription

+
+
+

Shipping Address

+
+ {% csrf_token %} + {{form.as_p}} +

+ +

+
+

We validate addresses with USPS, if you are having issues please contact us at support@ptcoffee.com.

+
+
+{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/confirmation.html b/src/storefront/templates/storefront/subscription/confirmation.html new file mode 100644 index 0000000..8f7ff27 --- /dev/null +++ b/src/storefront/templates/storefront/subscription/confirmation.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+

Subscriptions

+

SUBSCRIBE AND SAVE

+
+
+

Review your subscription

+ + Shipping address + +
+
Items:
+
{{ subscription.items }}
+
Metadata:
+
{{ subscription.metadata }}
+
Address:
+
{{ subscription.shipping_address }}
+
Weight:
+
{{ subscription.total_weight }}
+
Created At:
+
{{ subscription.created_at }}
+
+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+ +{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/create_form.html b/src/storefront/templates/storefront/subscription/create_form.html new file mode 100644 index 0000000..78979a1 --- /dev/null +++ b/src/storefront/templates/storefront/subscription/create_form.html @@ -0,0 +1,77 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+

Subscriptions

+

SUBSCRIBE AND SAVE

+
+
+
+

Subscription

+
+
+

Shipping address

+

{{shipping_address.email}}

+
+ {{shipping_address.first_name}} + {{shipping_address.last_name}}
+ {{shipping_address.street_address_1}}
+ {% if shipping_address.street_address_2 %} + {{shipping_address.street_address_2}}
+ {% endif %} + {{shipping_address.city}}, {{shipping_address.state}}, {{shipping_address.postal_code}} +
+ Change +
+
+

Items

+

Size: {{ sub_cart.size }}

+

Grind: {{ sub_cart.grind }}

+ {% for item in sub_cart.items %} +
+ {% with product=item.product %} +
+ {{product.get_first_img.image}} +
+
+

{{ item.quantity }} × {{product.name}}

+
+ {% endwith %} +
+ {% endfor %} +
+
+

Subscription summary

+
+ + + + + + + + + + + + + + + + + +
Subtotal{{ sub_cart.subtotal_price }}
Shipping${{ sub_cart.shipping_cost }}
Total${{ sub_cart.total_price }}
Schedule{{ sub_cart.schedule }}
+
+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+
+{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/done.html b/src/storefront/templates/storefront/subscription/done.html new file mode 100644 index 0000000..b6a6f66 --- /dev/null +++ b/src/storefront/templates/storefront/subscription/done.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+

Subscriptions

+

SUBSCRIBE AND SAVE

+
+
+
+

Success! Subscription created.

+
+
+{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/form.html b/src/storefront/templates/storefront/subscription/form.html new file mode 100644 index 0000000..80d62cd --- /dev/null +++ b/src/storefront/templates/storefront/subscription/form.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + +{% endblock %} + +{% block content %} +
+

Subscriptions

+

SUBSCRIBE AND SAVE

+
+
+
+
+ {% csrf_token %} +

Pick your coffee:

+
+ {% for product in product_list %} +
+ + + +
+ {% endfor %} +
+

Pick your grind:

+
+ {{ form.as_p }} +
+

Pick your size:

+
+ {% for product in stripe_products %} +
+ +
+
{{ product.cost }} / bag
+
+
Pick your Schedule:
+ +
+
+ {% endfor %} +
+
+
+

+ +

+
+
+
+
+{% endblock %} diff --git a/src/storefront/templates/storefront/subscription/payment.html b/src/storefront/templates/storefront/subscription/payment.html new file mode 100644 index 0000000..99e4599 --- /dev/null +++ b/src/storefront/templates/storefront/subscription/payment.html @@ -0,0 +1,66 @@ +{% extends 'base.html' %} +{% load static %} + +{% block head %} + + +{% endblock %} + +{% block content %} +
+

Subscriptions

+

SUBSCRIBE AND SAVE

+
+
+
+
+ +
+ +
+ +
+
+
+ +{% endblock %} diff --git a/src/storefront/templates/storefront/subscriptions.html b/src/storefront/templates/storefront/subscriptions.html index a0eac8c..73ffb92 100644 --- a/src/storefront/templates/storefront/subscriptions.html +++ b/src/storefront/templates/storefront/subscriptions.html @@ -14,24 +14,25 @@
- {% for product in stripe_products %} - {{ product }} - {% endfor %} -
-
-
+ {% csrf_token %}

Pick your coffee

{% for product in product_list %}
-
{% endfor %} diff --git a/src/storefront/urls.py b/src/storefront/urls.py index 5f626f1..5443db6 100644 --- a/src/storefront/urls.py +++ b/src/storefront/urls.py @@ -5,11 +5,6 @@ urlpatterns = [ path('about/', views.AboutView.as_view(), name='about'), path('fair-trade/', views.FairTradeView.as_view(), name='fair-trade'), path('reviews/', views.ReviewListView.as_view(), name='reviews'), - path( - 'subscriptions/', - views.SubscriptionCreateView.as_view(), - name='subscriptions' - ), path( 'categories//', @@ -104,4 +99,50 @@ urlpatterns = [ name='address-update', ) ])), + + path( + 'stripe-webhook/', + views.stripe_webhook, + name='stripe-webhook' + ), + # Subscriptions + path('subscriptions/', include([ + path( + 'form/', + views.SubscriptionFormView.as_view(), + name='subscription-form' + ), + path( + 'address/', + views.SubscriptionAddAddressView.as_view(), + name='subscription-address' + ), + path( + 'new/', + views.SubscriptionCreateView.as_view(), + name='subscription-create' + ), + path( + 'done/', + views.SubscriptionDoneView.as_view(), + name='subscription-done' + ), + path('/', include([ + path( + '', + views.SubscriptionDetailView.as_view(), + name='subscription-detail' + ), + path( + 'update/', + views.SubscriptionUpdateView.as_view(), + name='subscription-update' + ), + path( + 'delete/', + views.SubscriptionDeleteView.as_view(), + name='subscription-delete' + ), + ])), + ])), ] diff --git a/src/storefront/views.py b/src/storefront/views.py index 7134324..070943d 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -1,13 +1,16 @@ import logging +import locale import requests import json import stripe +from decimal import Decimal from django.conf import settings from django.utils import timezone from django.shortcuts import render, reverse, redirect, get_object_or_404 from django.urls import reverse_lazy from django.core.mail import EmailMessage from django.core.cache import cache +from django.contrib.sites.models import Site from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.http import JsonResponse, HttpResponseRedirect from django.views.generic.base import View, RedirectView, TemplateView @@ -26,7 +29,8 @@ from django.forms.models import model_to_dict from django.db.models import ( Exists, OuterRef, Prefetch, Subquery, Count, Sum, Avg, F, Q, Value ) - +from measurement.measures import Weight +from measurement.utils import guess from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment @@ -38,20 +42,23 @@ from accounts.forms import ( from core.models import ( ProductCategory, Product, ProductVariant, ProductOption, Order, Transaction, OrderLine, Coupon, ShippingRate, - SiteSettings + Subscription, SiteSettings ) from core.forms import ShippingRateForm +from core.shipping import get_shipping_cost from core import OrderStatus, ShippingContainer from .forms import ( AddToCartForm, CartItemUpdateForm, OrderCreateForm, - AddressForm, CouponApplyForm, ContactForm, CheckoutShippingForm, - SubscriptionCreateForm + AddressForm, CouponApplyForm, CheckoutShippingForm, + SubscriptionForm ) from .cart import CartItem, Cart from .payments import CaptureOrder logger = logging.getLogger(__name__) +locale.setlocale(locale.LC_ALL, '') +stripe.api_key = settings.STRIPE_API_KEY class CartView(FormView): @@ -365,7 +372,7 @@ class OrderCreateView(CreateView): shipping_container = cart.get_shipping_container() form.instance.shipping_total = cart.get_shipping_price(shipping_container) shipping_address = self.request.session.get('shipping_address') - form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address) + form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, shipping_address) form.instance.status = OrderStatus.DRAFT self.object = form.save() bulk_list = cart.build_bulk_list(self.object) @@ -379,57 +386,6 @@ class OrderCreateView(CreateView): return JsonResponse(data) -# stripe listen --forward-to localhost:8000/stripe-webhook -@csrf_exempt -@require_POST -def stripe_webhook(request): - # You can use webhooks to receive information about asynchronous payment events. - # For more about our webhook events check out https://stripe.com/docs/webhooks. - webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET') - request_data = json.loads(request.data) - - if webhook_secret: - # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. - signature = request.headers.get('stripe-signature') - try: - event = stripe.Webhook.construct_event( - payload=request.data, sig_header=signature, secret=webhook_secret) - data = event['data'] - except Exception as e: - return e - # Get the type of webhook event sent - used to check the status of PaymentIntents. - event_type = event['type'] - else: - data = request_data['data'] - event_type = request_data['type'] - - data_object = data['object'] - - if event_type == 'invoice.paid': - # Used to provision services after the trial has ended. - # The status of the invoice will show up as paid. Store the status in your - # database to reference when a user accesses your service to avoid hitting rate - # limits. - messages.success(request, 'Paid') - logger.warning(data) - - if event_type == 'invoice.payment_failed': - # If the payment fails or the customer does not have a valid payment method, - # an invoice.payment_failed event is sent, the subscription becomes past_due. - # Use this webhook to notify your user that their payment has - # failed and to retrieve new card details. - messages.warning(request, 'Payment failed') - logger.warning(data) - - if event_type == 'customer.subscription.deleted': - # handle subscription canceled automatically based - # upon your subscription settings. Or if the user cancels it. - messages.error(request, 'Deleted') - logger.warning(data) - - return jsonify({'status': 'success'}) - - @csrf_exempt @require_POST def paypal_order_transaction_capture(request, transaction_id): @@ -584,64 +540,338 @@ class ReviewListView(TemplateView): template_name = 'storefront/reviews.html' -class SubscriptionCreateView(FormView): - template_name = 'storefront/subscriptions.html' - form_class = SubscriptionCreateForm - success_url = reverse_lazy('storefront:payment-done') +class SubscriptionFormView(FormView): + template_name = 'storefront/subscription/form.html' + form_class = SubscriptionForm + success_url = reverse_lazy('storefront:subscription-address') + + def get_stripe_products(self): + # id, name + product_list = [{ + 'id': product['id'], + 'name': product['name'], + 'created': product['created'], + 'weight_per_item': product['metadata']['weight_per_item'], + 'cost': product['metadata']['cost'], + 'prices': [] + } for product in stripe.Product.list(active=True)] + + # id, product, recurring.interval_count, recurring.interval, unit_amount + price_list = [{ + 'id': price['id'], + 'product': price['product'], + 'interval_count': price.recurring.interval_count, + 'interval': price.recurring.interval, + 'unit_amount': price['unit_amount'] + } for price in stripe.Price.list(active=True)] + + for prod in product_list: + prod['prices'] = list(filter( + lambda p: True if p['product'] == prod['id'] else False, + price_list + )) + return sorted(product_list, key=lambda p: p['created']) def get_context_data(self, *args, **kwargs): - stripe.api_key = settings.STRIPE_API_KEY context = super().get_context_data(*args, **kwargs) - context['STRIPE_API_KEY'] = settings.STRIPE_API_KEY + context['stripe_products'] = self.get_stripe_products() context['product_list'] = Product.objects.filter( - visible_in_listings=True + visible_in_listings=True, + category__name='Coffee' ) - context['stripe_products'] = stripe.Price.list() return context def form_valid(self, form): - # TODO: Construct items element - items = [] - subscription = self.create_subscription(items) + self.request.session['subscription'] = { + 'items': [{ + 'price': form.cleaned_data['stripe_price_id'], + 'quantity': form.cleaned_data['total_quantity'] + }], + 'metadata': { + 'grind': form.cleaned_data['grind'], + 'total_weight': form.cleaned_data['total_weight'], + 'products_and_quantities': json.loads(form.cleaned_data['products_and_quantities']) + } + } return super().form_valid(form) - def create_subscription(self, items): - # items=[{ - # 'price': price_id, - # 'quantity': quantity - # }, { - # 'price': next_price_id, - # 'quantity': quantity - # }] - try: - # Create the subscription. Note we're expanding the Subscription's - # latest invoice and that invoice's payment_intent - # so we can pass it to the front end to confirm the payment - subscription = stripe.Subscription.create( - customer=self.request.user.stripe_id, - items=items, - payment_behavior='default_incomplete', - payment_settings={'save_default_payment_method': 'on_subscription'}, - expand=['latest_invoice.payment_intent'], - ) - # TODO: pass this secret to the sub_payment.html as 'CLIENT_SECRET' - # clientSecret=subscription.latest_invoice.payment_intent.client_secret - return subscription - except Exception as e: - return messages.error(self.request, e.user_message) +class SubscriptionAddAddressView(FormView): + template_name = 'storefront/subscription/address.html' + form_class = AddressForm + success_url = reverse_lazy('storefront:subscription-create') + def get_initial(self): + user = self.request.user + initial = None + if user.is_authenticated and user.default_shipping_address: + address = user.default_shipping_address + initial = { + 'full_name': address.first_name + ' ' + address.last_name, + 'email': user.email, + 'street_address_1': address.street_address_1, + 'street_address_2': address.street_address_2, + 'city': address.city, + 'state': address.state, + 'postal_code': address.postal_code + } + elif self.request.session.get('shipping_address'): + address = self.request.session.get('shipping_address') + initial = { + 'full_name': address['first_name'] + ' ' + address['last_name'], + 'email': address['email'], + 'street_address_1': address['street_address_1'], + 'street_address_2': address['street_address_2'], + 'city': address['city'], + 'state': address['state'], + 'postal_code': address['postal_code'] + } + return initial -class CreatePayment(View): - def post(self, request, *args, **kwargs): - stripe.api_key = settings.STRIPE_API_KEY - intent = stripe.PaymentIntent.create( - amount=2000, - currency=settings.DEFAULT_CURRENCY, - automatic_payment_methods={ - 'enabled': True, - }, + def form_valid(self, form): + # save address data to session + cleaned_data = form.cleaned_data + first_name, last_name = form.process_full_name( + cleaned_data.get('full_name') ) - return JsonResponse({ - 'clientSecret': intent['client_secret'] + address = { + 'first_name': first_name, + 'last_name': last_name, + 'email': cleaned_data['email'], + 'street_address_1': cleaned_data['street_address_1'], + 'street_address_2': cleaned_data['street_address_2'], + 'city': cleaned_data['city'], + 'state': cleaned_data['state'], + 'postal_code': cleaned_data['postal_code'] + } + self.request.session['shipping_address'] = address + return super().form_valid(form) + + +class SubscriptionCreateView(SuccessMessageMixin, CreateView): + model = Subscription + success_message = 'Subscription created.' + template_name = 'storefront/subscription/create_form.html' + fields = [] + + def get_item_list(self): + item_list = [{ + 'product': Product.objects.get(pk=item['pk']), + 'quantity': item['quantity'] + } for item in self.request.session['subscription']['metadata']['products_and_quantities']] + return item_list + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + subscription = self.request.session['subscription'] + metadata = subscription['metadata'] + price = stripe.Price.retrieve( + subscription['items'][0].get('price'), + expand=['product'] + ) + shipping_address = self.request.session.get('shipping_address') + weight, unit = metadata['total_weight'].split(':') + total_weight = guess(float(weight), unit, measures=[Weight]) + subtotal_price = (Decimal(price.unit_amount) * subscription['items'][0]['quantity']) / 100 + shipping_cost = get_shipping_cost( + total_weight, + shipping_address['postal_code'] + ) + total_price = subtotal_price + shipping_cost + context['sub_cart'] = { + 'items': self.get_item_list(), + 'size': price.product.name, + 'grind': metadata['grind'], + 'schedule': f'Every {price.recurring.interval_count} / {price.recurring.interval}', + 'subtotal_price': locale.currency(subtotal_price), + 'shipping_cost': shipping_cost, + 'total_price': total_price, + 'total_weight': guess(float(weight), unit, measures=[Weight]) + } + context['shipping_address'] = shipping_address + return context + + def get_line_items(self): + line_items = self.object.items + recurring = stripe.Price.retrieve( + line_items[0].get('price') + ).get('recurring') + shipping_cost = get_shipping_cost( + self.object.total_weight, + self.object.shipping_address.postal_code + ) * 100 + line_items.append({ + 'price_data': { + 'currency': settings.DEFAULT_CURRENCY.lower(), + 'unit_amount': int(shipping_cost), + 'product_data': { + 'name': 'Shipping' + }, + 'recurring': { + 'interval': recurring.interval, + 'interval_count': recurring.interval_count + } + }, + 'quantity': 1 }) + return line_items + + def get_success_url(self): + session = stripe.checkout.Session.create( + customer=self.object.customer.get_or_create_stripe_id(), + success_url='http://' + Site.objects.get_current().domain + reverse( + 'storefront:subscription-detail', kwargs={'pk': self.object.pk} + ) + '?session_id={CHECKOUT_SESSION_ID}', + cancel_url='http://' + Site.objects.get_current().domain + reverse( + 'storefront:subscription-create'), + mode='subscription', + line_items=self.get_line_items(), + subscription_data={'metadata': self.object.format_metadata()} + ) + return session.url + + def form_valid(self, form): + shipping_address = self.request.session.get('shipping_address') + subscription = self.request.session.get('subscription') + form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, shipping_address) + weight, unit = subscription['metadata']['total_weight'].split(':') + form.instance.total_weight = guess( + float(weight), unit, measures=[Weight] + ) + form.instance.items = subscription['items'] + form.instance.metadata = subscription['metadata'] + return super().form_valid(form) + + +class SubscriptionDetailView(DetailView): + model = Subscription + template_name = 'storefront/subscription/detail.html' + + +class SubscriptionDoneView(TemplateView): + template_name = 'storefront/subscription/done.html' + + +class SubscriptionUpdateView(SuccessMessageMixin, UpdateView): + model = Subscription + success_message = 'Subscription saved.' + fields = '__all__' + + +class SubscriptionDeleteView(SuccessMessageMixin, DeleteView): + model = Subscription + success_message = 'Subscription deleted.' + success_url = reverse_lazy('subscription-list') + + +# stripe listen --forward-to localhost:8000/stripe-webhook +@csrf_exempt +@require_POST +def stripe_webhook(request): + # You can use webhooks to receive information about asynchronous payment events. + # For more about our webhook events check out https://stripe.com/docs/webhooks. + webhook_secret = None + request_data = json.loads(request.body) + + if webhook_secret: + # Retrieve the event by verifying the signature using the raw body + # and secret if webhook signing is configured. + signature = request.headers.get('stripe-signature') + try: + event = stripe.Webhook.construct_event( + payload=request.data, sig_header=signature, secret=webhook_secret) + data = event['data'] + except Exception as e: + return e + # Get the type of webhook event sent - used to check the status + # of PaymentIntents. + event_type = event['type'] + else: + data = request_data['data'] + event_type = request_data['type'] + + data_object = data['object'] + + logger.warning('\n') + logger.warning(event_type.upper() + ':\n') + logger.warning(data) + logger.warning('\n') + + if event_type == 'checkout.session.completed': + # Payment is successful and the subscription is created. + # You should provision the subscription and save the customer ID to your database. + pass + + if event_type == 'customer.subscription.created': + try: + subscription = Subscription.objects.get( + pk=data_object['metadata'].get('subscription_pk') + ) + except Subscription.DoesNotExist: + logger.warning('Subscription does not exist') + raise + else: + subscription.stripe_id = data_object['id'] + subscription.is_active = True + subscription.save() + + if event_type == 'invoice.paid': + # Continue to provision the subscription as payments continue to be made. + # Store the status in your database and check when a user accesses your service. + # This approach helps you avoid hitting rate limits. + try: + subscription = Subscription.objects.get( + stripe_id=data_object['subscription'] + ) + except Subscription.DoesNotExist: + logger.warning('Subscription does not exist') + raise + else: + subscription.create_order(data_object) + + if event_type == 'invoice.payment_failed': + # The payment failed or the customer does not have a valid payment method. + # The subscription becomes past_due. Notify your customer and send them to the + # customer portal to update their payment information. + pass + + if event_type == 'invoice.created': + # Add shipping cost as an item on the invoice + # shipping_cost = get_shipping_cost( + # self.object.total_weight, + # self.request.user.default_shipping_address.postal_code + # ) * 100 + # stripe.InvoiceItem.create( + # customer=data_object['customer'], + # subscription=data_object['subscription'], + # description='Shipping', + # unit_amount=1234, + # currency=settings.DEFAULT_CURRENCY.lower() + # ) + pass + + # # if event_type == 'checkout.session.completed': + + # if event_type == 'invoice.paid': + # # Used to provision services after the trial has ended. + # # The status of the invoice will show up as paid. Store the status in your + # # database to reference when a user accesses your service to avoid hitting rate + # # limits. + # messages.success(request, 'Paid') + # logger.warning(data) + + # if event_type == 'invoice.payment_failed': + # # If the payment fails or the customer does not have a valid payment method, + # # an invoice.payment_failed event is sent, the subscription becomes past_due. + # # Use this webhook to notify your user that their payment has + # # failed and to retrieve new card details. + # messages.warning(request, 'Payment failed') + # logger.warning(data) + + # if event_type == 'customer.subscription.deleted': + # # handle subscription canceled automatically based + # # upon your subscription settings. Or if the user cancels it. + # messages.error(request, 'Deleted') + # logger.warning(data) + + return JsonResponse({'status': 'success'}) diff --git a/src/templates/base.html b/src/templates/base.html index 7de20d5..4b9a646 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -49,7 +49,7 @@ {% for category in category_list %}
  • {{ category }}
  • {% endfor %} - +
  • Subscriptions
  • Fair trade
  • Reviews
  • About
  • diff --git a/src/templates/dashboard.html b/src/templates/dashboard.html index 0f6734a..5c1a0b8 100644 --- a/src/templates/dashboard.html +++ b/src/templates/dashboard.html @@ -41,6 +41,10 @@ Orders + + + Subscriptions + Customers diff --git a/subscription_object.py b/subscription_object.py new file mode 100644 index 0000000..abf1fef --- /dev/null +++ b/subscription_object.py @@ -0,0 +1,225 @@ +data_object: { + "id": "in_1MGQM8FQsgcNaCv6JUfpAHbM", + "object": "invoice", + "account_country": "US", + "account_name": "nathanchapman", + "account_tax_ids": None, + "amount_due": 5355, + "amount_paid": 5355, + "amount_remaining": 0, + "application": None, + "application_fee_amount": None, + "attempt_count": 1, + "attempted": True, + "auto_advance": False, + "automatic_tax": {"enabled": False, "status": None}, + "billing_reason": "subscription_create", + "charge": "ch_3MGQM9FQsgcNaCv613M8vu43", + "collection_method": "charge_automatically", + "created": 1671383336, + "currency": "usd", + "custom_fields": None, + "customer": "cus_N0BNLHLuTQJRu4", + "customer_address": None, + "customer_email": "contact@nathanjchapman.com", + "customer_name": "Nathan JChapman", + "customer_phone": None, + "customer_shipping": { + "address": { + "city": "Logan", + "country": None, + "line1": "1579 Talon Dr", + "line2": "", + "postal_code": "84321", + "state": "UT", + }, + "name": "Nathan Chapman", + "phone": None, + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": None, + "default_source": None, + "default_tax_rates": [], + "description": None, + "discount": None, + "discounts": [], + "due_date": None, + "ending_balance": 0, + "footer": None, + "from_invoice": None, + "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1GLBrZFQsgcNaCv6/test_YWNjdF8xR0xCclpGUXNnY05hQ3Y2LF9OMFJFcDhJc3RyOXROMG1DeUNIRFVUVkdGMVNrRTRGLDYxOTI0MTM50200B7VnLQmS?s=ap", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1GLBrZFQsgcNaCv6/test_YWNjdF8xR0xCclpGUXNnY05hQ3Y2LF9OMFJFcDhJc3RyOXROMG1DeUNIRFVUVkdGMVNrRTRGLDYxOTI0MTM50200B7VnLQmS/pdf?s=ap", + "last_finalization_error": None, + "latest_revision": None, + "lines": { + "object": "list", + "data": [ + { + "id": "il_1MGQM5FQsgcNaCv6baOBoXDl", + "object": "line_item", + "amount": 1035, + "amount_excluding_tax": 1035, + "currency": "usd", + "description": "Shipping", + "discount_amounts": [], + "discountable": True, + "discounts": [], + "invoice_item": "ii_1MGQM5FQsgcNaCv6UjIu6jMT", + "livemode": False, + "metadata": {}, + "period": {"end": 1671383333, "start": 1671383333}, + "plan": None, + "price": { + "id": "price_1MGB0tFQsgcNaCv62dtt2BHB", + "object": "price", + "active": False, + "billing_scheme": "per_unit", + "created": 1671324359, + "currency": "usd", + "custom_unit_amount": None, + "livemode": False, + "lookup_key": None, + "metadata": {}, + "nickname": None, + "product": "prod_N0BN5Idzj7DdEj", + "recurring": None, + "tax_behavior": "unspecified", + "tiers_mode": None, + "transform_quantity": None, + "type": "one_time", + "unit_amount": 1035, + "unit_amount_decimal": "1035", + }, + "proration": False, + "proration_details": {"credited_items": None}, + "quantity": 1, + "subscription": None, + "tax_amounts": [], + "tax_rates": [], + "type": "invoiceitem", + "unit_amount_excluding_tax": "1035", + }, + { + "id": "il_1MGQM8FQsgcNaCv65ey9uwKi", + "object": "line_item", + "amount": 4320, + "amount_excluding_tax": 4320, + "currency": "usd", + "description": "3 × 16 oz Coffee (at $14.40 / month)", + "discount_amounts": [], + "discountable": True, + "discounts": [], + "livemode": False, + "metadata": { + "grind": '"Espresso"', + "total_weight": '"48:oz"', + "products_and_quantities": '[{"product": "Pantomime", "quantity": 2}, {"product": "Decaf", "quantity": 1}]', + }, + "period": {"end": 1674061736, "start": 1671383336}, + "plan": { + "id": "price_1MG7aEFQsgcNaCv6DZZoF2xG", + "object": "plan", + "active": True, + "aggregate_usage": None, + "amount": 1440, + "amount_decimal": "1440", + "billing_scheme": "per_unit", + "created": 1671311174, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": False, + "metadata": {}, + "nickname": None, + "product": "prod_N07pP13dnWszHN", + "tiers": None, + "tiers_mode": None, + "transform_usage": None, + "trial_period_days": None, + "usage_type": "licensed", + }, + "price": { + "id": "price_1MG7aEFQsgcNaCv6DZZoF2xG", + "object": "price", + "active": True, + "billing_scheme": "per_unit", + "created": 1671311174, + "currency": "usd", + "custom_unit_amount": None, + "livemode": False, + "lookup_key": None, + "metadata": {}, + "nickname": None, + "product": "prod_N07pP13dnWszHN", + "recurring": { + "aggregate_usage": None, + "interval": "month", + "interval_count": 1, + "trial_period_days": None, + "usage_type": "licensed", + }, + "tax_behavior": "exclusive", + "tiers_mode": None, + "transform_quantity": None, + "type": "recurring", + "unit_amount": 1440, + "unit_amount_decimal": "1440", + }, + "proration": False, + "proration_details": {"credited_items": None}, + "quantity": 3, + "subscription": "sub_1MGQM8FQsgcNaCv61HhjRVJu", + "subscription_item": "si_N0REI0MTk1C3D2", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription", + "unit_amount_excluding_tax": "1440", + }, + ], + "has_more": False, + "total_count": 2, + "url": "/v1/invoices/in_1MGQM8FQsgcNaCv6JUfpAHbM/lines", + }, + "livemode": False, + "metadata": {}, + "next_payment_attempt": None, + "number": "86494117-0006", + "on_behalf_of": None, + "paid": True, + "paid_out_of_band": False, + "payment_intent": "pi_3MGQM9FQsgcNaCv61W7mCS0C", + "payment_settings": { + "default_mandate": None, + "payment_method_options": None, + "payment_method_types": None, + }, + "period_end": 1671383336, + "period_start": 1671383336, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "quote": None, + "receipt_number": None, + "rendering_options": None, + "starting_balance": 0, + "statement_descriptor": None, + "status": "paid", + "status_transitions": { + "finalized_at": 1671383336, + "marked_uncollectible_at": None, + "paid_at": 1671383338, + "voided_at": None, + }, + "subscription": "sub_1MGQM8FQsgcNaCv61HhjRVJu", + "subtotal": 5355, + "subtotal_excluding_tax": 5355, + "tax": None, + "tax_percent": None, + "test_clock": None, + "total": 5355, + "total_discount_amounts": [], + "total_excluding_tax": 5355, + "total_tax_amounts": [], + "transfer_data": None, + "webhooks_delivered_at": None, +}