Merge remote-tracking branch 'paperwork/main'

This commit is contained in:
Adam Goldsmith 2022-02-14 12:44:42 -05:00
commit 52feb2d0b2
43 changed files with 2491 additions and 690 deletions

5
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__ __pycache__/
/cmsmanage/settings/prod.py *.sqlite3
/__pypackages__/

5
.pdm.toml Normal file
View File

@ -0,0 +1,5 @@
[python]
path = "/usr/bin/python"
[strategy]
save = "compatible"

25
Pipfile
View File

@ -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"

554
Pipfile.lock generated
View File

@ -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"
}
}
}

2
cmsmanage/settings/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dev.py
prod.py

View File

@ -10,7 +10,6 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/ https://docs.djangoproject.com/en/3.1/ref/settings/
""" """
import os
from pathlib import Path from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -22,18 +21,20 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"tasks.apps.TasksConfig",
"rentals.apps.RentalsConfig",
"widget_tweaks",
"markdownx",
"markdownify",
"recurrence",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"django_admin_logs",
"widget_tweaks",
"markdownx",
"recurrence",
"tasks.apps.TasksConfig",
"rentals.apps.RentalsConfig",
"membershipworks.apps.MembershipworksConfig",
"paperwork.apps.PaperworkConfig",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -51,7 +52,7 @@ ROOT_URLCONF = "cmsmanage.urls"
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")], "DIRS": [Path(BASE_DIR) / "templates"],
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
"context_processors": [ "context_processors": [
@ -68,6 +69,10 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
WSGI_APPLICATION = "cmsmanage.wsgi.application" WSGI_APPLICATION = "cmsmanage.wsgi.application"
DATABASE_ROUTERS = [
"membershipworks.routers.MembershipWorksRouter",
"paperwork.routers.PaperworkRouter",
]
# Default URL to redirect to after authentication # Default URL to redirect to after authentication
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"

View File

@ -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",
}
}

View File

@ -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

View File

@ -1,5 +1,5 @@
import ldap import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType from django_auth_ldap.config import LDAPSearch, PosixGroupType, LDAPGroupQuery
from .base import * from .base import *
@ -27,6 +27,12 @@ AUTH_LDAP_USER_ATTR_MAP = {
"email": "mail", "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( AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org", "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org",
ldap.SCOPE_SUBTREE, ldap.SCOPE_SUBTREE,

View File

29
membershipworks/admin.py Normal file
View File

@ -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]

6
membershipworks/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MembershipworksConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "membershipworks"

View File

@ -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",
},
),
]

View File

154
membershipworks/models.py Normal file
View File

@ -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"
)
]

View File

@ -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

3
membershipworks/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
membershipworks/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
paperwork/__init__.py Normal file
View File

130
paperwork/admin.py Normal file
View File

@ -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)

5
paperwork/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class PaperworkConfig(AppConfig):
name = "paperwork"

View File

@ -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 <Certifications@ClaremontMakerSpace.org>",
to,
reply_to=["Claremont MakerSpace <Info@ClaremontMakerSpace.org>"],
)
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)

View File

@ -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",
},
),
]

View File

@ -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",
),
]

View File

193
paperwork/models.py Normal file
View File

@ -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"

22
paperwork/routers.py Normal file
View File

@ -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

View File

@ -0,0 +1,23 @@
<p>
The following Claremont MakerSpace Member Certifications have been issued:
</p>
<table border="1">
<tr>
<th>Certification</th>
<th>Version</th>
<th>Member</th>
<th>Department</th>
<th>Certified By</th>
<th>Date</th>
</tr>
{% for certification in certifications %}
<tr>
<td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td>
<td>{{ certification.member }}</td>
<td>{{ certification.certification_version.definition.department }}</td>
<td>{{ certification.certified_by }}</td>
<td>{{ certification.date }}</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,24 @@
<p>
Dear <b>{{ shop_lead_names|join:", " }}</b>:
</p>
<p>
The following Claremont MakerSpace Member Certifications have been issued for the <b>{{ department }}</b>:
</p>
<table border="1">
<tr>
<th>Certification</th>
<th>Version</th>
<th>Member</th>
<th>Certified By</th>
<th>Date</th>
</tr>
{% for certification in certifications %}
<tr>
<td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td>
<td>{{ certification.member }}</td>
<td>{{ certification.certified_by }}</td>
<td>{{ certification.date }}</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,24 @@
<p>
Dear <b>{{ member.first_name }}</b>:
</p>
<p>
You have been issued the following Claremont MakerSpace Member Certifications:
</p>
<table border="1">
<tr>
<th>Certification</th>
<th>Version</th>
<th>Department</th>
<th>Certified By</th>
<th>Date</th>
</tr>
{% for certification in certifications %}
<tr>
<td>{{ certification.certification_version.definition.certification_name }}</td>
<td>{{ certification.certification_version.version }}</td>
<td>{{ certification.certification_version.definition.department }}</td>
<td>{{ certification.certified_by }}</td>
<td>{{ certification.date }}</td>
</tr>
{% endfor %}
</table>

