whole app

This commit is contained in:
2025-08-20 14:26:51 +02:00
commit a2a18bad24
16 changed files with 2101 additions and 0 deletions

16
Pipfile Normal file
View File

@@ -0,0 +1,16 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
sqlalchemy = "*"
mysql-connector-python = "*"
flake8 = "*"
flake8-html = "*"
pytest = "*"
[dev-packages]
[requires]
python_version = "3.10"

390
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,390 @@
{
"_meta": {
"hash": {
"sha256": "cfa8671212ad20e3d9752509b7d700143b3c1622bb7a13fc892bcfd86b888abd"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"exceptiongroup": {
"hashes": [
"sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10",
"sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"flake8": {
"hashes": [
"sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e",
"sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==7.3.0"
},
"flake8-html": {
"hashes": [
"sha256:8b870299620cc4a06f73644a1b4d457799abeca1cc914c62ae71ec5bf65c79a5",
"sha256:8f126748b1b0edd6cd39e87c6192df56e2f8655b0aa2bb00ffeac8cf27be4325"
],
"index": "pypi",
"version": "==0.4.3"
},
"greenlet": {
"hashes": [
"sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26",
"sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36",
"sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892",
"sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83",
"sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688",
"sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be",
"sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4",
"sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d",
"sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5",
"sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b",
"sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb",
"sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86",
"sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c",
"sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64",
"sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57",
"sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b",
"sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad",
"sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95",
"sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3",
"sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147",
"sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db",
"sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb",
"sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c",
"sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00",
"sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849",
"sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba",
"sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac",
"sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822",
"sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da",
"sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97",
"sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805",
"sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34",
"sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141",
"sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3",
"sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0",
"sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b",
"sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365",
"sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72",
"sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a",
"sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc",
"sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163",
"sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302",
"sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef",
"sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392",
"sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322",
"sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d",
"sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264",
"sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b",
"sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904",
"sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf",
"sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7",
"sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a",
"sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712",
"sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"
],
"markers": "python_version >= '3.9'",
"version": "==3.2.3"
},
"iniconfig": {
"hashes": [
"sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7",
"sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"
],
"markers": "python_version >= '3.8'",
"version": "==2.1.0"
},
"jinja2": {
"hashes": [
"sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d",
"sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.6"
},
"markupsafe": {
"hashes": [
"sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
"sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
"sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
"sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
"sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
"sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
"sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
"sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
"sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
"sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
"sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
"sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
"sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
"sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
"sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
"sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
"sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
"sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
"sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
"sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
"sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
"sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
"sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
"sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
"sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
"sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
"sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
"sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
"sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
"sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
"sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
"sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
"sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
"sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
"sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
"sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
"sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
"sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
"sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
"sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
"sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
"sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
"sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
"sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
"sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
"sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
"sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
"sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
"sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
"sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
"sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
"sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
"sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
"sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
"sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
"sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
"sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
"sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
"sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
"sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
"sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
],
"markers": "python_version >= '3.9'",
"version": "==3.0.2"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"mysql-connector-python": {
"hashes": [
"sha256:0aedee809e1f8dbab6b2732f51ee1619b54a56d15b9070655bc31fb822c1a015",
"sha256:1916256ecd039f4673715550d28138416bac5962335e06d36f7434c47feb5232",
"sha256:2589af070babdff9c920ee37f929218d80afa704f4e2a99f1ddcb13d19de4450",
"sha256:2a5de57814217077a8672063167b616b1034a37b614b93abcb602cc0b8c6fade",
"sha256:3853799f4b719357ea25eba05f5f278a158a85a5c8209b3d058947a948bc9262",
"sha256:4364d3a37c449f1c0bb9e52fd4eddc620126b9897b6b9f2fd1b3f33dacc16356",
"sha256:495798dd34445d749991fb3a2aa87b4205100676939556d8d4aab5d5558e7a1f",
"sha256:5508ff6b79d8d46b15791401784a1b5abd10c8e05aec2684c4a50e92c5893cd2",
"sha256:55d4a8ace6f97d58d9318d1250d903b0d3b100a6b798442a99c4ac966b974d12",
"sha256:66d48ec0ee903a84bcaf5d4d1901ed536fdd90ce6ecae0686f094b4530faf545",
"sha256:8ab7719d614cf5463521082fab86afc21ada504b538166090e00eeaa1ff729bc",
"sha256:8b16d51447e3603f18478fb5a19b333bfb73fb58f872eb055a105635f53d2345",
"sha256:8c79b500f1f9f12761426199d0498309ee5d20c94ed94fc8ae356679667f8181",
"sha256:9516a4cdbaee3c9200f0e7d9aafb31057692f45c202cdcb43a3f9b37c94e7c84",
"sha256:9c898c5f3e34314ed825f2ffdd52d674e03d59c45d02ac8083a8ec5173c1e0f8",
"sha256:9cc8d3c2f45d16b064b0063db857f8a7187b8659253dd32e3f19df1bf1d55ea0",
"sha256:ac70a7128f7e690dc0f4376be8366c7e5c8fa47a785232b8abba948576f016ff",
"sha256:be0ef15f6023ae2037347498f005a4471f694f8a6b8384c3194895e153120286",
"sha256:cb72fcda90b616f0b2d3dae257441e06e8896b2780c3dddc6a65275ec1408d9a",
"sha256:d33e2f88e1d4b15844cfed2bb6e90612525ba2c1af2fb10b4a25b2c89a1fe49a",
"sha256:d47a0d5b2b9b02f06647d5d7bbb19e237f234d6be91d0e0c935629faacf0797f",
"sha256:d87c9e8b5aa9a16cefebe017ee45ddfbad53e668f94d01fe2e055bb8daab9353",
"sha256:e24be22a5d96f3535afa5dd331166b02bf72655ea6ed6a2a0eb548c313548788",
"sha256:e8b0131006608e533b8eab20078f9e65486068c984ed3efd28413d350d241f44",
"sha256:ee1a901c287471013570e29cdf5ca7159898af31cf3a582180eadd41c96b42c9",
"sha256:f10fe89397e8da81026d8143e17fc5c12ae5e66e51753a0f49e1db179c4f7113",
"sha256:f979e712187796ad57cd0bef76666dd48ed4887104775833c9489ea837144ad8"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==9.3.0"
},
"packaging": {
"hashes": [
"sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484",
"sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"
],
"markers": "python_version >= '3.8'",
"version": "==25.0"
},
"pluggy": {
"hashes": [
"sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3",
"sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"
],
"markers": "python_version >= '3.9'",
"version": "==1.6.0"
},
"pycodestyle": {
"hashes": [
"sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783",
"sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"
],
"markers": "python_version >= '3.9'",
"version": "==2.14.0"
},
"pyflakes": {
"hashes": [
"sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58",
"sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"
],
"markers": "python_version >= '3.9'",
"version": "==3.4.0"
},
"pygments": {
"hashes": [
"sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887",
"sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"
],
"markers": "python_version >= '3.8'",
"version": "==2.19.2"
},
"pytest": {
"hashes": [
"sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7",
"sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==8.4.1"
},
"sqlalchemy": {
"hashes": [
"sha256:023b3ee6169969beea3bb72312e44d8b7c27c75b347942d943cf49397b7edeb5",
"sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582",
"sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b",
"sha256:087b6b52de812741c27231b5a3586384d60c353fbd0e2f81405a814b5591dc8b",
"sha256:0b3dbf1e7e9bc95f4bac5e2fb6d3fb2f083254c3fdd20a1789af965caf2d2348",
"sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda",
"sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5",
"sha256:1e3f196a0c59b0cae9a0cd332eb1a4bda4696e863f4f1cf84ab0347992c548c2",
"sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29",
"sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8",
"sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f",
"sha256:34ea30ab3ec98355235972dadc497bb659cc75f8292b760394824fab9cf39826",
"sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504",
"sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae",
"sha256:4d44522480e0bf34c3d63167b8cfa7289c1c54264c2950cc5fc26e7850967e45",
"sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443",
"sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23",
"sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576",
"sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1",
"sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0",
"sha256:5e22575d169529ac3e0a120cf050ec9daa94b6a9597993d1702884f6954a7d71",
"sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11",
"sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e",
"sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f",
"sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8",
"sha256:6ab60a5089a8f02009f127806f777fca82581c49e127f08413a66056bd9166dd",
"sha256:725875a63abf7c399d4548e686debb65cdc2549e1825437096a0af1f7e374814",
"sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08",
"sha256:81965cc20848ab06583506ef54e37cf15c83c7e619df2ad16807c03100745dea",
"sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30",
"sha256:81eedafa609917040d39aa9332e25881a8e7a0862495fcdf2023a9667209deda",
"sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9",
"sha256:8280856dd7c6a68ab3a164b4a4b1c51f7691f6d04af4d4ca23d6ecf2261b7923",
"sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df",
"sha256:8b4af17bda11e907c51d10686eda89049f9ce5669b08fbe71a29747f1e876036",
"sha256:90144d3b0c8b139408da50196c5cad2a6909b51b23df1f0538411cd23ffa45d3",
"sha256:906e6b0d7d452e9a98e5ab8507c0da791856b2380fdee61b765632bb8698026f",
"sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6",
"sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04",
"sha256:9a420a91913092d1e20c86a2f5f1fc85c1a8924dbcaf5e0586df8aceb09c9cc2",
"sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560",
"sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70",
"sha256:a373a400f3e9bac95ba2a06372c4fd1412a7cee53c37fc6c05f829bf672b8769",
"sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1",
"sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6",
"sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b",
"sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747",
"sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078",
"sha256:b50eab9994d64f4a823ff99a0ed28a6903224ddbe7fef56a6dd865eec9243440",
"sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f",
"sha256:c0b0e5e1b5d9f3586601048dd68f392dc0cc99a59bb5faf18aab057ce00d00b2",
"sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d",
"sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc",
"sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a",
"sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd",
"sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9",
"sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6"
],
"index": "pypi",
"markers": "python_version >= '3.7'",
"version": "==2.0.41"
},
"tomli": {
"hashes": [
"sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6",
"sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd",
"sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c",
"sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b",
"sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8",
"sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6",
"sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77",
"sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff",
"sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea",
"sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192",
"sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249",
"sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee",
"sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4",
"sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98",
"sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8",
"sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4",
"sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281",
"sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744",
"sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69",
"sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13",
"sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140",
"sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e",
"sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e",
"sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc",
"sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff",
"sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec",
"sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2",
"sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222",
"sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106",
"sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272",
"sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a",
"sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"
],
"markers": "python_version >= '3.8'",
"version": "==2.2.1"
},
"typing-extensions": {
"hashes": [
"sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36",
"sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"
],
"markers": "python_version >= '3.9'",
"version": "==4.14.1"
}
},
"develop": {}
}

0
__init__.py Normal file
View File

81
authentication.py Normal file
View File

@@ -0,0 +1,81 @@
from passlib.hash import argon2
from sqlalchemy import select
from sqlalchemy.orm import Session
from models import Collaborator, Credentials
import jwt
class PasswordTools:
"""
Tool used to manage passwords and interact with DB
"""
def __init__(self, db: Session):
self.db = db
def hash(self, password: str) -> str:
"""
:param password:
:return:
"""
return argon2.hash(password)
def get_by_name(self, username: str):
"""
Get the collaborator's name and return password hash associated if
existing
:param username: Collaborators.name
:return: Credential.password_hash
"""
if self.db.execute(
select(Collaborator).filter_by(name=username)).scalar():
sbq = select(Collaborator).where(
Collaborator.name == username).subquery()
stmt = select(Credentials).join(
sbq,
Credentials.collaborator_id == sbq.c.id)
return self.db.execute(stmt).scalar()
return {'message': "Wrong username"}
def check(self, username: str, password: str) -> bool:
# if self.db.scalars(
# select(Collaborator).filter_by(name=username)).all():
# sbq = select(Collaborator).where(
# Collaborator.name == username).subquery()
# stmt = select(Credentials).join(
# sbq,
# Credentials.collaborator_id == sbq.c.id)
# result = self.db.scalars(stmt).all()
# user_pw = result[0].password_hash
user = self.get_by_name(username)
if not user:
print("Wrong user")
return False
user_pw = user.password_hash
return argon2.verify(password, user_pw)
class TokenTools:
def __init__(self, username: str, password: str, team_id: int):
self.username = username
self.password = password
self.team_id = team_id
def get_token(self, username: str, password: str, team_id: int):
# team_id = Collaborator.get_team_by_name(self.username)
payload = {'user': username,
'password': password,
'team_id': team_id,
}
return jwt.encode(payload, password, algorithm="HS256")
def check_token(self):
pass
def store_token(self, token):
pass
class AuthTools:
def __init__(self, db: Session):
self.db = db

5
config.py Normal file
View File

@@ -0,0 +1,5 @@
user = "epicevents"
password = "m3gapassword"
user1 = "root"
pass1 = "proutproutprout"

66
controllers.py Normal file
View File

@@ -0,0 +1,66 @@
from menu import CommercialMenu, ManagementMenu, SupportMenu
from sqlalchemy.orm import Session
class App:
"""
Entry point of the application. Instantiate Tools which are meant to be
like kind of repository pattern to interact with data, and Menus then route
depending on team... sort of permission management using simple_term_menu
"""
def __init__(self,
db: Session,
view,
collaborator_tools,
customer_tools,
contract_tools,
event_tools,
passwd_tools,
tools):
self.db = db
self.view = view
self.passwd_tools = passwd_tools
self.collaborator_tools = collaborator_tools
self.customer_tools = customer_tools
self.contract_tools = contract_tools
self.event_tools = event_tools
self.tools = tools
def connect(self) -> tuple | None:
"""
Check if provided password exists in table credentials and verify
if matches with stored hash
:return: tuple(team_id:int, user_id:int) | None and print if no match
"""
username, password = self.view.prompt_connect()
if self.passwd_tools.check(username, password):
perm = self.collaborator_tools.get_team_by_name(username)
user_id = self.collaborator_tools.get_id_by_name(username)
return perm, user_id
else:
print("Connection failed")
return None
def start(self):
team, user_id = self.connect()
if not user_id:
exit()
if team == 1:
CommercialMenu(self.customer_tools,
self.contract_tools,
self.event_tools,
self.tools).launch()
if team == 2:
ManagementMenu(self.collaborator_tools,
self.customer_tools,
self.contract_tools,
self.event_tools,
self.tools).launch()
if team == 3:
SupportMenu(self.customer_tools,
self.contract_tools,
self.event_tools,
self.tools,
user_id).launch()

11
db.py Normal file
View File

@@ -0,0 +1,11 @@
from sqlalchemy import create_engine
from config import user1, pass1
from sqlalchemy.orm import sessionmaker
DB_URL = "mysql+mysqlconnector://"+user1+":"+pass1+"@localhost:3306/prout"
engine = create_engine(DB_URL, echo=False)
SessionLocal = sessionmaker(bind=engine)
session = SessionLocal()

73
flake-report/back.svg Normal file
View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="48"
viewBox="0 0 32 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="back.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#3d3d3d"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="47.245066"
inkscape:cy="13.218734"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
width="32px"
inkscape:window-width="1535"
inkscape:window-height="876"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1004.3622)">
<g
id="g5305"
transform="translate(0,-3.5)">
<path
inkscape:connector-curvature="0"
id="path5301"
d="M 15.577993,1039.1732 4.7040093,1028.3079 15.469253,1017.5512"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path5303"
d="m 4.921489,1028.3622 26.53252,0"
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

64
flake-report/file.svg Normal file
View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="48"
viewBox="0 0 32 48"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="file.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#3d3d3d"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="24.812518"
inkscape:cy="18.901073"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
width="32px"
inkscape:window-width="1535"
inkscape:window-height="876"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1004.3622)">
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 4.1360619,1007.3622 0,34.375 23.7278761,0 0,-29.9038 -4.471158,-4.4712 -19.2567181,0 z m 18.5469091,0.6084 4.471159,4.4712 -4.471159,0 0,-4.4712 z m -16.6202866,7.8079 19.8746316,0 0,1.8252 -19.8746316,0 0,-1.8252 z m 0,3.2448 19.8746316,0 0,1.8253 -19.8746316,0 0,-1.8253 z m 0,6.0841 19.8746316,0 0,1.8252 -19.8746316,0 0,-1.8252 z m 0,8.2135 19.8746316,0 0,1.8252 -19.8746316,0 0,-1.8252 z"
id="path4749"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

30
flake-report/index.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>flake8 violations</title>
<meta http-equiv="Content-Type" value="text/html; charset=UTF-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="masthead" class="sev-4"></div>
<div id="page">
<h1>flake8 violations</h1>
<p id="versions">Generated on 2025-08-19 17:29
with Installed plugins: flake8-html: 0.4.3, mccabe: 0.7.0, pycodestyle: 2.14.0, pyflakes: 3.4.0
</p>
<ul id="index">
<li>
<div id="all-good">
<span class="count sev-4">
<span class="tick">&#x2713;</span>
</span>
<h2>All good!</h2>
<p>No flake8 errors found in 10 files scanned.</p>
</div>
</li>
</ul>
</div>
</body>
</html>

327
flake-report/styles.css Normal file
View File

@@ -0,0 +1,327 @@
html {
font-family: sans-serif;
font-size: 90%;
}
#masthead {
position: fixed;
left: 0;
top: 0;
right: 0;
height: 40%;
}
h1, h2 {
font-family: sans-serif;
font-weight: normal;
}
h1 {
color: white;
font-size: 36px;
margin-top: 1em;
}
h1 img {
margin-right: 0.3em;
}
h2 {
margin-top: 0;
}
h1 a {
color: white;
}
#versions {
color: rgba(255, 255, 255, 0.7);
}
#page {
position: relative;
max-width: 960px;
margin: 0 auto;
}
#index {
background-color: white;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
padding: 0;
margin: 0;
}
#index li {
list-style: none;
margin: 0;
padding: 1px 0;
}
#index li + li {
border-top: solid silver 1px;
}
.details p {
margin-left: 3em;
color: #888;
}
#index a {
display: block;
padding: 0.8em 1em;
cursor: pointer;
}
#index #all-good {
padding: 1.4em 1em 0.8em;
}
#all-good .count .tick {
font-size: 2em;
}
#all-good .count {
float: left;
}
#all-good h2,
#all-good p {
margin-left: 50px;
}
#index a:hover {
background-color: #eee;
}
.count {
display: inline-block;
border-radius: 50%;
text-align: center;
width: 2.5em;
line-height: 2.5em;
height: 2.5em;
color: white;
margin-right: 1em;
}
.sev-1 {
background-color: #a00;
}
.sev-2 {
background-color: #b80;
}
.sev-3 {
background-color: #28c;
}
.sev-4 {
background-color: #383;
}
a {
text-decoration: none;
}
#doc {
background-color: white;
margin: 1em 0;
padding: 1em;
padding-left: 1.2em;
position: relative;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
}
#doc pre {
margin: 0;
padding: 0.07em;
}
.violations {
position: absolute;
margin: 1.2em 0 0 3em;
padding: 0.5em 1em;
font-size: 14px;
background-color: white;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
display: none;
}
.violations .count {
font-size: 70%;
}
.violations li {
padding: 0.1em 0.3em;
list-style: none;
}
.line-violations::before {
display: block;
content: "";
position: absolute;
left: -1em;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: red;
}
.code:hover .violations {
display: block;
}
tt {
white-space: pre-wrap;
font-family: Consolas, monospace;
font-size: 10pt;
}
tt i {
color: silver;
display: inline-block;
text-align: right;
width: 3em;
box-sizing: border-box;
height: 100%;
border-right: solid #eee 1px;
padding-right: 0.2em;
}
.le {
background-color: #ffe8e8;
cursor: pointer;
}
.le:hover {
background-color: #fcc;
}
.details {
clear: both;
}
#index .details {
border-top-style: none;
margin: 1em;
}
ul.details {
margin-left: 0;
padding-left: 0;
}
#index .details li {
list-style: none;
border-top-style: none;
margin: 0.3em 0;
padding: 0;
}
#srclink {
float: right;
font-size: 36px;
margin: 0;
}
#srclink a {
color: white;
}
#index .details a {
padding: 0;
color: inherit;
}
.le {
background-color: #ffe8e8;
cursor: pointer;
}
.le.sev-1 {
background-color: #f88;
}
.le.sev-2 {
background-color: #fda;
}
.le.sev-3 {
background-color: #adf;
}
img {
height: 1.2em;
vertical-align: -0.35em;
}
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.hll { background-color: #ffffcc }
.c { color: #3D7B7B; font-style: italic } /* Comment */
.err { border: 1px solid #F00 } /* Error */
.k { color: #008000; font-weight: bold } /* Keyword */
.o { color: #666 } /* Operator */
.ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
.cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
.cp { color: #9C6500 } /* Comment.Preproc */
.cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
.c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
.cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
.gd { color: #A00000 } /* Generic.Deleted */
.ge { font-style: italic } /* Generic.Emph */
.ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.gr { color: #E40000 } /* Generic.Error */
.gh { color: #000080; font-weight: bold } /* Generic.Heading */
.gi { color: #008400 } /* Generic.Inserted */
.go { color: #717171 } /* Generic.Output */
.gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.gs { font-weight: bold } /* Generic.Strong */
.gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.gt { color: #04D } /* Generic.Traceback */
.kc { color: #008000; font-weight: bold } /* Keyword.Constant */
.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
.kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
.kp { color: #008000 } /* Keyword.Pseudo */
.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
.kt { color: #B00040 } /* Keyword.Type */
.m { color: #666 } /* Literal.Number */
.s { color: #BA2121 } /* Literal.String */
.na { color: #687822 } /* Name.Attribute */
.nb { color: #008000 } /* Name.Builtin */
.nc { color: #00F; font-weight: bold } /* Name.Class */
.no { color: #800 } /* Name.Constant */
.nd { color: #A2F } /* Name.Decorator */
.ni { color: #717171; font-weight: bold } /* Name.Entity */
.ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
.nf { color: #00F } /* Name.Function */
.nl { color: #767600 } /* Name.Label */
.nn { color: #00F; font-weight: bold } /* Name.Namespace */
.nt { color: #008000; font-weight: bold } /* Name.Tag */
.nv { color: #19177C } /* Name.Variable */
.ow { color: #A2F; font-weight: bold } /* Operator.Word */
.w { color: #BBB } /* Text.Whitespace */
.mb { color: #666 } /* Literal.Number.Bin */
.mf { color: #666 } /* Literal.Number.Float */
.mh { color: #666 } /* Literal.Number.Hex */
.mi { color: #666 } /* Literal.Number.Integer */
.mo { color: #666 } /* Literal.Number.Oct */
.sa { color: #BA2121 } /* Literal.String.Affix */
.sb { color: #BA2121 } /* Literal.String.Backtick */
.sc { color: #BA2121 } /* Literal.String.Char */
.dl { color: #BA2121 } /* Literal.String.Delimiter */
.sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
.s2 { color: #BA2121 } /* Literal.String.Double */
.se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
.sh { color: #BA2121 } /* Literal.String.Heredoc */
.si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
.sx { color: #008000 } /* Literal.String.Other */
.sr { color: #A45A77 } /* Literal.String.Regex */
.s1 { color: #BA2121 } /* Literal.String.Single */
.ss { color: #19177C } /* Literal.String.Symbol */
.bp { color: #008000 } /* Name.Builtin.Pseudo */
.fm { color: #00F } /* Name.Function.Magic */
.vc { color: #19177C } /* Name.Variable.Class */
.vg { color: #19177C } /* Name.Variable.Global */
.vi { color: #19177C } /* Name.Variable.Instance */
.vm { color: #19177C } /* Name.Variable.Magic */
.il { color: #666 } /* Literal.Number.Integer.Long */

32
main.py Normal file
View File

@@ -0,0 +1,32 @@
import models
import tools
from db import engine, session
from controllers import App
from views import View
from authentication import PasswordTools
models.Base.metadata.create_all(bind=engine)
def main():
view = View()
collaborator_tools = tools.CollaboratorTools(session)
customer_tools = tools.CustomerTools(session)
contract_tools = tools.ContractTools(session)
event_tools = tools.EventTools(session)
passwd_tools = PasswordTools(session)
common_tools = tools.Tools(session)
with session:
App(session,
view,
collaborator_tools,
customer_tools,
contract_tools,
event_tools,
passwd_tools,
common_tools).start()
if __name__ == "__main__":
main()

351
menu.py Normal file
View File

@@ -0,0 +1,351 @@
from simple_term_menu import TerminalMenu
from models import Customer, Contract, Collaborator, Event
class Prompt:
def menu(self, options):
terminal_menu = TerminalMenu(options)
menu_entry_index = terminal_menu.show()
selection = options[menu_entry_index]
return selection
def exec_menu(self, dict_options):
selection = self.menu(list(dict_options.keys()))
selected_function = dict_options.get(selection)
selected_function()
def return_menu(self, dict_options):
selection = self.menu(list(dict_options.keys()))
return dict_options.get(selection)
class CommercialMenu:
"""
Main menu for commercial users displaying only specific submenus according
to permissions :
customers: CRUD
contracts: RU
events: CR
filter :
contract: signed
"""
def __init__(self, customer_tools, contract_tools, event_tools, tools):
self.customer_tools = customer_tools
self.contract_tools = contract_tools
self.event_tools = event_tools
self.tools = tools
self.prompt = Prompt()
def launch(self):
"""
Menu entry point for commercial user; display objects then submenu
:return: call submenus
"""
base = {
"Customer": self.customer_menu,
"Contract": self.contract_menu,
"Event": self.event_menu,
"Quit": quit,
}
while True:
self.prompt.exec_menu(base)
def customers_for_update(self):
"""
get all customers from db, create a dict options, call menu
and get choice (Customer.id)
:return: exec the update tool with the chosen id
"""
options = {}
for item in self.tools.list(Customer):
options[item[0].name] = item[0].id
choice = self.prompt.return_menu(options)
self.customer_tools.update(choice)
def customer_menu(self):
"""
display the CRUD menu for customer and get choice
:return: exec the function associated with chosen item
"""
customer_options = {
"List": self.customer_tools.list,
"Create": self.customer_tools.create,
"Update": self.customers_for_update,
"Delete": self.customer_tools.delete,
}
self.prompt.exec_menu(customer_options)
def contracts_for_update(self):
"""
get all contracts from db, create a dict options, call menu
and get choice (Contract.id)
:return: exec the update tool with the chosen id
"""
options = {}
for item in self.tools.list(Contract):
options["Contrat "+str(item[0].id)] = item[0].id
choice = self.prompt.return_menu(options)
self.contract_tools.update(choice)
def contract_menu(self):
"""
display the CRUD menu for contracts and get choice
:return: exec the function associated with chosen item
"""
contract_options = {
"List": self.contract_list,
"Update": self.contracts_for_update,
}
self.prompt.exec_menu(contract_options)
def contract_list(self):
"""
interface after CRUD menu : list meant to provide filter
here on signed attribute
:return: exec tool method which apply filter and call view to display
"""
contract_list_options = {
"Signed": self.contract_tools.signed,
"Not signed": self.contract_tools.not_signed,
}
self.prompt.exec_menu(contract_list_options)
def event_menu(self):
"""
display the CRUD menu for event and get choice
:return: exec the function associated with chosen item
"""
event_options = {
"List": self.event_tools.list,
"Create": self.event_tools.create,
}
self.prompt.exec_menu(event_options)
class ManagementMenu:
"""
Main menu for manager users displaying only specific submenus according
to permissions :
collaborators: CRUD
customers: R
contracts: CRU
events: RU
filter:
event: support_id
"""
def __init__(self, collaborator_tools,
customer_tools,
contract_tools,
event_tools, tools):
self.collaborator_tools = collaborator_tools
self.customer_tools = customer_tools
self.contract_tools = contract_tools
self.event_tools = event_tools
self.tools = tools
self.prompt = Prompt()
def launch(self):
"""
Menu entry point for management user; display objects then submenu
:return: call submenus
"""
base = {
"Collaborator": self.collaborator_menu,
"Customer": self.customer_menu,
"Contract": self.contract_menu,
"Event": self.event_menu,
"Quit": quit,
}
while True:
self.prompt.exec_menu(base)
def choose_collaborator(self):
"""
gets all collaborators from db, creates a dict options, calls menu
and returns choice (Collaborator.id)
:return: chosen Collaborator.id:int
"""
options = {}
for item in self.tools.list(Collaborator):
options[item[0].name] = item[0].id
return self.prompt.return_menu(options)
def collaborator_update(self):
"""
exec choose_collaborator and call tool update with chosen id
"""
choice = self.choose_collaborator()
self.collaborator_tools.update(choice)
def collaborator_delete(self):
"""
exec choose_collaborator and call tool delete with chosen id
"""
choice = self.choose_collaborator()
self.collaborator_tools.delete(choice)
def collaborator_menu(self):
"""
display the CRUD menu for collaborator and get choice
:return: exec the function associated with chosen item
"""
collaborator_options = {
"List": self.collaborator_tools.list,
"Create": self.collaborator_tools.create,
"Update": self.collaborator_update,
"Delete": self.collaborator_delete,
}
self.prompt.exec_menu(collaborator_options)
def customer_menu(self):
"""
display the CRUD menu for customer and get choice
:return: exec the function associated with chosen item
"""
customer_options = {"List": self.customer_tools.list}
self.prompt.exec_menu(customer_options)
def contracts_for_update(self):
(options,
customer_options,
commercial_options,
event_options) = {},{},{},{}
commercial = self.collaborator_tools.get_by_team_id(1)
for customer in self.tools.list(Customer):
customer_options[customer[0].name] = customer[0].id
for user in commercial:
commercial_options[user[0].name] = user[0].id
for event in self.tools.list(Event):
event_options[event[0].name] = event[0].id
for item in self.tools.list(Contract):
options["Contrat "+str(item[0].id)] = item[0].id
choice = self.prompt.return_menu(options)
self.contract_tools.update(choice, customer_options,
commercial_options, event_options)
def contract_menu(self):
"""
display the CRUD menu for contract and get choice
:return: exec the function associated with chosen item
"""
contract_options = {"List": self.contract_tools.list,
"Create": self.contract_tools.create,
"Update": self.contracts_for_update,
}
self.prompt.exec_menu(contract_options)
def event_for_update(self):
"""
get all events from db, create a dict options, call menu
and get choice (Event.id)
also gets all support collaborators and creates dict option given
to tool update method
:return: exec the update tool with the chosen id
"""
options = {}
support_options = {}
support = self.collaborator_tools.get_by_team_id(3)
for user in support:
support_options[user[0].name] = user[0].id
for item in self.tools.list(Event):
options[item[0].name] = item[0].id
choice = self.prompt.return_menu(options)
self.event_tools.update(choice, support_options)
def event_list(self):
event_list_options = {
"All": self.event_tools.list,
"No Support yet": self.event_no_support,
}
self.prompt.exec_menu(event_list_options)
def event_no_support(self):
self.event_tools.filter("support_id", "NULL")
def event_menu(self):
event_options = {"List": self.event_list,
"Update": self.event_for_update,
}
self.prompt.exec_menu(event_options)
class SupportMenu:
"""
Main menu for support users displaying only specific submenus according
to permissions :
customers: R
contracts: R
events: CRU
filter:
event: owned
"""
def __init__(self,
customer_tools,
contract_tools,
event_tools,
tools,
user_id):
self.customer_tools = customer_tools
self.contract_tools = contract_tools
self.event_tools = event_tools
self.tools = tools
self.user_id = user_id
self.prompt = Prompt()
def launch(self):
base = {
"Customer": self.customer_menu,
"Contract": self.contract_menu,
"Event": self.event_menu,
"Quit": quit,
}
while True:
self.prompt.exec_menu(base)
def customer_menu(self):
customer_options = {"List": self.customer_tools.list}
self.prompt.exec_menu(customer_options)
def contract_menu(self):
contract_options = {"List": self.contract_tools.list,
}
self.prompt.exec_menu(contract_options)
def event_menu(self):
event_options = {"List": self.event_list,
"Create": self.event_tools.create,
"Update": self.event_for_update,
}
self.prompt.exec_menu(event_options)
def event_list(self):
event_list_options = {
"All": self.event_tools.list,
"Owned only": self.event_owned,
}
self.prompt.exec_menu(event_list_options)
def event_owned(self):
self.event_tools.filter_owned(self.user_id)
def event_for_update(self):
options = {}
for item in self.tools.list(Event):
options[item[0].name] = item[0].id
choice = self.prompt.return_menu(options)
self.event_tools.update(choice)
def menu(options):
terminal_menu = TerminalMenu(options)
menu_entry_index = terminal_menu.show()
selection = options[menu_entry_index]
return selection
def return_menu(dict_options):
selection = menu(list(dict_options.keys()))
return dict_options.get(selection)

143
models.py Normal file
View File

@@ -0,0 +1,143 @@
from datetime import datetime, date
from sqlalchemy import ForeignKey, String, Integer, DateTime, Boolean, Date
from sqlalchemy.sql import func
from sqlalchemy.orm import (DeclarativeBase, Mapped,
mapped_column, relationship)
from typing import Optional
class Base(DeclarativeBase):
pass
class Collaborator(Base):
__tablename__ = "collaborator"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
email: Mapped[str] = mapped_column(String(30))
phone: Mapped[int] = mapped_column(Integer)
team_id: Mapped[Optional[int]] = mapped_column(ForeignKey("team.id"))
team: Mapped["Team"] = relationship(back_populates="collaborator")
customers: Mapped[Optional[list["Customer"]]] = relationship()
contracts: Mapped[Optional[list["Contract"]]] = relationship()
events: Mapped[Optional[list["Event"]]] = relationship()
def __repr__(self):
return (f"Collaborator: (id={self.id!r}, "
f"name={self.name!r}, "
f"email={self.email!r}, "
f"phone={self.phone!r}, "
f"team_id={self.team_id!r}, "
f"customers={self.customers!r}, "
f"contracts={self.contracts!r}, "
f"events={self.events!r})"
)
class Credentials(Base):
__tablename__ = "credentials"
id: Mapped[int] = mapped_column(primary_key=True)
collaborator_id: Mapped[int] = mapped_column(ForeignKey("collaborator.id"))
password_hash: Mapped[str] = mapped_column(String(200))
class Team(Base):
__tablename__ = "team"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(10))
collaborator: Mapped[list["Collaborator"]] = relationship(
back_populates="team")
class Customer(Base):
__tablename__ = "customer"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
email: Mapped[str] = mapped_column(String(20))
phone: Mapped[int] = mapped_column(Integer)
company: Mapped[str] = mapped_column(String(40))
creation_date: Mapped[date] = mapped_column(Date,
server_default=func.now())
last_update: Mapped[Optional[datetime]] = mapped_column(DateTime)
commercial_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("collaborator.id"))
def __repr__(self):
return (f"Customer: (id={self.id!r}, "
f"name={self.name!r}, "
f"email={self.email!r}, "
f"phone={self.phone!r}, "
f"company={self.company!r}, "
f"creation_date={self.creation_date!r}, "
f"last_update={self.last_update!r}, "
f"commercial_id={self.commercial_id!r})"
)
class Contract(Base):
__tablename__ = "contract"
id: Mapped[int] = mapped_column(primary_key=True)
signed: Mapped[bool] = mapped_column(Boolean, default=False)
creation_date: Mapped[date] = mapped_column(Date,
server_default=func.now())
amount: Mapped[int] = mapped_column(Integer)
customer_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("customer.id"))
commercial_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("collaborator.id"))
event_id: Mapped[Optional[int]] = mapped_column(ForeignKey("event.id"))
def __repr__(self):
return (f"Contract: (id={self.id!r}, "
f"creation_date={self.creation_date!r}, "
f"amount={self.amount!r}, "
f"customer_id={self.customer_id!r}, "
f"commercial_id={self.commercial_id!r}, "
f"event_id={self.event_id!r}, "
f"signed={self.signed!r}, "
)
class Event(Base):
__tablename__ = "event"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(200))
contract_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("contract.id"))
customer_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("customer.id"))
customer_contact: Mapped[str] = mapped_column(String(30))
date_start: Mapped[str] = mapped_column(String(30))
date_end: Mapped[Optional[str]] = mapped_column(String(30))
location: Mapped[Optional[str]] = mapped_column(String(100))
attendees: Mapped[Optional[set["Attendee"]]] = relationship(
back_populates="event")
support_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("collaborator.id"))
def __repr__(self):
return (f"Event: (id={self.id!r}, "
f"name={self.name!r}, "
f"contract_id={self.contract_id!r}, "
f"customer_id={self.customer_id!r}, "
f"customer_contact={self.customer_contact!r}, "
f"support_id={self.support_id!r}, "
f"date_start={self.date_start!r}, "
f"date_end={self.date_end!r}, "
f"location={self.location!r}, "
f"attendees={self.attendees!r}"
)
class Attendee(Base):
__tablename__ = "attendee"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(50))
event_id: Mapped[Optional[int]] = mapped_column(ForeignKey("event.id"))
event: Mapped[Optional[set["Event"]]] = relationship(
back_populates="attendees")

373
tools.py Normal file
View File

@@ -0,0 +1,373 @@
from menu import return_menu
from models import Collaborator, Customer, Contract, Event, Credentials
from authentication import PasswordTools
from views import View
from sqlalchemy.orm import Session
from sqlalchemy import select, update, insert, delete
class Tools:
def __init__(self, db: Session):
self.db = db
self.view = View()
def list(self, object):
while self.db:
return self.db.execute(select(object)).all()
def filter(self, object, filter):
item, value = filter
stmt = (select(object).where(**{item: value}))
print(stmt)
# while self.db:
# return self.db.execute(select(object).where(**{item: value})).all()
class CollaboratorTools:
"""
Interface to manage Collaborator object in DB
"""
def __init__(self, db: Session):
self.db = db
self.view = View()
self.pw_tools = PasswordTools(self.db)
def get_id_by_name(self, username):
collaborator = self.db.execute(
select(Collaborator).where(Collaborator.name == username)).scalar()
return collaborator.id
def get_by_team_id(self, team_id):
collaborator = self.db.execute(
select(Collaborator).where(Collaborator.team_id == team_id)).all()
return collaborator
def list(self):
result = self.db.execute(select(Collaborator)).all()
self.view.display_results(result)
def create(self) -> None:
"""
Create a new collaborator with minimum information
:return: None; creates object in DB
"""
pwd_tools = PasswordTools(self.db)
collaborator = self.view.prompt_for_collaborator()
print(collaborator)
new_collab = Collaborator(
name=collaborator['name'],
email=collaborator['email'],
phone=collaborator['phone'],
team_id=collaborator['team_id']
)
self.db.add(new_collab)
self.db.commit()
new_collab_pwd = Credentials(collaborator_id=new_collab.id,
password_hash=pwd_tools.hash("123456"))
self.db.add(new_collab_pwd)
self.db.commit()
self.view.display_confirm("collaborator", new_collab.id)
def update(self, my_id) -> None:
"""
Update a collaborator; asks for field to change and value
:param my_id: id of the collaborator to modify
:return: None; Update entry in DB
"""
pwd_tools = PasswordTools(self.db)
collab = self.db.get(Collaborator, my_id)
item, value = self.view.prompt_for_collaborator_update()
stmt = (update(Collaborator).where(Collaborator.id == my_id).values(
**{item: value}))
if item == 'password':
if not self.db.execute(select(Credentials).where(
Credentials.collaborator_id == my_id)).all():
stmt = (insert(Credentials).values(
collaborator_id=my_id,
password_hash=pwd_tools.hash(value)))
else:
stmt = (update(Credentials).where(
Credentials.collaborator_id == my_id).values(
password_hash=pwd_tools.hash(value)))
self.db.execute(stmt)
self.db.commit()
self.view.display_change(collab.name, item, value)
def delete(self, my_id) -> None:
"""
Delete a collaborator
:param my_id: id of the collaborator to delete
:return: None; deletes entry in DB
"""
stmt = delete(Collaborator).where(Collaborator.id == my_id)
self.db.execute(stmt)
self.db.commit()
def get_team_by_name(self, username: str) -> int | None:
"""
Check if username exists, returns team_id to check permission further
:param username: string, Collaborator.username
:return: None|team_id:int
"""
ret = self.db.execute(
select(Collaborator).where(Collaborator.name == username)).scalar()
if ret is None:
print({'message': "This username doesn't exist"})
return ret
else:
return ret.team_id
def list_by_team_id(self, my_id):
pass
def list_by_contract_id(self, my_id):
pass
def list_by_event_id(self, my_id):
pass
class CustomerTools:
"""
Interface to manage Customer object in DB
"""
def __init__(self, db: Session):
self.db = db
self.view = View()
def list(self):
result = self.db.execute(select(Customer)).all()
self.view.display_results(result)
return result
def create(self) -> None:
"""
Create a new customer with minimum information
:return: None; creates object in DB
"""
customer = self.view.prompt_for_customer()
new_customer = Customer(
name=customer['name'],
email=customer['email'],
phone=customer['phone'],
company=customer['company'],
)
self.db.add(new_customer)
self.db.commit()
self.view.display_confirm("customer", new_customer.id)
def update(self, my_id):
"""
Update a customer; asks for field to change and value
:param my_id: id of the collaborator to modify
:return: None; Update entry in DB
"""
cust = self.db.get(Customer, my_id)
item, value = self.view.prompt_for_customer_update()
stmt = (update(Customer).where(Customer.id == my_id).values(
**{item: value}))
self.db.execute(stmt)
self.db.commit()
self.view.display_change(cust.name, item, value)
def delete(self, my_id) -> None:
"""
Delete a customer
:param my_id: id of the customer to delete
:return: None; deletes entry in DB
"""
stmt = delete(Customer).where(Customer.id == my_id)
self.db.execute(stmt)
self.db.commit()
def get_by_commercial_id(self, my_id):
"""
Check if commercial exists, then returns a list
:param my_id: a commercial id
:return: a list of Customer
"""
if self.db.get(Collaborator, my_id):
ret = self.db.execute(
select(Customer).where(Customer.commercial_id == my_id)).all()
if ret is None:
print({'message': "No customer found"})
return ret
else:
print({'message': "No commercial with this id"})
return None
class ContractTools:
"""
Interface to manage Contract object in DB
"""
def __init__(self, db: Session):
self.db = db
self.view = View()
def list(self):
"""
List all contracts from DB
:return: a list of Contract objects
"""
result = self.db.execute(select(Contract)).all()
self.view.display_results(result)
def signed(self):
"""
List only Contract where signed is True (or 1)
:return: a list of Contract objects
"""
result = self.db.execute(
select(Contract).where(Contract.signed == 1))
self.view.display_results(result)
def not_signed(self):
"""
List only Contract where signed is False (or 0)
:return: a list of Contract objects
"""
result = self.db.execute(
select(Contract).where(Contract.signed == 0))
self.view.display_results(result)
def create(self) -> None:
"""
Create a new contracts with minimum information
:return: None; creates object in DB
"""
contract = self.view.prompt_for_contract()
new_contract = Contract(
customer=contract['customer'],
commercial=contract['commercial'],
amount=contract['amount'],
)
self.db.add(new_contract)
self.db.commit()
self.view.display_confirm("contract", new_contract.id)
def update(self, my_id,
customer_options,
commercial_options,
event_options):
"""
Update a contract; asks for field to change and value
:param my_id: id of the contract to modify
:return: None; Update entry in DB
"""
item, value = self.view.prompt_for_contract_update()
if item == 'signed':
if value.lower() == "yes":
value = 1
else:
value = 0
# stmt = (update(Contract).where(Contract.id == my_id).values(
# signed=is_signed))
if item == 'customer_id':
value = return_menu(customer_options)
if item == 'commercial_id':
value = return_menu(commercial_options)
if item == 'event_id':
value = return_menu(event_options)
stmt = (update(Contract).where(Contract.id == my_id).values(
**{item: value}))
self.db.execute(stmt)
self.db.commit()
self.view.display_change("Contract"+str(my_id), item, value)
def delete(self, my_id) -> None:
"""
Delete a contract
:param my_id: id of the contract to delete
:return: None; deletes entry in DB
"""
stmt = delete(Contract).where(Contract.id == my_id)
self.db.execute(stmt)
self.db.commit()
class EventTools:
"""
Interface to manage Event object in DB
"""
def __init__(self, db: Session):
self.db = db
self.view = View()
def list(self):
"""
List all events from DB
:return: list of Event objects
"""
result = self.db.execute(select(Event)).all()
self.view.display_results(result)
def filter(self, field, value):
"""
Retrieve only event with specified field
:param field: Event.field
:return: display a list of Event
"""
result = self.db.execute(
select(Event).filter_by(**{field: value})).all()
print(field, value, result)
self.view.display_results(result)
def filter_owned(self, user_id):
"""
List only events where support_id is the id of the user logged in
:param user_id: the connected user id
:return: list of Event objects
"""
result = self.db.execute(
select(Event).where(Event.support_id == user_id)).all()
if not result:
self.view.display_error()
return None
self.view.display_results(result)
return result
def create(self) -> None:
"""
Create a new event with minimum information
:return: None; creates object in DB
"""
event = self.view.prompt_for_event()
new_event = Event(
name=event['name'],
contract_id=event['contract_id'],
customer_id=event['customer_id'],
customer_contact=event['customer_contact'],
date_start=event['date_start'],
date_end=event['date_end'],
location=event['location'],
)
self.db.add(new_event)
self.db.commit()
self.view.display_confirm("event", new_event.id)
def update(self, my_id, support_options):
"""
Update an event in DB; asks for field to change and value
:param my_id: id of the event to modify
:return: None; Update entry in DB
"""
event = self.db.get(Event, my_id)
item, value = self.view.prompt_for_event_update(support_options)
if item == 'support_id':
value = return_menu(support_options)
stmt = (update(Event).where(Event.id == my_id).values(
**{item: value}))
self.db.execute(stmt)
self.db.commit()
self.view.display_change(event.name, item, value)
def delete(self, my_id) -> None:
"""
Delete an event
:param my_id: id of the event to delete
:return: None; deletes entry in DB
"""
stmt = delete(Event).where(Event.id == my_id)
self.db.execute(stmt)
self.db.commit()

139
views.py Normal file
View File

@@ -0,0 +1,139 @@
from getpass import getpass
from menu import return_menu
class View:
def __init__(self):
pass
def prompt_connect(self):
print("Please connect")
collaborator = input("Username : ")
password = getpass("Password : ")
return collaborator, password
def prompt_for_id(self):
id = input("What id ? ")
return id
def prompt_for_update(self, options):
print("What do you want to update ?")
ids = ("support_id", "commercial_id", "customer_id", "event_id")
item = return_menu(options)
if item == 'password':
data = getpass("New password : ")
elif item == 'team_id':
data = self.prompt_for_collaborator_team()
elif item in ids:
data = ""
else:
data = input(f"New {item}'s value : ")
return item, data
def prompt_for_collaborator_team(self):
options = {"Commercial": 1,
"Management": 2,
"Support": 3,
}
return return_menu(options)
def prompt_for_collaborator(self):
collaborator = {}
options = {"Commercial": 1,
"Management": 2,
"Support": 3,
}
print("Please enter collaborator's information")
collaborator['name'] = input("Name ? : ")
collaborator['email'] = input("Email ? : ")
collaborator['phone'] = input("Phone ? : ")
item = return_menu(options)
collaborator['team_id'] = item
return collaborator
def prompt_for_collaborator_update(self) -> tuple:
options = {"password": "password",
"name": "name",
"email": "email",
"phone": "phone",
"team": "team_id",
}
return self.prompt_for_update(options)
def prompt_for_customer(self) -> dict:
customer = {}
print("** New customer **")
customer['name'] = input("Name ? : ")
customer['email'] = input("Email ? : ")
customer['phone'] = input("Phone ? : ")
customer['company'] = input("Company name ? : ")
return customer
def prompt_for_customer_update(self) -> tuple:
options = {"name": "name",
"email": "email",
"phone": "phone",
"company": "company",
}
return self.prompt_for_update(options)
def prompt_for_contract(self) -> dict:
contract = {}
print("** New contract **")
contract['customer'] = input("Customer (id) ? : ")
contract['commercial'] = input("Commercial (id) ")
contract['amount'] = input("Budget ? : ")
return contract
def prompt_for_contract_update(self) -> tuple:
options = {"customer": "customer_id",
"commercial": "commercial_id",
"event": "event_id",
"amount": "amount",
"signed": "signed",
}
return self.prompt_for_update(options)
def prompt_for_event(self) -> dict:
event = {}
print("** New Event **")
event['name'] = input("Event's name ? : ")
event['contract_id'] = input("Contract (id) ? : ")
event['customer_id'] = input("Customer (id) ? : ")
event['customer_contact'] = input("Customer's contact ? : ")
event['date_start'] = input("Start date ? : ")
event['date_end'] = input("End date ? : ")
event['location'] = input("Location ? : ")
# event['attendee'] = input("Attendees ? : ")
return event
def prompt_for_event_update(self, support_options) -> tuple:
options = {"name": "name",
"contract": "contract_id",
"customer": "customer_id",
"date_start": "date_start",
"date_end": "date_end",
"location": "location",
"Support Member": "support_id"
}
return self.prompt_for_update(options)
def display_quit(self):
print("Bye")
def display_confirm(self, object, id):
print(f"New {object} with id:{id} created !")
def display_change(self, object, item, value):
print(f"{item} for {object} updated, set to {value} !")
def display_results(self, result):
for item in result:
print(item)
def display_error(self):
print("No object matches this query")
def display_items(self):
print()