commit a2a18bad24a2bebd55605c396e23cd32397a8c63 Author: yann Date: Wed Aug 20 14:26:51 2025 +0200 whole app diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..e613d32 --- /dev/null +++ b/Pipfile @@ -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" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..bf6859e --- /dev/null +++ b/Pipfile.lock @@ -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": {} +} diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authentication.py b/authentication.py new file mode 100644 index 0000000..e469328 --- /dev/null +++ b/authentication.py @@ -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 diff --git a/config.py b/config.py new file mode 100644 index 0000000..739599a --- /dev/null +++ b/config.py @@ -0,0 +1,5 @@ +user = "epicevents" +password = "m3gapassword" + +user1 = "root" +pass1 = "proutproutprout" diff --git a/controllers.py b/controllers.py new file mode 100644 index 0000000..9b30af7 --- /dev/null +++ b/controllers.py @@ -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() diff --git a/db.py b/db.py new file mode 100644 index 0000000..0998337 --- /dev/null +++ b/db.py @@ -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() diff --git a/flake-report/back.svg b/flake-report/back.svg new file mode 100644 index 0000000..ce80d2e --- /dev/null +++ b/flake-report/back.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/flake-report/file.svg b/flake-report/file.svg new file mode 100644 index 0000000..98706cf --- /dev/null +++ b/flake-report/file.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/flake-report/index.html b/flake-report/index.html new file mode 100644 index 0000000..49b3334 --- /dev/null +++ b/flake-report/index.html @@ -0,0 +1,30 @@ + + + + flake8 violations + + + + +
+
+

flake8 violations

+

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 +

+ +
+ + \ No newline at end of file diff --git a/flake-report/styles.css b/flake-report/styles.css new file mode 100644 index 0000000..6e0e447 --- /dev/null +++ b/flake-report/styles.css @@ -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 */ \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..ce5be3a --- /dev/null +++ b/main.py @@ -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() diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..b252f31 --- /dev/null +++ b/menu.py @@ -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) diff --git a/models.py b/models.py new file mode 100644 index 0000000..c6a1000 --- /dev/null +++ b/models.py @@ -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") diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..ee350f0 --- /dev/null +++ b/tools.py @@ -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() diff --git a/views.py b/views.py new file mode 100644 index 0000000..b8d001b --- /dev/null +++ b/views.py @@ -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()