3
paperwork/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
paperwork/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

662
pdm.lock Normal file
View File

@ -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"},
]

View File

@ -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] [tool.black]
extend-exclude = "/migrations/.*.py"
[tool.djlint] [tool.djlint]
extension = ".dj.html" extension = ".dj.html"
indent = 2 indent = 2
blank_line_after_tag = "load,extends" blank_line_after_tag = "load,extends"
ignore = "T003,H017,H030,H031" 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"

View File

@ -16,41 +16,95 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='LockerBank', name="LockerBank",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200)), "id",
('location', models.CharField(max_length=200)), models.BigAutoField(
('slug', models.SlugField(unique=True)), 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( migrations.CreateModel(
name='LockerUnit', name="LockerUnit",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('index', models.PositiveIntegerField()), "id",
('first_letter', models.CharField(max_length=1, unique=True, validators=[django.core.validators.RegexValidator('[A-Z]')])), models.BigAutoField(
('first_number', models.PositiveIntegerField()), auto_created=True,
('rows', models.PositiveIntegerField(default=5)), primary_key=True,
('columns', models.PositiveIntegerField(default=2)), serialize=False,
('bank', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='units', to='rentals.lockerbank')), 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={ options={
'ordering': ['index'], "ordering": ["index"],
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='LockerRental', name="LockerRental",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('column', models.PositiveIntegerField()), "id",
('row', models.PositiveIntegerField()), models.BigAutoField(
('locker_unit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rentals', to='rentals.lockerunit')), auto_created=True,
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.AddConstraint(
model_name='lockerunit', model_name="lockerunit",
constraint=models.UniqueConstraint(fields=('bank', 'index'), name='unique_bank_index'), constraint=models.UniqueConstraint(
fields=("bank", "index"), name="unique_bank_index"
),
), ),
] ]

27
tasks/clean_markdown.py Normal file
View File

@ -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

View File

@ -12,65 +12,141 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0012_alter_user_first_name_max_length'), ("auth", "0012_alter_user_first_name_max_length"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Tool', name="Tool",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200)), "id",
('asset_tag', models.CharField(blank=True, max_length=10, unique=True)), 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( migrations.CreateModel(
name='Task', name="Task",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200)), "id",
('slug', models.SlugField()), models.BigAutoField(
('description', markdownx.models.MarkdownxField(blank=True)), auto_created=True,
('recurrence_interval', models.CharField(max_length=200)), primary_key=True,
('recurrence_base', models.DateField(blank=True, null=True)), serialize=False,
('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tool')), 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={ options={
'unique_together': {('tool', 'slug')}, "unique_together": {("tool", "slug")},
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Event', name="Event",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('date', models.DateField()), "id",
('notes', markdownx.models.MarkdownxField(blank=True)), models.BigAutoField(
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')), auto_created=True,
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 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( migrations.CreateModel(
name='GroupToolSubscription', name="GroupToolSubscription",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('days_before', models.PositiveIntegerField()), "id",
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), models.BigAutoField(
('tool', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tool')), 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={ options={
'unique_together': {('group', 'tool')}, "unique_together": {("group", "tool")},
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='GroupTaskSubscription', name="GroupTaskSubscription",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('days_before', models.PositiveIntegerField()), "id",
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group')), models.BigAutoField(
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')), 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={ options={
'unique_together': {('group', 'task')}, "unique_together": {("group", "task")},
}, },
), ),
] ]

View File

@ -3,27 +3,28 @@ from django.utils.text import slugify
def slugify_name(apps, schema_editor): def slugify_name(apps, schema_editor):
Tool = apps.get_model('tasks', 'tool') Tool = apps.get_model("tasks", "tool")
for tool in Tool.objects.all(): for tool in Tool.objects.all():
tool.slug = slugify(tool.name) tool.slug = slugify(tool.name)
tool.save(update_fields=['slug']) tool.save(update_fields=["slug"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('tasks', '0001_initial'), ("tasks", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='tool', model_name="tool",
name='slug', name="slug",
field=models.SlugField(blank=True, null=True, unique=True), field=models.SlugField(blank=True, null=True, unique=True),
), ),
migrations.RunPython(slugify_name), migrations.RunPython(slugify_name),
migrations.AlterField( migrations.AlterField(
model_name='tool', model_name="tool",
name='slug', name="slug",
field=models.SlugField(unique=True), field=models.SlugField(unique=True),
) ),
] ]

