diff --git a/.gitignore b/.gitignore index fd96dd2..3745c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -__pycache__ -/cmsmanage/settings/prod.py +__pycache__/ +*.sqlite3 +/__pypackages__/ diff --git a/.pdm.toml b/.pdm.toml new file mode 100644 index 0000000..0e88b87 --- /dev/null +++ b/.pdm.toml @@ -0,0 +1,5 @@ +[python] +path = "/usr/bin/python" + +[strategy] +save = "compatible" diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 7f67f97..0000000 --- a/Pipfile +++ /dev/null @@ -1,25 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -django = "~=3.2" -django-widget-tweaks = "~=1.4" -django-auth-ldap = "~=4.0" -django-markdownx = "~=3.0" -django-markdownify = "~=0.9" -uvicorn = "~=0.17" -mysqlclient = "~=2.1" -django-recurrence = "~=1.10" - -[dev-packages] -djlint = "~=0.7" -black = "*" -pre-commit = "*" - -[requires] -python_version = "3.9" - -[scripts] -manage = "python3 ./manage.py" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index b7cde6a..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,554 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "05bf4a81c16ace1a283eed7cb9c0c3890141eed9840be4b037fe8cc003895619" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "asgiref": { - "hashes": [ - "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", - "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" - ], - "markers": "python_version >= '3.7'", - "version": "==3.5.0" - }, - "bleach": { - "hashes": [ - "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da", - "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994" - ], - "markers": "python_version >= '3.6'", - "version": "==4.1.0" - }, - "click": { - "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" - }, - "django": { - "hashes": [ - "sha256:0a0a37f0b93aef30c4bf3a839c187e1175bcdeb7e177341da0cb7b8194416891", - "sha256:69c94abe5d6b1b088bf475e09b7b74403f943e34da107e798465d2045da27e75" - ], - "index": "pypi", - "version": "==3.2.11" - }, - "django-auth-ldap": { - "hashes": [ - "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239", - "sha256:94119c94981809124d3dc4bed974f71c7a980666896df626f556a88a5fe0b59c" - ], - "index": "pypi", - "version": "==4.0.0" - }, - "django-markdownify": { - "hashes": [ - "sha256:2d3e460a34fb4498c8f7a054e7c6d4d5f67cc0792f86331b2af2dc27f776b65c", - "sha256:b060eb7869f493f7bff390ba2f7a81b981dd6c5f163dbb95423b6cf714ca4d23" - ], - "index": "pypi", - "version": "==0.9.0" - }, - "django-markdownx": { - "hashes": [ - "sha256:e18e395cad0ade96afbb250a81cad15618e417ac3c0d9c37d964be3d8fd57bcf", - "sha256:f4d8998618c0548bf5349713d805e7440684d70116de0f10413932286c4e375f" - ], - "index": "pypi", - "version": "==3.0.1" - }, - "django-recurrence": { - "hashes": [ - "sha256:715f681f6af029ff3a8d73c7b1460abd8cbc5d5a5001efcb127032e84d9cb963", - "sha256:9053b44b78b7fbfe3530673edfdd6d2f562105f8a192bc6a4b906a3df4f95f59" - ], - "index": "pypi", - "version": "==1.10.3" - }, - "django-widget-tweaks": { - "hashes": [ - "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", - "sha256:fe6b17d5d595c63331f300917980db2afcf71f240ab9341b954aea8f45d25b9a" - ], - "index": "pypi", - "version": "==1.4.12" - }, - "h11": { - "hashes": [ - "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", - "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" - ], - "markers": "python_version >= '3.6'", - "version": "==0.13.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6", - "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e" - ], - "markers": "python_version < '3.10'", - "version": "==4.10.1" - }, - "markdown": { - "hashes": [ - "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006", - "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3" - ], - "markers": "python_version >= '3.6'", - "version": "==3.3.6" - }, - "mysqlclient": { - "hashes": [ - "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947", - "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c", - "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12", - "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b", - "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "pillow": { - "hashes": [ - "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6", - "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc", - "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52", - "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4", - "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af", - "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315", - "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4", - "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281", - "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb", - "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9", - "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128", - "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105", - "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553", - "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5", - "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d", - "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6", - "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100", - "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce", - "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd", - "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05", - "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f", - "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f", - "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7", - "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f", - "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762", - "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379", - "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee", - "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925", - "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f", - "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f", - "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e", - "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4" - ], - "markers": "python_version >= '3.7'", - "version": "==9.0.0" - }, - "pyasn1": { - "hashes": [ - "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", - "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", - "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", - "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", - "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", - "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", - "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", - "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", - "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", - "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", - "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", - "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", - "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" - ], - "version": "==0.4.8" - }, - "pyasn1-modules": { - "hashes": [ - "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", - "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", - "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", - "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", - "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", - "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", - "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", - "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", - "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", - "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", - "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", - "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", - "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" - ], - "version": "==0.2.8" - }, - "pyparsing": { - "hashes": [ - "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", - "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.7" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" - }, - "python-ldap": { - "hashes": [ - "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12" - ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "pytz": { - "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" - ], - "version": "==2021.3" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "sqlparse": { - "hashes": [ - "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", - "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" - ], - "markers": "python_version >= '3.5'", - "version": "==0.4.2" - }, - "uvicorn": { - "hashes": [ - "sha256:60a149248181920a73b2e97aec1dacec5501618867f041a228b2519d91a62a91", - "sha256:fa166e6c3d58e23ff5a1a3543b079c7b28aa057ab1388201e4b34a49ec05da72" - ], - "index": "pypi", - "version": "==0.17.0.post1" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "zipp": { - "hashes": [ - "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d", - "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375" - ], - "markers": "python_version >= '3.7'", - "version": "==3.7.0" - } - }, - "develop": { - "black": { - "hashes": [ - "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", - "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" - ], - "index": "pypi", - "version": "==21.12b0" - }, - "cfgv": { - "hashes": [ - "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", - "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" - ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.3.1" - }, - "click": { - "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" - }, - "colorama": { - "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.4.4" - }, - "distlib": { - "hashes": [ - "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", - "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" - ], - "version": "==0.3.4" - }, - "djlint": { - "hashes": [ - "sha256:68aad9ddfef883cc9d9e0d177387b74840af5ca12dcce6e4629eb7075c97dc05", - "sha256:714ed457e022047149c8bff57d5be00ce30f8846b60e866791c66d27e7d11e7f" - ], - "index": "pypi", - "version": "==0.7.3" - }, - "filelock": { - "hashes": [ - "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80", - "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146" - ], - "markers": "python_version >= '3.7'", - "version": "==3.4.2" - }, - "identify": { - "hashes": [ - "sha256:d11469ff952a4d7fd7f9be520d335dc450f585d474b39b5dfb86a500831ab6c7", - "sha256:d27d10099844741c277b45d809bd452db0d70a9b41ea3cd93799ebbbcc6dcb29" - ], - "markers": "python_version >= '3.7'", - "version": "==2.4.5" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "version": "==0.4.3" - }, - "nodeenv": { - "hashes": [ - "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b", - "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7" - ], - "version": "==1.6.0" - }, - "pathspec": { - "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" - ], - "version": "==0.9.0" - }, - "platformdirs": { - "hashes": [ - "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca", - "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda" - ], - "markers": "python_version >= '3.7'", - "version": "==2.4.1" - }, - "pre-commit": { - "hashes": [ - "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616", - "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a" - ], - "index": "pypi", - "version": "==2.17.0" - }, - "pyyaml": { - "hashes": [ - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" - ], - "markers": "python_version >= '3.6'", - "version": "==6.0" - }, - "regex": { - "hashes": [ - "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87", - "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52", - "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3", - "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288", - "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f", - "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c", - "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184", - "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f", - "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8", - "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02", - "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3", - "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38", - "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d", - "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633", - "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4", - "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5", - "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202", - "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3", - "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118", - "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d", - "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729", - "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed", - "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607", - "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c", - "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a", - "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75", - "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899", - "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0", - "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832", - "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9", - "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a", - "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6", - "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1", - "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68", - "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e", - "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74", - "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7", - "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3", - "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4", - "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4", - "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b", - "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c", - "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101", - "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a", - "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1", - "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7", - "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d", - "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605", - "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d", - "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916", - "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949", - "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6", - "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3", - "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6", - "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9", - "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af", - "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59", - "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f", - "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2", - "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298", - "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4", - "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c", - "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc", - "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a", - "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43", - "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a", - "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb", - "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093", - "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8", - "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52", - "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442", - "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338", - "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f", - "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab" - ], - "version": "==2022.1.18" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" - }, - "tomli": { - "hashes": [ - "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", - "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" - ], - "markers": "python_version >= '3.6'", - "version": "==1.2.3" - }, - "tomlkit": { - "hashes": [ - "sha256:29e84a855712dfe0e88a48f6d05c21118dbafb283bb2eed614d46f80deb8e9a1", - "sha256:b824e3466f1d475b2b5f1c392954c6cb7ea04d64354ff7300dc7c14257dc85db" - ], - "markers": "python_version >= '3.6' and python_full_version < '4.0.0'", - "version": "==0.8.0" - }, - "tqdm": { - "hashes": [ - "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c", - "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.62.3" - }, - "typing-extensions": { - "hashes": [ - "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", - "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.1" - }, - "virtualenv": { - "hashes": [ - "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09", - "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.13.0" - } - } -} diff --git a/cmsmanage/settings/.gitignore b/cmsmanage/settings/.gitignore new file mode 100644 index 0000000..a2ac02f --- /dev/null +++ b/cmsmanage/settings/.gitignore @@ -0,0 +1,2 @@ +dev.py +prod.py diff --git a/cmsmanage/settings/base.py b/cmsmanage/settings/base.py index c8ba782..ec5e62d 100644 --- a/cmsmanage/settings/base.py +++ b/cmsmanage/settings/base.py @@ -10,7 +10,6 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ -import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -22,18 +21,20 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - "tasks.apps.TasksConfig", - "rentals.apps.RentalsConfig", - "widget_tweaks", - "markdownx", - "markdownify", - "recurrence", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "django_admin_logs", + "widget_tweaks", + "markdownx", + "recurrence", + "tasks.apps.TasksConfig", + "rentals.apps.RentalsConfig", + "membershipworks.apps.MembershipworksConfig", + "paperwork.apps.PaperworkConfig", ] MIDDLEWARE = [ @@ -51,7 +52,7 @@ ROOT_URLCONF = "cmsmanage.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], + "DIRS": [Path(BASE_DIR) / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ @@ -68,6 +69,10 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" WSGI_APPLICATION = "cmsmanage.wsgi.application" +DATABASE_ROUTERS = [ + "membershipworks.routers.MembershipWorksRouter", + "paperwork.routers.PaperworkRouter", +] # Default URL to redirect to after authentication LOGIN_REDIRECT_URL = "/" diff --git a/cmsmanage/settings/dev.py b/cmsmanage/settings/dev.py deleted file mode 100644 index 260aa89..0000000 --- a/cmsmanage/settings/dev.py +++ /dev/null @@ -1,20 +0,0 @@ -from .base import * - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "g&zqf68eks@z$ctb%3_5+retmt%_i0=)7-y$i^gvc6p#s1+*ng" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -# Database -# https://docs.djangoproject.com/en/3.1/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } -} diff --git a/cmsmanage/settings/dev_base.py b/cmsmanage/settings/dev_base.py new file mode 100644 index 0000000..e8aab68 --- /dev/null +++ b/cmsmanage/settings/dev_base.py @@ -0,0 +1,7 @@ +from .base import * + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True diff --git a/cmsmanage/settings/prod_base.py b/cmsmanage/settings/prod_base.py index 772c78d..fee4566 100644 --- a/cmsmanage/settings/prod_base.py +++ b/cmsmanage/settings/prod_base.py @@ -1,5 +1,5 @@ import ldap -from django_auth_ldap.config import LDAPSearch, PosixGroupType +from django_auth_ldap.config import LDAPSearch, PosixGroupType, LDAPGroupQuery from .base import * @@ -27,6 +27,12 @@ AUTH_LDAP_USER_ATTR_MAP = { "email": "mail", } +AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_staff": LDAPGroupQuery( + "cn=MW_CMS Staff,cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org" + ), +} + AUTH_LDAP_GROUP_SEARCH = LDAPSearch( "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org", ldap.SCOPE_SUBTREE, diff --git a/membershipworks/__init__.py b/membershipworks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/membershipworks/admin.py b/membershipworks/admin.py new file mode 100644 index 0000000..67dea7a --- /dev/null +++ b/membershipworks/admin.py @@ -0,0 +1,29 @@ +from django.contrib import admin + +from .models import Member, Flag + + +class ReadOnlyAdmin(admin.ModelAdmin): + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False + + def has_delete_permission(self, request, obj=None): + return False + + +class MemberFlagInline(admin.TabularInline): + model = Member.flags.through + + +@admin.register(Member) +class MemberAdmin(ReadOnlyAdmin): + search_fields = ["account_name"] + inlines = [MemberFlagInline] + + +@admin.register(Flag) +class FlagAdmin(ReadOnlyAdmin): + inlines = [MemberFlagInline] diff --git a/membershipworks/apps.py b/membershipworks/apps.py new file mode 100644 index 0000000..191218a --- /dev/null +++ b/membershipworks/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MembershipworksConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "membershipworks" diff --git a/membershipworks/migrations/0001_initial.py b/membershipworks/migrations/0001_initial.py new file mode 100644 index 0000000..48bcf0f --- /dev/null +++ b/membershipworks/migrations/0001_initial.py @@ -0,0 +1,346 @@ +# Generated by Django 4.0.2 on 2022-02-12 05:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Flag", + fields=[ + ( + "id", + models.CharField(max_length=24, primary_key=True, serialize=False), + ), + ("name", models.TextField(null=True)), + ("type", models.CharField(max_length=6)), + ], + options={ + "db_table": "flag", + "managed": False, + }, + ), + migrations.CreateModel( + name="Member", + fields=[ + ( + "uid", + models.CharField(max_length=24, primary_key=True, serialize=False), + ), + ( + "year_of_birth", + models.TextField(db_column="Year of Birth", null=True), + ), + ("account_name", models.TextField(db_column="Account Name", null=True)), + ("first_name", models.TextField(db_column="First Name", null=True)), + ("last_name", models.TextField(db_column="Last Name", null=True)), + ("phone", models.TextField(db_column="Phone", null=True)), + ("email", models.TextField(db_column="Email", null=True)), + ( + "address_street", + models.TextField(db_column="Address (Street)", null=True), + ), + ( + "address_city", + models.TextField(db_column="Address (City)", null=True), + ), + ( + "address_state_province", + models.TextField(db_column="Address (State/Province)", null=True), + ), + ( + "address_postal_code", + models.TextField(db_column="Address (Postal Code)", null=True), + ), + ( + "address_country", + models.TextField(db_column="Address (Country)", null=True), + ), + ( + "profile_description", + models.TextField(db_column="Profile description", null=True), + ), + ("website", models.TextField(db_column="Website", null=True)), + ("fax", models.TextField(db_column="Fax", null=True)), + ( + "contact_person", + models.TextField(db_column="Contact Person", null=True), + ), + ("password", models.TextField(db_column="Password", null=True)), + ( + "position_relation", + models.TextField(db_column="Position/relation", null=True), + ), + ( + "parent_account_id", + models.TextField(db_column="Parent Account ID", null=True), + ), + ( + "gift_membership_purchased_by", + models.TextField( + db_column="Gift Membership purchased by", null=True + ), + ), + ( + "purchased_gift_membership_for", + models.TextField( + db_column="Purchased Gift Membership for", null=True + ), + ), + ( + "closet_storage", + models.TextField(db_column="Closet Storage #", null=True), + ), + ( + "storage_shelf", + models.TextField(db_column="Storage Shelf #", null=True), + ), + ( + "personal_studio_space", + models.TextField(db_column="Personal Studio Space #", null=True), + ), + ( + "access_permitted_shops_during_extended_hours", + models.BooleanField( + db_column="Access Permitted Shops During Extended Hours?" + ), + ), + ( + "normal_access_permitted_during_covid19_limited_operations", + models.BooleanField( + db_column="Normal Access Permitted During COVID-19 Limited Operations" + ), + ), + ( + "access_permitted_during_covid19_staffed_period_only", + models.BooleanField( + db_column="Access Permitted During COVID-19 Staffed Period Only" + ), + ), + ( + "access_front_door_and_studio_space_during_extended_hours", + models.BooleanField( + db_column="Access Front Door and Studio Space During Extended Hours?" + ), + ), + ( + "access_wood_shop", + models.BooleanField(db_column="Access Wood Shop?"), + ), + ( + "access_metal_shop", + models.BooleanField(db_column="Access Metal Shop?"), + ), + ( + "access_storage_closet", + models.BooleanField(db_column="Access Storage Closet?"), + ), + ( + "access_studio_space", + models.BooleanField(db_column="Access Studio Space?"), + ), + ( + "access_front_door", + models.BooleanField(db_column="Access Front Door?"), + ), + ( + "access_card_number", + models.TextField(db_column="Access Card Number", null=True), + ), + ( + "access_card_facility_code", + models.TextField(db_column="Access Card Facility Code", null=True), + ), + ( + "auto_billing_id", + models.TextField(db_column="Auto Billing ID", null=True), + ), + ( + "billing_method", + models.TextField(db_column="Billing Method", null=True), + ), + ("renewal_date", models.DateField(db_column="Renewal Date", null=True)), + ("join_date", models.DateField(db_column="Join Date", null=True)), + ("admin_note", models.TextField(db_column="Admin note", null=True)), + ( + "profile_gallery_image_url", + models.TextField(db_column="Profile gallery image URL", null=True), + ), + ( + "business_card_image_url", + models.TextField(db_column="Business card image URL", null=True), + ), + ("instagram", models.TextField(db_column="Instagram", null=True)), + ("pinterest", models.TextField(db_column="Pinterest", null=True)), + ("youtube", models.TextField(db_column="Youtube", null=True)), + ("yelp", models.TextField(db_column="Yelp", null=True)), + ("google", models.TextField(db_column="Google+", null=True)), + ("bbb", models.TextField(db_column="BBB", null=True)), + ("twitter", models.TextField(db_column="Twitter", null=True)), + ("facebook", models.TextField(db_column="Facebook", null=True)), + ("linked_in", models.TextField(db_column="LinkedIn", null=True)), + ( + "do_not_show_street_address_in_profile", + models.TextField( + db_column="Do not show street address in profile", null=True + ), + ), + ( + "do_not_list_in_directory", + models.TextField(db_column="Do not list in directory", null=True), + ), + ( + "how_did_you_hear", + models.TextField(db_column="HowDidYouHear", null=True), + ), + ( + "authorize_charge", + models.TextField(db_column="authorizeCharge", null=True), + ), + ( + "policy_agreement", + models.TextField(db_column="policyAgreement", null=True), + ), + ( + "waiver_form_signed_and_on_file_date", + models.DateField( + db_column="Waiver form signed and on file date.", null=True + ), + ), + ( + "membership_agreement_signed_and_on_file_date", + models.DateField( + db_column="Membership Agreement signed and on file date.", + null=True, + ), + ), + ("ip_address", models.TextField(db_column="IP Address", null=True)), + ("audit_date", models.DateField(db_column="Audit Date", null=True)), + ( + "agreement_version", + models.TextField(db_column="Agreement Version", null=True), + ), + ( + "paperwork_status", + models.TextField(db_column="Paperwork status", null=True), + ), + ( + "membership_agreement_dated", + models.BooleanField(db_column="Membership agreement dated"), + ), + ( + "membership_agreement_acknowledgement_page_filled_out", + models.BooleanField( + db_column="Membership Agreement Acknowledgement Page Filled Out" + ), + ), + ( + "membership_agreement_signed", + models.BooleanField(db_column="Membership Agreement Signed"), + ), + ( + "liability_form_filled_out", + models.BooleanField(db_column="Liability Form Filled Out"), + ), + ( + "self_certify_essential_business", + models.BooleanField(db_column="selfCertifyEssentialBusiness"), + ), + ( + "accepted_covid19_policy", + models.BooleanField(db_column="Accepted COVID-19 Policy"), + ), + ], + options={ + "db_table": "members", + "ordering": ("first_name", "last_name"), + "managed": False, + }, + ), + migrations.CreateModel( + name="MemberFlag", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ], + options={ + "db_table": "memberflag", + "managed": False, + }, + ), + migrations.CreateModel( + name="Transaction", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("sid", models.CharField(max_length=27, null=True)), + ("timestamp", models.DateTimeField()), + ("type", models.TextField(null=True)), + ( + "sum", + models.DecimalField(decimal_places=4, max_digits=13, null=True), + ), + ( + "fee", + models.DecimalField(decimal_places=4, max_digits=13, null=True), + ), + ("event_id", models.TextField(null=True)), + ("for_what", models.TextField(db_column="For", null=True)), + ("items", models.TextField(db_column="Items", null=True)), + ( + "discount_code", + models.TextField(db_column="Discount Code", null=True), + ), + ("note", models.TextField(db_column="Note", null=True)), + ("name", models.TextField(db_column="Name", null=True)), + ( + "contact_person", + models.TextField(db_column="Contact Person", null=True), + ), + ("full_address", models.TextField(db_column="Full Address", null=True)), + ("street", models.TextField(db_column="Street", null=True)), + ("city", models.TextField(db_column="City", null=True)), + ( + "state_province", + models.TextField(db_column="State/Province", null=True), + ), + ("postal_code", models.TextField(db_column="Postal Code", null=True)), + ("country", models.TextField(db_column="Country", null=True)), + ("phone", models.TextField(db_column="Phone", null=True)), + ("email", models.TextField(db_column="Email", null=True)), + ( + "member", + models.ForeignKey( + db_column="uid", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="transactions", + to="membershipworks.member", + ), + ), + ], + options={ + "db_table": "transactions", + }, + ), + ] diff --git a/membershipworks/migrations/__init__.py b/membershipworks/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/membershipworks/models.py b/membershipworks/models.py new file mode 100644 index 0000000..6f2b154 --- /dev/null +++ b/membershipworks/models.py @@ -0,0 +1,154 @@ +from datetime import datetime + +from django.db import models + + +class Flag(models.Model): + id = models.CharField(max_length=24, primary_key=True) + name = models.TextField(null=True) + type = models.CharField(max_length=6) + + def __str__(self): + return f"{self.name} ({self.type})" + + class Meta: + managed = False + db_table = "flag" + + +# TODO: is this still a temporal table? +class Member(models.Model): + uid = models.CharField(max_length=24, primary_key=True) + year_of_birth = models.TextField(db_column="Year of Birth", null=True) + account_name = models.TextField(db_column="Account Name", null=True) + first_name = models.TextField(db_column="First Name", null=True) + last_name = models.TextField(db_column="Last Name", null=True) + phone = models.TextField(db_column="Phone", null=True) + email = models.TextField(db_column="Email", null=True) + address_street = models.TextField(db_column="Address (Street)", null=True) + address_city = models.TextField(db_column="Address (City)", null=True) + address_state_province = models.TextField( + db_column="Address (State/Province)", null=True + ) + address_postal_code = models.TextField(db_column="Address (Postal Code)", null=True) + address_country = models.TextField(db_column="Address (Country)", null=True) + profile_description = models.TextField(db_column="Profile description", null=True) + website = models.TextField(db_column="Website", null=True) + fax = models.TextField(db_column="Fax", null=True) + contact_person = models.TextField(db_column="Contact Person", null=True) + password = models.TextField(db_column="Password", null=True) + position_relation = models.TextField(db_column="Position/relation", null=True) + parent_account_id = models.TextField(db_column="Parent Account ID", null=True) + gift_membership_purchased_by = models.TextField( + db_column="Gift Membership purchased by", null=True + ) + purchased_gift_membership_for = models.TextField( + db_column="Purchased Gift Membership for", null=True + ) + closet_storage = models.TextField(db_column="Closet Storage #", null=True) + storage_shelf = models.TextField(db_column="Storage Shelf #", null=True) + personal_studio_space = models.TextField( + db_column="Personal Studio Space #", null=True + ) + access_permitted_shops_during_extended_hours = models.BooleanField( + db_column="Access Permitted Shops During Extended Hours?" + ) + normal_access_permitted_during_covid19_limited_operations = models.BooleanField( + db_column="Normal Access Permitted During COVID-19 Limited Operations" + ) + access_permitted_during_covid19_staffed_period_only = models.BooleanField( + db_column="Access Permitted During COVID-19 Staffed Period Only" + ) + access_front_door_and_studio_space_during_extended_hours = models.BooleanField( + db_column="Access Front Door and Studio Space During Extended Hours?" + ) + access_wood_shop = models.BooleanField(db_column="Access Wood Shop?") + access_metal_shop = models.BooleanField(db_column="Access Metal Shop?") + access_storage_closet = models.BooleanField(db_column="Access Storage Closet?") + access_studio_space = models.BooleanField(db_column="Access Studio Space?") + access_front_door = models.BooleanField(db_column="Access Front Door?") + access_card_number = models.TextField(db_column="Access Card Number", null=True) + access_card_facility_code = models.TextField( + db_column="Access Card Facility Code", null=True + ) + auto_billing_id = models.TextField(db_column="Auto Billing ID", null=True) + billing_method = models.TextField(db_column="Billing Method", null=True) + renewal_date = models.DateField(db_column="Renewal Date", null=True) + join_date = models.DateField(db_column="Join Date", null=True) + admin_note = models.TextField(db_column="Admin note", null=True) + profile_gallery_image_url = models.TextField( + db_column="Profile gallery image URL", null=True + ) + business_card_image_url = models.TextField( + db_column="Business card image URL", null=True + ) + instagram = models.TextField(db_column="Instagram", null=True) + pinterest = models.TextField(db_column="Pinterest", null=True) + youtube = models.TextField(db_column="Youtube", null=True) + yelp = models.TextField(db_column="Yelp", null=True) + google = models.TextField(db_column="Google+", null=True) + bbb = models.TextField(db_column="BBB", null=True) + twitter = models.TextField(db_column="Twitter", null=True) + facebook = models.TextField(db_column="Facebook", null=True) + linked_in = models.TextField(db_column="LinkedIn", null=True) + do_not_show_street_address_in_profile = models.TextField( + db_column="Do not show street address in profile", null=True + ) + do_not_list_in_directory = models.TextField( + db_column="Do not list in directory", null=True + ) + how_did_you_hear = models.TextField(db_column="HowDidYouHear", null=True) + authorize_charge = models.TextField(db_column="authorizeCharge", null=True) + policy_agreement = models.TextField(db_column="policyAgreement", null=True) + waiver_form_signed_and_on_file_date = models.DateField( + db_column="Waiver form signed and on file date.", null=True + ) + membership_agreement_signed_and_on_file_date = models.DateField( + db_column="Membership Agreement signed and on file date.", null=True + ) + ip_address = models.TextField(db_column="IP Address", null=True) + audit_date = models.DateField(db_column="Audit Date", null=True) + agreement_version = models.TextField(db_column="Agreement Version", null=True) + paperwork_status = models.TextField(db_column="Paperwork status", null=True) + membership_agreement_dated = models.BooleanField( + db_column="Membership agreement dated" + ) + membership_agreement_acknowledgement_page_filled_out = models.BooleanField( + db_column="Membership Agreement Acknowledgement Page Filled Out" + ) + membership_agreement_signed = models.BooleanField( + db_column="Membership Agreement Signed" + ) + liability_form_filled_out = models.BooleanField( + db_column="Liability Form Filled Out" + ) + self_certify_essential_business = models.BooleanField( + db_column="selfCertifyEssentialBusiness" + ) + accepted_covid19_policy = models.BooleanField(db_column="Accepted COVID-19 Policy") + flags = models.ManyToManyField(Flag, through="MemberFlag", related_name="members") + + def __str__(self): + return f"{self.account_name}" + + class Meta: + managed = False + db_table = "members" + ordering = ("first_name", "last_name") + + +class MemberFlag(models.Model): + member = models.ForeignKey(Member, on_delete=models.PROTECT, db_column="uid") + flag = models.ForeignKey(Flag, on_delete=models.PROTECT) + + def __str__(self): + return f"{self.member} - {self.flag}" + + class Meta: + managed = False + db_table = "memberflag" + constraints = [ + models.UniqueConstraint( + fields=["member", "flag_id"], name="unique_member_flag" + ) + ] diff --git a/membershipworks/routers.py b/membershipworks/routers.py new file mode 100644 index 0000000..29f8e61 --- /dev/null +++ b/membershipworks/routers.py @@ -0,0 +1,18 @@ +class MembershipWorksRouter: + app_label = "membershipworks" + db = "membershipworks" + + def db_for_read(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def allow_migrate(self, db, app_label, model_name=None, **hints): + if db == self.db: + return False + return None diff --git a/membershipworks/tests.py b/membershipworks/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/membershipworks/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/membershipworks/views.py b/membershipworks/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/membershipworks/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/paperwork/__init__.py b/paperwork/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/paperwork/admin.py b/paperwork/admin.py new file mode 100644 index 0000000..1849dff --- /dev/null +++ b/paperwork/admin.py @@ -0,0 +1,130 @@ +from django.core import mail +from django.contrib import admin, messages +from django.db.models.functions import Now + +from .models import ( + CmsRedRiverVeteransScholarship, + CertificationDefinition, + Certification, + CertificationVersion, + InstructorOrVendor, + SpecialProgram, + Waiver, +) +from .certification_emails import all_certification_emails + + +class CertificationVersionInline(admin.TabularInline): + model = CertificationVersion + extra = 1 + + +@admin.register(CertificationVersion) +class CertificationVersionAdmin(admin.ModelAdmin): + search_fields = ["definition__certification_name", "version"] + list_display = ["definition", "version"] + list_filter = ["definition__department", "definition__certification_name"] + + +@admin.register(CertificationDefinition) +class CertificationDefinitionAdmin(admin.ModelAdmin): + search_fields = ["certification_name"] + list_display = ["certification_name", "department"] + list_filter = ["department"] + inlines = [CertificationVersionInline] + + +@admin.register(Certification) +class CertificationAdmin(admin.ModelAdmin): + search_fields = [ + "name", + "certification__certification_name", + "certification__department", + ] + autocomplete_fields = ["member"] + exclude = ["shop_lead_notified"] + + @admin.display( + description="Certification Name", + ordering="certification_version__definition__certification_name", + ) + def certification_name(self, obj): + return obj.certification_version.definition.certification_name + + @admin.display( + description="Certification Version", ordering="certification_version__version" + ) + def certification_version_version(self, obj): + return obj.certification_version.version + + @admin.display( + description="Department", + ordering="certification_version__definition__department", + ) + def certification_department(self, obj): + return obj.certification_version.definition.department + + list_display = [ + "certification_name", + "name", + "certification_version_version", + "certification_department", + "date", + "shop_lead_notified", + "certified_by", + ] + list_display_links = [ + "certification_name", + "name", + ] + list_filter = [ + "certification_version__definition__department", + ("shop_lead_notified", admin.EmptyFieldListFilter), + ] + + actions = ["send_notifications"] + + @admin.action( + description="Notify Shop Leads and Members of selected certifications" + ) + def send_notifications(self, request, queryset): + try: + emails = list(all_certification_emails(queryset)) + print(emails) + + with mail.get_connection() as conn: + conn.send_messages(emails) + + for cert in queryset: + cert.update(shop_lead_notified=Now()) + + self.message_user( + request, + f"{len(emails)} notifications sent for {len(queryset)} certifications", + messages.SUCCESS, + ) + except Exception as e: + self.message_user( + request, + f"Failed to send notifications! {e}", + messages.ERROR, + ) + raise + + +@admin.register(InstructorOrVendor) +class InstructorOrVendorAdmin(admin.ModelAdmin): + search_fields = ["name"] + + +@admin.register(SpecialProgram) +class SpecialProgramAdmin(admin.ModelAdmin): + search_fields = ["program_name"] + + +@admin.register(Waiver) +class WaiverAdmin(admin.ModelAdmin): + search_fields = ["name"] + + +admin.site.register(CmsRedRiverVeteransScholarship) diff --git a/paperwork/apps.py b/paperwork/apps.py new file mode 100644 index 0000000..edf8c86 --- /dev/null +++ b/paperwork/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PaperworkConfig(AppConfig): + name = "paperwork" diff --git a/paperwork/certification_emails.py b/paperwork/certification_emails.py new file mode 100644 index 0000000..e6374f5 --- /dev/null +++ b/paperwork/certification_emails.py @@ -0,0 +1,102 @@ +from itertools import groupby + +from django.core import mail +from django.core.mail.message import sanitize_address +from django.contrib.auth import get_user_model +from django.template import loader + +from markdownify import markdownify +import mdformat + +from membershipworks.models import Member + + +def make_multipart_email(subject, html_body, to): + plain_body = mdformat.text(markdownify(html_body), extensions={"tables"}) + + email = mail.EmailMultiAlternatives( + subject, + plain_body, + "Claremont MakerSpace Member Certification System ", + to, + reply_to=["Claremont MakerSpace "], + ) + + email.attach_alternative(html_body, "text/html") + return email + + +def make_department_email(department, certifications): + template = loader.get_template("paperwork/department_certifications_email.dj.html") + shop_leads = Member.objects.filter( + flags__type="label", flags__name="Shop Lead: " + department + ).values_list("first_name", "account_name", "email", named=True) + + html_body = template.render( + { + "shop_lead_names": [shop_lead.first_name for shop_lead in shop_leads], + "department": department, + "certifications": certifications, + } + ) + + return make_multipart_email( + f"{len(certifications)} new CMS Certifications issued for {department}", + html_body, + to=[ + sanitize_address((shop_lead.account_name, shop_lead.email), "ascii") + for shop_lead in shop_leads + ], + ) + + +def department_emails(ordered_queryset): + certifications_by_department = groupby( + ordered_queryset, lambda c: c.certification_version.definition.department + ) + + for department, certifications in certifications_by_department: + yield make_department_email(department, list(certifications)) + + +def make_member_email(member, certifications): + template = loader.get_template("paperwork/member_certifications_email.dj.html") + + html_body = template.render({"member": member, "certifications": certifications}) + + return make_multipart_email( + f"You have been issued {len(certifications)} new CMS Certifications", + html_body, + to=[sanitize_address((member.account_name, member.email), "ascii")], + ) + + +def member_emails(ordered_queryset): + certifications_by_member = groupby( + ordered_queryset.filter(member__isnull=False), lambda c: c.member + ) + + for member, certifications in certifications_by_member: + yield make_member_email(member, list(certifications)) + + +def admin_email(ordered_queryset): + template = loader.get_template("paperwork/admin_certifications_email.dj.html") + html_body = template.render({"certifications": ordered_queryset}) + + return make_multipart_email( + f"{len(ordered_queryset)} new CMS Certifications issued", + html_body, + # TODO: Admin emails should probably be from a group, not all staff + to=[get_user_model().filter(is_staff=True).values("email", flat=True)], + ) + + +def all_certification_emails(queryset): + ordered_queryset = queryset.select_related( + "certification_version__definition" + ).order_by("certification_version__definition__department") + + yield from department_emails(ordered_queryset) + yield from member_emails(ordered_queryset) + yield admin_email(ordered_queryset) diff --git a/paperwork/migrations/0001_initial.py b/paperwork/migrations/0001_initial.py new file mode 100644 index 0000000..8ca1fca --- /dev/null +++ b/paperwork/migrations/0001_initial.py @@ -0,0 +1,325 @@ +# Generated by Django 4.0.2 on 2022-02-03 21:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("membershipworks", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="CertificationDefinition", + fields=[ + ( + "certification_identifier", + models.AutoField( + db_column="Certification Identifier", + primary_key=True, + serialize=False, + ), + ), + ( + "certification_name", + models.CharField( + blank=True, + db_column="Certification Name", + max_length=255, + null=True, + ), + ), + ( + "department", + models.CharField( + blank=True, db_column="Department", max_length=255, null=True + ), + ), + ], + options={ + "db_table": "Certification Definitions", + "ordering": ("certification_name", "department"), + }, + ), + migrations.CreateModel( + name="CmsRedRiverVeteransScholarship", + fields=[ + ("serial", models.AutoField(primary_key=True, serialize=False)), + ( + "program_name", + models.CharField(db_column="Program Name", max_length=255), + ), + ( + "member_name", + models.CharField( + blank=True, db_column="Member Name", max_length=255, null=True + ), + ), + ( + "discount_percent", + models.DecimalField( + blank=True, + db_column="Discount Percent", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "discount_code", + models.CharField( + blank=True, db_column="Discount Code", max_length=255, null=True + ), + ), + ( + "membership_code", + models.CharField( + blank=True, + db_column="Membership Code", + max_length=255, + null=True, + ), + ), + ( + "start_date", + models.DateField(blank=True, db_column="Start Date", null=True), + ), + ( + "end_date", + models.DateField(blank=True, db_column="End Date", null=True), + ), + ( + "program_amount", + models.DecimalField( + blank=True, + db_column="Program Amount", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "program_status", + models.CharField( + blank=True, db_column="Program Status", max_length=16, null=True + ), + ), + ], + options={ + "db_table": "CMS Red River Veterans Scholarship", + }, + ), + migrations.CreateModel( + name="InstructorOrVendor", + fields=[ + ("serial", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(db_column="Name", max_length=255)), + ( + "instructor_agreement_date", + models.DateField( + blank=True, db_column="Instructor Agreement Date", null=True + ), + ), + ( + "w9_date", + models.DateField(blank=True, db_column="W9 date", null=True), + ), + ("phone", models.CharField(blank=True, max_length=255, null=True)), + ( + "email_address", + models.CharField( + blank=True, db_column="email address", max_length=255, null=True + ), + ), + ], + options={ + "db_table": "Instructors and Vendors", + }, + ), + migrations.CreateModel( + name="SpecialProgram", + fields=[ + ( + "program_name", + models.CharField( + db_column="Program Name", + max_length=255, + primary_key=True, + serialize=False, + ), + ), + ( + "discount_percent", + models.DecimalField( + blank=True, + db_column="Discount Percent", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "discount_code", + models.CharField( + blank=True, db_column="Discount Code", max_length=255, null=True + ), + ), + ( + "membership_code", + models.CharField( + blank=True, + db_column="Membership Code", + max_length=255, + null=True, + ), + ), + ( + "start_date", + models.DateField(blank=True, db_column="Start Date", null=True), + ), + ( + "end_date", + models.DateField(blank=True, db_column="End Date", null=True), + ), + ( + "program_amount", + models.DecimalField( + blank=True, + db_column="Program Amount", + decimal_places=0, + max_digits=16, + null=True, + ), + ), + ( + "program_status", + models.CharField( + blank=True, db_column="Program Status", max_length=16, null=True + ), + ), + ], + options={ + "db_table": "Special_Programs", + }, + ), + migrations.CreateModel( + name="Waiver", + fields=[ + ( + "number", + models.AutoField( + db_column="Number", primary_key=True, serialize=False + ), + ), + ("name", models.CharField(db_column="Name", max_length=255)), + ("date", models.DateField(db_column="Date")), + ( + "emergency_contact_name", + models.CharField( + blank=True, + db_column="Emergency Contact Name", + max_length=255, + null=True, + ), + ), + ( + "emergency_contact_number", + models.CharField( + blank=True, + db_column="Emergency Contact Number", + max_length=25, + null=True, + ), + ), + ( + "waiver_version", + models.CharField(db_column="Waiver version", max_length=64), + ), + ( + "guardian_name", + models.CharField( + blank=True, db_column="Guardian Name", max_length=255, null=True + ), + ), + ( + "guardian_relation", + models.CharField( + blank=True, + db_column="Guardian Relation", + max_length=255, + null=True, + ), + ), + ( + "guardian_date", + models.DateField(blank=True, db_column="Guardian Date", null=True), + ), + ], + options={ + "db_table": "Waivers", + }, + ), + migrations.CreateModel( + name="Certification", + fields=[ + ( + "number", + models.AutoField( + db_column="Number", primary_key=True, serialize=False + ), + ), + ("name", models.CharField(db_column="Name", max_length=255)), + ( + "certified_by", + models.CharField( + blank=True, db_column="Certified_By", max_length=255, null=True + ), + ), + ("date", models.DateField(blank=True, db_column="Date", null=True)), + ( + "version", + models.CharField( + blank=True, db_column="Version", max_length=255, null=True + ), + ), + ( + "shop_lead_notified", + models.DateTimeField( + blank=True, db_column="Shop Lead Notified", null=True + ), + ), + ( + "certification", + models.ForeignKey( + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), + ( + "member", + models.ForeignKey( + blank=True, + db_column="uid", + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="membershipworks.member", + ), + ), + ( + "notes", + models.CharField( + blank=True, db_column="Notes", max_length=255, null=True + ), + ), + ], + options={ + "db_table": "Certifications", + }, + ), + ] diff --git a/paperwork/migrations/0002_add_certification_version_model.py b/paperwork/migrations/0002_add_certification_version_model.py new file mode 100644 index 0000000..74c701a --- /dev/null +++ b/paperwork/migrations/0002_add_certification_version_model.py @@ -0,0 +1,88 @@ +# (Partially) Generated by Django 4.0.2 on 2022-02-04 18:01 + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_certification_version_forward(apps, schema_editor): + db_alias = schema_editor.connection.alias + + Certification = apps.get_model("paperwork", "Certification") + CertificationVersion = apps.get_model("paperwork", "CertificationVersion") + + for certification in Certification.objects.using(db_alias).all(): + (version, _) = CertificationVersion.objects.using(db_alias).get_or_create( + definition=certification.certification, version=certification.version + ) + certification.certification_version = version + certification.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("paperwork", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="CertificationVersion", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "version", + models.CharField( + blank=True, db_column="Version", max_length=255, null=True + ), + ), + ( + "definition", + models.ForeignKey( + db_column="Certification", + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationdefinition", + ), + ), + ], + ), + migrations.AddConstraint( + model_name="certificationversion", + constraint=models.UniqueConstraint( + fields=("definition", "version"), name="unique_certification_version" + ), + ), + migrations.AddField( + model_name="certification", + name="certification_version", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationversion", + null=True, + ), + ), + migrations.RunPython(migrate_certification_version_forward), + migrations.AlterField( + model_name="certification", + name="certification_version", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="paperwork.certificationversion", + ), + ), + migrations.RemoveField( + model_name="certification", + name="certification", + ), + migrations.RemoveField( + model_name="certification", + name="version", + ), + ] diff --git a/paperwork/migrations/__init__.py b/paperwork/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/paperwork/models.py b/paperwork/models.py new file mode 100644 index 0000000..1ebcbc4 --- /dev/null +++ b/paperwork/models.py @@ -0,0 +1,193 @@ +from django.db import models + +from membershipworks.models import Member + + +class CmsRedRiverVeteransScholarship(models.Model): + serial = models.AutoField(primary_key=True) + program_name = models.CharField(db_column="Program Name", max_length=255) + member_name = models.CharField( + db_column="Member Name", max_length=255, blank=True, null=True + ) + discount_percent = models.DecimalField( + db_column="Discount Percent", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + discount_code = models.CharField( + db_column="Discount Code", max_length=255, blank=True, null=True + ) + membership_code = models.CharField( + db_column="Membership Code", max_length=255, blank=True, null=True + ) + start_date = models.DateField(db_column="Start Date", blank=True, null=True) + end_date = models.DateField(db_column="End Date", blank=True, null=True) + program_amount = models.DecimalField( + db_column="Program Amount", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + program_status = models.CharField( + db_column="Program Status", max_length=16, blank=True, null=True + ) + + def __str__(self): + return f"{self.program_name} {self.member_name}" + + class Meta: + db_table = "CMS Red River Veterans Scholarship" + + +class CertificationDefinition(models.Model): + certification_identifier = models.AutoField( + db_column="Certification Identifier", primary_key=True + ) + certification_name = models.CharField( + db_column="Certification Name", max_length=255, blank=True, null=True + ) + department = models.CharField( + db_column="Department", max_length=255, blank=True, null=True + ) + + def __str__(self): + return f"{self.certification_name} <{self.department}>" + + class Meta: + db_table = "Certification Definitions" + ordering = ("certification_name", "department") + + +class CertificationVersion(models.Model): + definition = models.ForeignKey( + CertificationDefinition, on_delete=models.PROTECT, db_column="Certification" + ) + version = models.CharField( + db_column="Version", max_length=255, blank=True, null=True + ) + + def __str__(self): + return f"{self.definition} [{self.version}]" + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["definition", "version"], name="unique_certification_version" + ) + ] + + +class Certification(models.Model): + number = models.AutoField(db_column="Number", primary_key=True) + certification_version = models.ForeignKey( + CertificationVersion, on_delete=models.PROTECT + ) + name = models.CharField(db_column="Name", max_length=255) + member = models.ForeignKey( + Member, + on_delete=models.PROTECT, + to_field="uid", + db_column="uid", + blank=True, + null=True, + db_constraint=False, + ) + certified_by = models.CharField( + db_column="Certified_By", max_length=255, blank=True, null=True + ) + date = models.DateField(db_column="Date", blank=True, null=True) + shop_lead_notified = models.DateTimeField( + db_column="Shop Lead Notified", blank=True, null=True + ) + notes = models.CharField(db_column="Notes", max_length=255, blank=True, null=True) + + def __str__(self): + return f"{self.name} - {self.certification_version}" + + class Meta: + db_table = "Certifications" + + +class InstructorOrVendor(models.Model): + serial = models.AutoField(primary_key=True) + name = models.CharField(db_column="Name", max_length=255) + instructor_agreement_date = models.DateField( + db_column="Instructor Agreement Date", blank=True, null=True + ) + w9_date = models.DateField(db_column="W9 date", blank=True, null=True) + phone = models.CharField(max_length=255, blank=True, null=True) + email_address = models.CharField( + db_column="email address", max_length=255, blank=True, null=True + ) + + def __str__(self): + return f"{self.name}" + + class Meta: + db_table = "Instructors and Vendors" + + +class SpecialProgram(models.Model): + program_name = models.CharField( + db_column="Program Name", primary_key=True, max_length=255 + ) + discount_percent = models.DecimalField( + db_column="Discount Percent", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + discount_code = models.CharField( + db_column="Discount Code", max_length=255, blank=True, null=True + ) + membership_code = models.CharField( + db_column="Membership Code", max_length=255, blank=True, null=True + ) + start_date = models.DateField(db_column="Start Date", blank=True, null=True) + end_date = models.DateField(db_column="End Date", blank=True, null=True) + program_amount = models.DecimalField( + db_column="Program Amount", + max_digits=16, + decimal_places=0, + blank=True, + null=True, + ) + program_status = models.CharField( + db_column="Program Status", max_length=16, blank=True, null=True + ) + + def __str__(self): + return self.program_name + + class Meta: + db_table = "Special_Programs" + + +class Waiver(models.Model): + number = models.AutoField(db_column="Number", primary_key=True) + name = models.CharField(db_column="Name", max_length=255) + date = models.DateField(db_column="Date") + emergency_contact_name = models.CharField( + db_column="Emergency Contact Name", max_length=255, blank=True, null=True + ) + emergency_contact_number = models.CharField( + db_column="Emergency Contact Number", max_length=25, blank=True, null=True + ) + waiver_version = models.CharField(db_column="Waiver version", max_length=64) + guardian_name = models.CharField( + db_column="Guardian Name", max_length=255, blank=True, null=True + ) + guardian_relation = models.CharField( + db_column="Guardian Relation", max_length=255, blank=True, null=True + ) + guardian_date = models.DateField(db_column="Guardian Date", blank=True, null=True) + + def __str__(self): + return f"{self.name} {self.date}" + + class Meta: + db_table = "Waivers" diff --git a/paperwork/routers.py b/paperwork/routers.py new file mode 100644 index 0000000..ec907a5 --- /dev/null +++ b/paperwork/routers.py @@ -0,0 +1,22 @@ +class PaperworkRouter: + app_label = "paperwork" + related_app_labels = {"paperwork", "membershipworks"} + db = "cms" + + def db_for_read(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def db_for_write(self, model, **hints): + if model._meta.app_label == self.app_label: + return self.db + return None + + def allow_relation(self, obj1, obj2, **hints): + if ( + obj1._meta.app_label in self.related_app_labels + or obj2._meta.app_label in self.related_app_labels + ): + return True + return None diff --git a/paperwork/templates/paperwork/admin_certifications_email.dj.html b/paperwork/templates/paperwork/admin_certifications_email.dj.html new file mode 100644 index 0000000..07a994c --- /dev/null +++ b/paperwork/templates/paperwork/admin_certifications_email.dj.html @@ -0,0 +1,23 @@ +

+ The following Claremont MakerSpace Member Certifications have been issued: +

+ + + + + + + + + + {% for certification in certifications %} + + + + + + + + + {% endfor %} +
CertificationVersionMemberDepartmentCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.member }}{{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/templates/paperwork/department_certifications_email.dj.html b/paperwork/templates/paperwork/department_certifications_email.dj.html new file mode 100644 index 0000000..e3c38a5 --- /dev/null +++ b/paperwork/templates/paperwork/department_certifications_email.dj.html @@ -0,0 +1,24 @@ +

+ Dear {{ shop_lead_names|join:", " }}: +

+

+ The following Claremont MakerSpace Member Certifications have been issued for the {{ department }}: +

+ + + + + + + + + {% for certification in certifications %} + + + + + + + + {% endfor %} +
CertificationVersionMemberCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.member }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/templates/paperwork/member_certifications_email.dj.html b/paperwork/templates/paperwork/member_certifications_email.dj.html new file mode 100644 index 0000000..f0bc76f --- /dev/null +++ b/paperwork/templates/paperwork/member_certifications_email.dj.html @@ -0,0 +1,24 @@ +

+ Dear {{ member.first_name }}: +

+

+ You have been issued the following Claremont MakerSpace Member Certifications: +

+ + + + + + + + + {% for certification in certifications %} + + + + + + + + {% endfor %} +
CertificationVersionDepartmentCertified ByDate
{{ certification.certification_version.definition.certification_name }}{{ certification.certification_version.version }}{{ certification.certification_version.definition.department }}{{ certification.certified_by }}{{ certification.date }}
diff --git a/paperwork/tests.py b/paperwork/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/paperwork/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/paperwork/views.py b/paperwork/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/paperwork/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 0000000..9b08bcc --- /dev/null +++ b/pdm.lock @@ -0,0 +1,662 @@ +[[package]] +name = "asgiref" +version = "3.5.0" +requires_python = ">=3.7" +summary = "ASGI specs, helper code, and adapters" + +[[package]] +name = "attrs" +version = "21.4.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Classes Without Boilerplate" + +[[package]] +name = "beautifulsoup4" +version = "4.10.0" +requires_python = ">3.0.0" +summary = "Screen-scraping library" +dependencies = [ + "soupsieve>1.2", +] + +[[package]] +name = "black" +version = "22.1.0" +requires_python = ">=3.6.2" +summary = "The uncompromising code formatter." +dependencies = [ + "click>=8.0.0", + "mypy-extensions>=0.4.3", + "pathspec>=0.9.0", + "platformdirs>=2", + "tomli>=1.1.0", + "typing-extensions>=3.10.0.0; python_version < \"3.10\"", +] + +[[package]] +name = "click" +version = "8.0.3" +requires_python = ">=3.6" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "colorama" +version = "0.4.4" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "Cross-platform colored terminal text." + +[[package]] +name = "django" +version = "4.0.2" +requires_python = ">=3.8" +summary = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +dependencies = [ + "asgiref<4,>=3.4.1", + "sqlparse>=0.2.2", + "tzdata; sys_platform == \"win32\"", +] + +[[package]] +name = "django-admin-logs" +version = "1.0.2" +requires_python = ">=3.5" +summary = "View, delete or disable Django admin log entries." +dependencies = [ + "Django>=2.1", +] + +[[package]] +name = "django-auth-ldap" +version = "4.0.0" +requires_python = ">=3.6" +summary = "Django LDAP authentication backend." +dependencies = [ + "Django>=2.2", + "python-ldap>=3.1", +] + +[[package]] +name = "django-markdownx" +version = "4.0.0b1" +summary = "A comprehensive Markdown editor built for Django." +dependencies = [ + "Django", + "Markdown", + "Pillow", +] + +[[package]] +name = "django-recurrence" +version = "1.11.1" +requires_python = ">=3.7" +summary = "Django utility wrapping dateutil.rrule" +dependencies = [ + "django>=2.2", + "python-dateutil", +] + +[[package]] +name = "django-widget-tweaks" +version = "1.4.12" +requires_python = ">=3.7" +summary = "Tweak the form field rendering in templates, not in python-level form definitions." + +[[package]] +name = "djlint" +version = "0.7.4" +requires_python = ">=3.7,<4.0" +summary = "HTML Template Linter and Formatter" +dependencies = [ + "PyYAML<7.0,>=6.0", + "click<9.0.0,>=8.0.1", + "colorama<0.5.0,>=0.4.4", + "importlib-metadata<5.0.0,>=4.10.1", + "pathspec<0.10.0,>=0.9.0", + "regex<2023.0.0,>=2022.1.18", + "tomlkit<0.9.0,>=0.8.0", + "tqdm<5.0.0,>=4.62.2", +] + +[[package]] +name = "h11" +version = "0.13.0" +requires_python = ">=3.6" +summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" + +[[package]] +name = "importlib-metadata" +version = "4.11.0" +requires_python = ">=3.7" +summary = "Read metadata from Python packages" +dependencies = [ + "zipp>=0.5", +] + +[[package]] +name = "markdown" +version = "3.3.6" +requires_python = ">=3.6" +summary = "Python implementation of Markdown." +dependencies = [ + "importlib-metadata>=4.4; python_version < \"3.10\"", +] + +[[package]] +name = "markdown-it-py" +version = "2.0.1" +requires_python = "~=3.6" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "attrs<22,>=19", + "mdurl~=0.1", +] + +[[package]] +name = "markdownify" +version = "0.10.3" +summary = "Convert HTML to markdown." +dependencies = [ + "beautifulsoup4<5,>=4.9", + "six<2,>=1.15", +] + +[[package]] +name = "mdformat" +version = "0.7.13" +requires_python = ">=3.7,<4.0" +summary = "CommonMark compliant Markdown formatter" +dependencies = [ + "importlib-metadata>=3.6.0; python_version < \"3.10\"", + "markdown-it-py<3.0.0,>=1.0.0b2", + "tomli>=1.1.0", +] + +[[package]] +name = "mdformat-tables" +version = "0.4.1" +requires_python = ">=3.6.1" +summary = "An mdformat plugin for rendering tables." +dependencies = [ + "mdformat<0.8.0,>=0.7.5", +] + +[[package]] +name = "mdurl" +version = "0.1.0" +requires_python = ">=3.6" +summary = "Markdown URL utilities" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +summary = "Experimental type system extensions for programs checked with the mypy typechecker." + +[[package]] +name = "mysqlclient" +version = "2.1.0" +requires_python = ">=3.5" +summary = "Python interface to MySQL" + +[[package]] +name = "pathspec" +version = "0.9.0" +summary = "Utility library for gitignore style pattern matching of file paths." + +[[package]] +name = "pillow" +version = "9.0.1" +requires_python = ">=3.7" +summary = "Python Imaging Library (Fork)" + +[[package]] +name = "platformdirs" +version = "2.5.0" +requires_python = ">=3.7" +summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." + +[[package]] +name = "pyasn1" +version = "0.4.8" +summary = "ASN.1 types and codecs" + +[[package]] +name = "pyasn1-modules" +version = "0.2.8" +summary = "A collection of ASN.1-based protocols modules." +dependencies = [ + "pyasn1<0.5.0,>=0.4.6", +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] + +[[package]] +name = "python-ldap" +version = "3.4.0" +requires_python = ">=3.6" +summary = "Python modules for implementing LDAP clients" +dependencies = [ + "pyasn1-modules>=0.1.5", + "pyasn1>=0.3.7", +] + +[[package]] +name = "pyyaml" +version = "6.0" +requires_python = ">=3.6" +summary = "YAML parser and emitter for Python" + +[[package]] +name = "regex" +version = "2022.1.18" +summary = "Alternative regular expression module, to replace re." + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" + +[[package]] +name = "soupsieve" +version = "2.3.1" +requires_python = ">=3.6" +summary = "A modern CSS selector implementation for Beautiful Soup." + +[[package]] +name = "sqlparse" +version = "0.4.2" +requires_python = ">=3.5" +summary = "A non-validating SQL parser." + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" + +[[package]] +name = "tomlkit" +version = "0.8.0" +requires_python = ">=3.6,<4.0" +summary = "Style preserving TOML library" + +[[package]] +name = "tqdm" +version = "4.62.3" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +summary = "Fast, Extensible Progress Meter" +dependencies = [ + "colorama; platform_system == \"Windows\"", +] + +[[package]] +name = "typing-extensions" +version = "4.1.1" +requires_python = ">=3.6" +summary = "Backported and Experimental Type Hints for Python 3.6+" + +[[package]] +name = "tzdata" +version = "2021.5" +requires_python = ">=2" +summary = "Provider of IANA time zone data" + +[[package]] +name = "uvicorn" +version = "0.17.4" +requires_python = ">=3.7" +summary = "The lightning-fast ASGI server." +dependencies = [ + "asgiref>=3.4.0", + "click>=7.0", + "h11>=0.8", +] + +[[package]] +name = "zipp" +version = "3.7.0" +requires_python = ">=3.7" +summary = "Backport of pathlib-compatible object wrapper for zip files" + +[metadata] +lock_version = "3.1" +content_hash = "sha256:46cf64b2180e5ba20253e265677b5c645952556baa19c194c904fa3a8af42741" + +[metadata.files] +"asgiref 3.5.0" = [ + {file = "asgiref-3.5.0-py3-none-any.whl", hash = "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9"}, + {file = "asgiref-3.5.0.tar.gz", hash = "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0"}, +] +"attrs 21.4.0" = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +"beautifulsoup4 4.10.0" = [ + {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, + {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, +] +"black 22.1.0" = [ + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +] +"click 8.0.3" = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +"colorama 0.4.4" = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +"django 4.0.2" = [ + {file = "Django-4.0.2-py3-none-any.whl", hash = "sha256:996495c58bff749232426c88726d8cd38d24c94d7c1d80835aafffa9bc52985a"}, + {file = "Django-4.0.2.tar.gz", hash = "sha256:110fb58fb12eca59e072ad59fc42d771cd642dd7a2f2416582aa9da7a8ef954a"}, +] +"django-admin-logs 1.0.2" = [ + {file = "django_admin_logs-1.0.2-py3-none-any.whl", hash = "sha256:81753c20d372bc5562fe4a09090418bbb61b308388e851b19192873a472fa3d1"}, + {file = "django-admin-logs-1.0.2.tar.gz", hash = "sha256:aedb5df940d32c10423d65136343bc009727df8a5a49ed0196e65241d823a890"}, +] +"django-auth-ldap 4.0.0" = [ + {file = "django_auth_ldap-4.0.0-py3-none-any.whl", hash = "sha256:94119c94981809124d3dc4bed974f71c7a980666896df626f556a88a5fe0b59c"}, + {file = "django-auth-ldap-4.0.0.tar.gz", hash = "sha256:276f79e624ce083ce13f161387f65ff1c0efe83ef8a42f2b9830d43317b15239"}, +] +"django-markdownx 4.0.0b1" = [ + {file = "django_markdownx-4.0.0b1-py2.py3-none-any.whl", hash = "sha256:e862ddc3e0aa8a2d6bb297fcf115a35b36b874ea5f07463577f1b34aa5073c3e"}, + {file = "django-markdownx-4.0.0b1.tar.gz", hash = "sha256:4f0ee12c38a9aeab9f8da0588ca169ec32c4dad84fc90dc4e9f56481b5de3473"}, +] +"django-recurrence 1.11.1" = [ + {file = "django_recurrence-1.11.1-py3-none-any.whl", hash = "sha256:0c65f30872599b5813a9bab6952dada23c55894f28674490a753ada559f14bc5"}, + {file = "django-recurrence-1.11.1.tar.gz", hash = "sha256:9c89444e651a78c587f352c5f63eda48ab2f53996347b9fcdff2d248f4fcff70"}, +] +"django-widget-tweaks 1.4.12" = [ + {file = "django_widget_tweaks-1.4.12-py3-none-any.whl", hash = "sha256:fe6b17d5d595c63331f300917980db2afcf71f240ab9341b954aea8f45d25b9a"}, + {file = "django-widget-tweaks-1.4.12.tar.gz", hash = "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e"}, +] +"djlint 0.7.4" = [ + {file = "djlint-0.7.4-py3-none-any.whl", hash = "sha256:1bced1cf7b0712b00d33a8df8418aff56ccea01d21b79a94ca6e876cf763e6b5"}, + {file = "djlint-0.7.4.tar.gz", hash = "sha256:aad18147db996cb93d63493126ad29d13251f60e6e45f98ab0e9b6252875ccf0"}, +] +"h11 0.13.0" = [ + {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, + {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, +] +"importlib-metadata 4.11.0" = [ + {file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"}, + {file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"}, +] +"markdown 3.3.6" = [ + {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, + {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, +] +"markdown-it-py 2.0.1" = [ + {file = "markdown_it_py-2.0.1-py3-none-any.whl", hash = "sha256:31974138ca8cafbcb62213f4974b29571b940e78364584729233f59b8dfdb8bd"}, + {file = "markdown-it-py-2.0.1.tar.gz", hash = "sha256:7b5c153ae1ab2cde00a33938bce68f3ad5d68fbe363f946de7d28555bed4e08a"}, +] +"markdownify 0.10.3" = [ + {file = "markdownify-0.10.3-py3-none-any.whl", hash = "sha256:edad0ad3896ec7460d05537ad804bbb3614877c6cd0df27b56dee218236d9ce2"}, + {file = "markdownify-0.10.3.tar.gz", hash = "sha256:782e310390cd5e4bde7543ceb644598c78b9824ee9f8d7ef9f9f4f8782e46974"}, +] +"mdformat 0.7.13" = [ + {file = "mdformat-0.7.13-py3-none-any.whl", hash = "sha256:accca5fb17da270b63d27ce05c86eba1e8dd2a0342e9c7bb4cff4e50040b65bb"}, + {file = "mdformat-0.7.13.tar.gz", hash = "sha256:28ede5e435ba84154e332edced33ee24fa30453d8fcbfbf7e41cd126a0851112"}, +] +"mdformat-tables 0.4.1" = [ + {file = "mdformat_tables-0.4.1-py3-none-any.whl", hash = "sha256:981f3dc7350027f78e3fd6a5fe8a16e123eec423af2d140e588d855751501019"}, + {file = "mdformat_tables-0.4.1.tar.gz", hash = "sha256:3024e88e9d29d7b8bb07fd6b59c9d5dcf14d2060122be29e30e72d27b65d7da9"}, +] +"mdurl 0.1.0" = [ + {file = "mdurl-0.1.0-py3-none-any.whl", hash = "sha256:40654d6dcb8d21501ed13c21cc0bd6fc42ff07ceb8be30029e5ae63ebc2ecfda"}, + {file = "mdurl-0.1.0.tar.gz", hash = "sha256:94873a969008ee48880fb21bad7de0349fef529f3be178969af5817239e9b990"}, +] +"mypy-extensions 0.4.3" = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +"mysqlclient 2.1.0" = [ + {file = "mysqlclient-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947"}, + {file = "mysqlclient-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b62d23c11c516cedb887377c8807628c1c65d57593b57853186a6ee18b0c6a5b"}, + {file = "mysqlclient-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2c8410f54492a3d2488a6a53e2d85b7e016751a1e7d116e7aea9c763f59f5e8c"}, + {file = "mysqlclient-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6279263d5a9feca3e0edbc2b2a52c057375bf301d47da2089c075ff76331d14"}, + {file = "mysqlclient-2.1.0.tar.gz", hash = "sha256:973235686f1b720536d417bf0a0d39b4ab3d5086b2b6ad5e6752393428c02b12"}, +] +"pathspec 0.9.0" = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +"pillow 9.0.1" = [ + {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"}, + {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"}, + {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"}, + {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"}, + {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"}, + {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"}, + {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"}, + {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"}, + {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"}, + {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"}, + {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"}, + {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"}, + {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"}, + {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"}, + {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"}, + {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"}, + {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"}, + {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"}, + {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"}, + {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"}, + {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"}, + {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"}, +] +"platformdirs 2.5.0" = [ + {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, + {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, +] +"pyasn1 0.4.8" = [ + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +"pyasn1-modules 0.2.8" = [ + {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, + {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, +] +"python-dateutil 2.8.2" = [ + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, +] +"python-ldap 3.4.0" = [ + {file = "python-ldap-3.4.0.tar.gz", hash = "sha256:60464c8fc25e71e0fd40449a24eae482dcd0fb7fcf823e7de627a6525b3e0d12"}, +] +"pyyaml 6.0" = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +"regex 2022.1.18" = [ + {file = "regex-2022.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34316bf693b1d2d29c087ee7e4bb10cdfa39da5f9c50fa15b07489b4ab93a1b5"}, + {file = "regex-2022.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a0b9f6a1a15d494b35f25ed07abda03209fa76c33564c09c9e81d34f4b919d7"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f99112aed4fb7cee00c7f77e8b964a9b10f69488cdff626ffd797d02e2e4484f"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a2bf98ac92f58777c0fafc772bf0493e67fcf677302e0c0a630ee517a43b949"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8618d9213a863c468a865e9d2ec50221015f7abf52221bc927152ef26c484b4c"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b52cc45e71657bc4743a5606d9023459de929b2a198d545868e11898ba1c3f59"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e12949e5071c20ec49ef00c75121ed2b076972132fc1913ddf5f76cae8d10b4"}, + {file = "regex-2022.1.18-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b02e3e72665cd02afafb933453b0c9f6c59ff6e3708bd28d0d8580450e7e88af"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:abfcb0ef78df0ee9df4ea81f03beea41849340ce33a4c4bd4dbb99e23ec781b6"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6213713ac743b190ecbf3f316d6e41d099e774812d470422b3a0f137ea635832"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:61ebbcd208d78658b09e19c78920f1ad38936a0aa0f9c459c46c197d11c580a0"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b013f759cd69cb0a62de954d6d2096d648bc210034b79b1881406b07ed0a83f9"}, + {file = "regex-2022.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9187500d83fd0cef4669385cbb0961e227a41c0c9bc39219044e35810793edf7"}, + {file = "regex-2022.1.18-cp310-cp310-win32.whl", hash = "sha256:94c623c331a48a5ccc7d25271399aff29729fa202c737ae3b4b28b89d2b0976d"}, + {file = "regex-2022.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:1a171eaac36a08964d023eeff740b18a415f79aeb212169080c170ec42dd5184"}, + {file = "regex-2022.1.18-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:49810f907dfe6de8da5da7d2b238d343e6add62f01a15d03e2195afc180059ed"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2f5c3f7057530afd7b739ed42eb04f1011203bc5e4663e1e1d01bb50f813e3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85ffd6b1cb0dfb037ede50ff3bef80d9bf7fa60515d192403af6745524524f3b"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba37f11e1d020969e8a779c06b4af866ffb6b854d7229db63c5fdddfceaa917f"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e27ea1ebe4a561db75a880ac659ff439dec7f55588212e71700bb1ddd5af9"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37978254d9d00cda01acc1997513f786b6b971e57b778fbe7c20e30ae81a97f3"}, + {file = "regex-2022.1.18-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54a1eb9fd38f2779e973d2f8958fd575b532fe26013405d1afb9ee2374e7ab8"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:768632fd8172ae03852e3245f11c8a425d95f65ff444ce46b3e673ae5b057b74"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de2923886b5d3214be951bc2ce3f6b8ac0d6dfd4a0d0e2a4d2e5523d8046fdfb"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1333b3ce73269f986b1fa4d5d395643810074dc2de5b9d262eb258daf37dc98f"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:d19a34f8a3429bd536996ad53597b805c10352a8561d8382e05830df389d2b43"}, + {file = "regex-2022.1.18-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d2f355a951f60f0843f2368b39970e4667517e54e86b1508e76f92b44811a8a"}, + {file = "regex-2022.1.18-cp36-cp36m-win32.whl", hash = "sha256:2245441445099411b528379dee83e56eadf449db924648e5feb9b747473f42e3"}, + {file = "regex-2022.1.18-cp36-cp36m-win_amd64.whl", hash = "sha256:25716aa70a0d153cd844fe861d4f3315a6ccafce22b39d8aadbf7fcadff2b633"}, + {file = "regex-2022.1.18-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e070d3aef50ac3856f2ef5ec7214798453da878bb5e5a16c16a61edf1817cc3"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22709d701e7037e64dae2a04855021b62efd64a66c3ceed99dfd684bfef09e38"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9099bf89078675c372339011ccfc9ec310310bf6c292b413c013eb90ffdcafc"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04611cc0f627fc4a50bc4a9a2e6178a974c6a6a4aa9c1cca921635d2c47b9c87"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552a39987ac6655dad4bf6f17dd2b55c7b0c6e949d933b8846d2e312ee80005a"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e031899cb2bc92c0cf4d45389eff5b078d1936860a1be3aa8c94fa25fb46ed8"}, + {file = "regex-2022.1.18-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2dacb3dae6b8cc579637a7b72f008bff50a94cde5e36e432352f4ca57b9e54c4"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e5c31d70a478b0ca22a9d2d76d520ae996214019d39ed7dd93af872c7f301e52"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb804c7d0bfbd7e3f33924ff49757de9106c44e27979e2492819c16972ec0da2"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:36b2d700a27e168fa96272b42d28c7ac3ff72030c67b32f37c05616ebd22a202"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:16f81025bb3556eccb0681d7946e2b35ff254f9f888cff7d2120e8826330315c"}, + {file = "regex-2022.1.18-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:da80047524eac2acf7c04c18ac7a7da05a9136241f642dd2ed94269ef0d0a45a"}, + {file = "regex-2022.1.18-cp37-cp37m-win32.whl", hash = "sha256:6ca45359d7a21644793de0e29de497ef7f1ae7268e346c4faf87b421fea364e6"}, + {file = "regex-2022.1.18-cp37-cp37m-win_amd64.whl", hash = "sha256:38289f1690a7e27aacd049e420769b996826f3728756859420eeee21cc857118"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6014038f52b4b2ac1fa41a58d439a8a00f015b5c0735a0cd4b09afe344c94899"}, + {file = "regex-2022.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b5d6f9aed3153487252d00a18e53f19b7f52a1651bc1d0c4b5844bc286dfa52"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d24b03daf7415f78abc2d25a208f234e2c585e5e6f92f0204d2ab7b9ab48e3"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf594cc7cc9d528338d66674c10a5b25e3cde7dd75c3e96784df8f371d77a298"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd914db437ec25bfa410f8aa0aa2f3ba87cdfc04d9919d608d02330947afaeab"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90b6840b6448203228a9d8464a7a0d99aa8fa9f027ef95fe230579abaf8a6ee1"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11772be1eb1748e0e197a40ffb82fb8fd0d6914cd147d841d9703e2bef24d288"}, + {file = "regex-2022.1.18-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a602bdc8607c99eb5b391592d58c92618dcd1537fdd87df1813f03fed49957a6"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7e26eac9e52e8ce86f915fd33380f1b6896a2b51994e40bb094841e5003429b4"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:519c0b3a6fbb68afaa0febf0d28f6c4b0a1074aefc484802ecb9709faf181607"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3c7ea86b9ca83e30fa4d4cd0eaf01db3ebcc7b2726a25990966627e39577d729"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51f02ca184518702975b56affde6c573ebad4e411599005ce4468b1014b4786c"}, + {file = "regex-2022.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:385ccf6d011b97768a640e9d4de25412204fbe8d6b9ae39ff115d4ff03f6fe5d"}, + {file = "regex-2022.1.18-cp38-cp38-win32.whl", hash = "sha256:1f8c0ae0a0de4e19fddaaff036f508db175f6f03db318c80bbc239a1def62d02"}, + {file = "regex-2022.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:760c54ad1b8a9b81951030a7e8e7c3ec0964c1cb9fee585a03ff53d9e531bb8e"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93c20777a72cae8620203ac11c4010365706062aa13aaedd1a21bb07adbb9d5d"}, + {file = "regex-2022.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6aa427c55a0abec450bca10b64446331b5ca8f79b648531138f357569705bc4a"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38baee6bdb7fe1b110b6b3aaa555e6e872d322206b7245aa39572d3fc991ee4"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:752e7ddfb743344d447367baa85bccd3629c2c3940f70506eb5f01abce98ee68"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8acef4d8a4353f6678fd1035422a937c2170de58a2b29f7da045d5249e934101"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c73d2166e4b210b73d1429c4f1ca97cea9cc090e5302df2a7a0a96ce55373f1c"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24c89346734a4e4d60ecf9b27cac4c1fee3431a413f7aa00be7c4d7bbacc2c4d"}, + {file = "regex-2022.1.18-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:596f5ae2eeddb79b595583c2e0285312b2783b0ec759930c272dbf02f851ff75"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ecfe51abf7f045e0b9cdde71ca9e153d11238679ef7b5da6c82093874adf3338"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1d6301f5288e9bdca65fab3de6b7de17362c5016d6bf8ee4ba4cbe833b2eda0f"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:93cce7d422a0093cfb3606beae38a8e47a25232eea0f292c878af580a9dc7605"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cf0db26a1f76aa6b3aa314a74b8facd586b7a5457d05b64f8082a62c9c49582a"}, + {file = "regex-2022.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:defa0652696ff0ba48c8aff5a1fac1eef1ca6ac9c660b047fc8e7623c4eb5093"}, + {file = "regex-2022.1.18-cp39-cp39-win32.whl", hash = "sha256:6db1b52c6f2c04fafc8da17ea506608e6be7086715dab498570c3e55e4f8fbd1"}, + {file = "regex-2022.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:ebaeb93f90c0903233b11ce913a7cb8f6ee069158406e056f884854c737d2442"}, + {file = "regex-2022.1.18.tar.gz", hash = "sha256:97f32dc03a8054a4c4a5ab5d761ed4861e828b2c200febd4e46857069a483916"}, +] +"six 1.16.0" = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +"soupsieve 2.3.1" = [ + {file = "soupsieve-2.3.1-py3-none-any.whl", hash = "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb"}, + {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"}, +] +"sqlparse 0.4.2" = [ + {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, + {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, +] +"tomli 2.0.1" = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +"tomlkit 0.8.0" = [ + {file = "tomlkit-0.8.0-py3-none-any.whl", hash = "sha256:b824e3466f1d475b2b5f1c392954c6cb7ea04d64354ff7300dc7c14257dc85db"}, + {file = "tomlkit-0.8.0.tar.gz", hash = "sha256:29e84a855712dfe0e88a48f6d05c21118dbafb283bb2eed614d46f80deb8e9a1"}, +] +"tqdm 4.62.3" = [ + {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, + {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, +] +"typing-extensions 4.1.1" = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, +] +"tzdata 2021.5" = [ + {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"}, + {file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"}, +] +"uvicorn 0.17.4" = [ + {file = "uvicorn-0.17.4-py3-none-any.whl", hash = "sha256:e85872d84fb651cccc4c5d2a71cf7ead055b8fb4d8f1e78e36092282c0cf2aec"}, + {file = "uvicorn-0.17.4.tar.gz", hash = "sha256:25850bbc86195a71a6477b3e4b3b7b4c861fb687fb96912972ce5324472b1011"}, +] +"zipp 3.7.0" = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/pyproject.toml b/pyproject.toml index fc61384..89cc1ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,49 @@ +[project] +name = "CMS Management" +version = "0.1.0" +description = "" +authors = [ + {name = "Adam Goldsmith", email = "contact@adamgoldsmith.name"}, +] +dependencies = [ + "django~=4.0", + "django-admin-logs~=1.0", + "django-auth-ldap~=4.0", + "django-markdownx<5,>=4.0.0b1", + "django-recurrence~=1.11", + "django-widget-tweaks~=1.4", + "markdownify~=0.10", + "mdformat~=0.7", + "mdformat-tables~=0.4", + "mysqlclient~=2.1", +] +requires-python = ">=3.9" + +[project.optional-dependencies] +server = ["uvicorn~=0.17"] + [tool.black] -extend-exclude = "/migrations/.*.py" [tool.djlint] extension = ".dj.html" indent = 2 blank_line_after_tag = "load,extends" ignore = "T003,H017,H030,H031" + +[[tool.pdm.source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[tool.pdm.dev-dependencies] +lint = [ + "black~=22.1", + "djlint~=0.7", +] + +[tool.pdm.scripts] +start = "./manage.py runserver" + +[build-system] +requires = ["pdm-pep517"] +build-backend = "pdm.pep517.api" diff --git a/rentals/migrations/0001_initial.py b/rentals/migrations/0001_initial.py index 3f78ee2..dc6b5c7 100644 --- a/rentals/migrations/0001_initial.py +++ b/rentals/migrations/0001_initial.py @@ -16,41 +16,95 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='LockerBank', + name="LockerBank", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('location', models.CharField(max_length=200)), - ('slug', models.SlugField(unique=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("location", models.CharField(max_length=200)), + ("slug", models.SlugField(unique=True)), ], ), migrations.CreateModel( - name='LockerUnit', + name="LockerUnit", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('index', models.PositiveIntegerField()), - ('first_letter', models.CharField(max_length=1, unique=True, validators=[django.core.validators.RegexValidator('[A-Z]')])), - ('first_number', models.PositiveIntegerField()), - ('rows', models.PositiveIntegerField(default=5)), - ('columns', models.PositiveIntegerField(default=2)), - ('bank', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='units', to='rentals.lockerbank')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("index", models.PositiveIntegerField()), + ( + "first_letter", + models.CharField( + max_length=1, + unique=True, + validators=[django.core.validators.RegexValidator("[A-Z]")], + ), + ), + ("first_number", models.PositiveIntegerField()), + ("rows", models.PositiveIntegerField(default=5)), + ("columns", models.PositiveIntegerField(default=2)), + ( + "bank", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="units", + to="rentals.lockerbank", + ), + ), ], options={ - 'ordering': ['index'], + "ordering": ["index"], }, ), migrations.CreateModel( - name='LockerRental', + name="LockerRental", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('column', models.PositiveIntegerField()), - ('row', models.PositiveIntegerField()), - ('locker_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rentals', to='rentals.lockerunit')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("column", models.PositiveIntegerField()), + ("row", models.PositiveIntegerField()), + ( + "locker_unit", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="rentals", + to="rentals.lockerunit", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AddConstraint( - model_name='lockerunit', - constraint=models.UniqueConstraint(fields=('bank', 'index'), name='unique_bank_index'), + model_name="lockerunit", + constraint=models.UniqueConstraint( + fields=("bank", "index"), name="unique_bank_index" + ), ), ] diff --git a/tasks/clean_markdown.py b/tasks/clean_markdown.py new file mode 100644 index 0000000..26ccebd --- /dev/null +++ b/tasks/clean_markdown.py @@ -0,0 +1,27 @@ +import bleach +from markdownx.utils import markdownify + +# fmt: off +MARKDOWN_TAGS = [ + "h1", "h2", "h3", "h4", "h5", "h6", + "b", "i", "strong", "em", "tt", "sub", "sup", + "p", "br", "span", "div", + "blockquote", "code", "pre", + "hr", + "ul", "ol", "li", + "dl", "dd", "dt", + "img", + "a", + "table", "thead", "tbody", "tr", "th", "td", +] + +MARKDOWN_ATTRS = { + "*": ["id"], + "img": ["src", "alt", "title"], + "a": ["href", "alt", "title"], +} + + +def markdown_to_clean_html(md: str) -> str: + x = bleach.clean(markdownify(md), tags=MARKDOWN_TAGS, attributes=MARKDOWN_ATTRS) + return x diff --git a/tasks/migrations/0001_initial.py b/tasks/migrations/0001_initial.py index 67c1da9..f19b370 100644 --- a/tasks/migrations/0001_initial.py +++ b/tasks/migrations/0001_initial.py @@ -12,65 +12,141 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0012_alter_user_first_name_max_length'), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='Tool', + name="Tool", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('asset_tag', models.CharField(blank=True, max_length=10, unique=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("asset_tag", models.CharField(blank=True, max_length=10, unique=True)), ], ), migrations.CreateModel( - name='Task', + name="Task", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200)), - ('slug', models.SlugField()), - ('description', markdownx.models.MarkdownxField(blank=True)), - ('recurrence_interval', models.CharField(max_length=200)), - ('recurrence_base', models.DateField(blank=True, null=True)), - ('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tool')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200)), + ("slug", models.SlugField()), + ("description", markdownx.models.MarkdownxField(blank=True)), + ("recurrence_interval", models.CharField(max_length=200)), + ("recurrence_base", models.DateField(blank=True, null=True)), + ( + "tool", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="tasks.tool" + ), + ), ], options={ - 'unique_together': {('tool', 'slug')}, + "unique_together": {("tool", "slug")}, }, ), migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField()), - ('notes', markdownx.models.MarkdownxField(blank=True)), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField()), + ("notes", markdownx.models.MarkdownxField(blank=True)), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="tasks.task" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='GroupToolSubscription', + name="GroupToolSubscription", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('days_before', models.PositiveIntegerField()), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), - ('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tool')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("days_before", models.PositiveIntegerField()), + ( + "group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="auth.group" + ), + ), + ( + "tool", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="tasks.tool" + ), + ), ], options={ - 'unique_together': {('group', 'tool')}, + "unique_together": {("group", "tool")}, }, ), migrations.CreateModel( - name='GroupTaskSubscription', + name="GroupTaskSubscription", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('days_before', models.PositiveIntegerField()), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("days_before", models.PositiveIntegerField()), + ( + "group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="auth.group" + ), + ), + ( + "task", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="tasks.task" + ), + ), ], options={ - 'unique_together': {('group', 'task')}, + "unique_together": {("group", "task")}, }, ), ] diff --git a/tasks/migrations/0002_tool_slug.py b/tasks/migrations/0002_tool_slug.py index 5df6d22..a352013 100644 --- a/tasks/migrations/0002_tool_slug.py +++ b/tasks/migrations/0002_tool_slug.py @@ -3,27 +3,28 @@ from django.utils.text import slugify def slugify_name(apps, schema_editor): - Tool = apps.get_model('tasks', 'tool') + Tool = apps.get_model("tasks", "tool") for tool in Tool.objects.all(): tool.slug = slugify(tool.name) - tool.save(update_fields=['slug']) + tool.save(update_fields=["slug"]) + class Migration(migrations.Migration): dependencies = [ - ('tasks', '0001_initial'), + ("tasks", "0001_initial"), ] operations = [ migrations.AddField( - model_name='tool', - name='slug', + model_name="tool", + name="slug", field=models.SlugField(blank=True, null=True, unique=True), ), migrations.RunPython(slugify_name), migrations.AlterField( - model_name='tool', - name='slug', + model_name="tool", + name="slug", field=models.SlugField(unique=True), - ) + ), ] diff --git a/tasks/migrations/0003_task_recurrence.py b/tasks/migrations/0003_task_recurrence.py index 47e776e..0f9f141 100644 --- a/tasks/migrations/0003_task_recurrence.py +++ b/tasks/migrations/0003_task_recurrence.py @@ -4,28 +4,29 @@ from django.db import migrations import recurrence import recurrence.fields + def transfer_recurrence(apps, schema_editor): - Task = apps.get_model('tasks', 'task') + Task = apps.get_model("tasks", "task") for task in Task.objects.all(): - task.recurrence = recurrence.deserialize('RRULE:' + task.recurrence_interval) - task.save(update_fields=['recurrence']) + task.recurrence = recurrence.deserialize("RRULE:" + task.recurrence_interval) + task.save(update_fields=["recurrence"]) class Migration(migrations.Migration): dependencies = [ - ('tasks', '0002_tool_slug'), + ("tasks", "0002_tool_slug"), ] operations = [ migrations.AddField( - model_name='task', - name='recurrence', - field=recurrence.fields.RecurrenceField(default=''), + model_name="task", + name="recurrence", + field=recurrence.fields.RecurrenceField(default=""), preserve_default=False, ), migrations.RunPython(transfer_recurrence), migrations.RemoveField( - model_name='task', - name='recurrence_interval', + model_name="task", + name="recurrence_interval", ), ] diff --git a/tasks/models.py b/tasks/models.py index 278bbb6..82638e3 100644 --- a/tasks/models.py +++ b/tasks/models.py @@ -7,6 +7,8 @@ from django.urls import reverse from markdownx.models import MarkdownxField from recurrence.fields import RecurrenceField +from .clean_markdown import markdown_to_clean_html + class Tool(models.Model): name = models.CharField(max_length=200) @@ -37,6 +39,10 @@ class Task(models.Model): def get_absolute_url(self): return reverse("tasks:taskDetail", args=[self.tool.slug, self.slug]) + @property + def description_html(self): + return markdown_to_clean_html(self.description) + @property def last_event(self): return self.event_set.latest("date") @@ -121,5 +127,9 @@ class Event(models.Model): date = models.DateField() notes = MarkdownxField(blank=True) + @property + def notes_html(self): + return markdown_to_clean_html(self.notes) + def __str__(self): return f"{self.task}: {self.user} {self.date}" diff --git a/tasks/templates/tasks/taskDetail.dj.html b/tasks/templates/tasks/taskDetail.dj.html index 5140d94..2dd5219 100644 --- a/tasks/templates/tasks/taskDetail.dj.html +++ b/tasks/templates/tasks/taskDetail.dj.html @@ -1,6 +1,5 @@ {% extends "base.dj.html" %} -{% load markdownify %} {% load widget_tweaks %} {% block title %}{{ tool.name }} - {{ task.name }} | {{ block.super }}{% endblock %} @@ -66,7 +65,7 @@

Description

- {{ task.description|markdownify }} + {{ task.description_html|safe }}
{% if form.errors %}
@@ -123,7 +122,7 @@ {{ event.date }} {{ event.user }} - {{ event.notes|markdownify }} + {{ event.notes_html|safe }} {% endfor %} diff --git a/tasks/templates/tasks/toolDetail.dj.html b/tasks/templates/tasks/toolDetail.dj.html index 1990739..134dbca 100644 --- a/tasks/templates/tasks/toolDetail.dj.html +++ b/tasks/templates/tasks/toolDetail.dj.html @@ -1,7 +1,5 @@ {% extends "base.dj.html" %} -{% load markdownify %} - {% block title %}{{ tool }} | {{ block.super }}{% endblock %} {% block admin_link %} {% url 'admin:tasks_tool_change' tool.id %} @@ -43,7 +41,7 @@ {{ event.date }} {{ event.task.name }} {{ event.user }} - {{ event.notes|markdownify }} + {{ event.notes_html|safe }} {% endfor %} diff --git a/templates/base.dj.html b/templates/base.dj.html index e59a903..088adca 100644 --- a/templates/base.dj.html +++ b/templates/base.dj.html @@ -47,9 +47,13 @@
{% for message in messages %} {# TODO: should use tags correctly for bootstrap alerts #} -