View File

@ -4,28 +4,29 @@ from django.db import migrations
import recurrence import recurrence
import recurrence.fields import recurrence.fields
def transfer_recurrence(apps, schema_editor): def transfer_recurrence(apps, schema_editor):
Task = apps.get_model('tasks', 'task') Task = apps.get_model("tasks", "task")
for task in Task.objects.all(): for task in Task.objects.all():
task.recurrence = recurrence.deserialize('RRULE:' + task.recurrence_interval) task.recurrence = recurrence.deserialize("RRULE:" + task.recurrence_interval)
task.save(update_fields=['recurrence']) task.save(update_fields=["recurrence"])
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('tasks', '0002_tool_slug'), ("tasks", "0002_tool_slug"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='task', model_name="task",
name='recurrence', name="recurrence",
field=recurrence.fields.RecurrenceField(default=''), field=recurrence.fields.RecurrenceField(default=""),
preserve_default=False, preserve_default=False,
), ),
migrations.RunPython(transfer_recurrence), migrations.RunPython(transfer_recurrence),
migrations.RemoveField( migrations.RemoveField(
model_name='task', model_name="task",
name='recurrence_interval', name="recurrence_interval",
), ),
] ]

View File

@ -7,6 +7,8 @@ from django.urls import reverse
from markdownx.models import MarkdownxField from markdownx.models import MarkdownxField
from recurrence.fields import RecurrenceField from recurrence.fields import RecurrenceField
from .clean_markdown import markdown_to_clean_html
class Tool(models.Model): class Tool(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
@ -37,6 +39,10 @@ class Task(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("tasks:taskDetail", args=[self.tool.slug, self.slug]) return reverse("tasks:taskDetail", args=[self.tool.slug, self.slug])
@property
def description_html(self):
return markdown_to_clean_html(self.description)
@property @property
def last_event(self): def last_event(self):
return self.event_set.latest("date") return self.event_set.latest("date")
@ -121,5 +127,9 @@ class Event(models.Model):
date = models.DateField() date = models.DateField()
notes = MarkdownxField(blank=True) notes = MarkdownxField(blank=True)
@property
def notes_html(self):
return markdown_to_clean_html(self.notes)
def __str__(self): def __str__(self):
return f"{self.task}: {self.user} {self.date}" return f"{self.task}: {self.user} {self.date}"

View File

@ -1,6 +1,5 @@
{% extends "base.dj.html" %} {% extends "base.dj.html" %}
{% load markdownify %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block title %}{{ tool.name }} - {{ task.name }} | {{ block.super }}{% endblock %} {% block title %}{{ tool.name }} - {{ task.name }} | {{ block.super }}{% endblock %}
@ -66,7 +65,7 @@
</section> </section>
<section> <section>
<h2>Description</h2> <h2>Description</h2>
{{ task.description|markdownify }} {{ task.description_html|safe }}
</section> </section>
{% if form.errors %} {% if form.errors %}
<div class="alert alert-warning"> <div class="alert alert-warning">
@ -123,7 +122,7 @@
<tr> <tr>
<td class="text-nowrap">{{ event.date }}</td> <td class="text-nowrap">{{ event.date }}</td>
<td>{{ event.user }}</td> <td>{{ event.user }}</td>
<td>{{ event.notes|markdownify }}</td> <td>{{ event.notes_html|safe }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -1,7 +1,5 @@
{% extends "base.dj.html" %} {% extends "base.dj.html" %}
{% load markdownify %}
{% block title %}{{ tool }} | {{ block.super }}{% endblock %} {% block title %}{{ tool }} | {{ block.super }}{% endblock %}
{% block admin_link %} {% block admin_link %}
{% url 'admin:tasks_tool_change' tool.id %} {% url 'admin:tasks_tool_change' tool.id %}
@ -43,7 +41,7 @@
<td class="text-nowrap">{{ event.date }}</td> <td class="text-nowrap">{{ event.date }}</td>
<td>{{ event.task.name }}</td> <td>{{ event.task.name }}</td>
<td>{{ event.user }}</td> <td>{{ event.user }}</td>
<td>{{ event.notes|markdownify }}</td> <td>{{ event.notes_html|safe }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -47,9 +47,13 @@
<div class="messages m-3"> <div class="messages m-3">
{% for message in messages %} {% for message in messages %}
{# TODO: should use tags correctly for bootstrap alerts #} {# TODO: should use tags correctly for bootstrap alerts #}
<div role="alert" class="alert alert-info alert-dismissible fade show {% if message.tags %} {{ message.tags }}{% endif %}"> <div role="alert"
class="alert alert-info alert-dismissible fade show {% if message.tags %} {{ message.tags }}{% endif %}">
{{ message }} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <button type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"></button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>