From b9a11d2b374add7944aeb92561769572507eca87 Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Sat, 17 Oct 2020 11:28:14 +0530 Subject: [PATCH 1/8] implemented admin route using flask-admin --- app/admin.py | 12 ++ app/templates/admin/index.html | 5 + poetry.lock | 264 ++++++++++++++++++++------------- pyproject.toml | 3 +- run.py | 4 +- 5 files changed, 180 insertions(+), 108 deletions(-) create mode 100644 app/admin.py create mode 100644 app/templates/admin/index.html diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 00000000..ce969317 --- /dev/null +++ b/app/admin.py @@ -0,0 +1,12 @@ +from flask_admin import Admin +from flask_admin.contrib.sqla import ModelView + +from app import db +from .models import Resource, Category, Language + + +def run_flask_admin(app): + admin = Admin(app, name="Admin") + admin.add_view(ModelView(Resource, db.session)) + admin.add_view(ModelView(Category, db.session)) + admin.add_view(ModelView(Language, db.session)) diff --git a/app/templates/admin/index.html b/app/templates/admin/index.html new file mode 100644 index 00000000..12a6f1c7 --- /dev/null +++ b/app/templates/admin/index.html @@ -0,0 +1,5 @@ +{% extends 'admin/master.html' %} + +{% block body %} +

Admin Home Page

+{% endblock %} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 1bc7bf38..dc308bdc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -33,17 +33,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "19.3.0" +version = "20.2.0" description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "bandit" @@ -61,7 +61,7 @@ stevedore = ">=1.20.0" [[package]] name = "certifi" -version = "2020.4.5.2" +version = "2020.6.20" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -69,7 +69,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.0" +version = "1.14.3" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -96,7 +96,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "colorama" -version = "0.4.3" +version = "0.4.4" description = "Cross-platform colored terminal text." category = "main" optional = false @@ -104,7 +104,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "5.1" +version = "5.3" description = "Code coverage measurement for Python" category = "main" optional = false @@ -165,6 +165,22 @@ dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxco docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dotenv = ["python-dotenv"] +[[package]] +name = "flask-admin" +version = "1.5.6" +description = "Simple and extensible admin interface framework for Flask" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = ">=0.7" +wtforms = "*" + +[package.extras] +aws = ["boto"] +azure = ["azure-storage-blob"] + [[package]] name = "flask-cors" version = "3.0.9" @@ -192,7 +208,7 @@ Flask-SQLAlchemy = ">=1.0" [[package]] name = "flask-sqlalchemy" -version = "2.4.3" +version = "2.4.4" description = "Adds SQLAlchemy support to your Flask application." category = "main" optional = false @@ -215,7 +231,7 @@ smmap = ">=3.0.1,<4" [[package]] name = "gitpython" -version = "3.1.3" +version = "3.1.9" description = "Python Git Library" category = "main" optional = false @@ -226,7 +242,7 @@ gitdb = ">=4.0.1,<5" [[package]] name = "idna" -version = "2.9" +version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -234,7 +250,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "1.6.1" +version = "2.0.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -249,7 +265,7 @@ testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] name = "iniconfig" -version = "1.0.0" +version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" category = "main" optional = false @@ -322,11 +338,11 @@ six = "*" [[package]] name = "pbr" -version = "5.4.5" +version = "5.5.0" description = "Python Build Reasonableness" category = "main" optional = false -python-versions = "*" +python-versions = ">=2.6" [[package]] name = "pluggy" @@ -363,7 +379,7 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" [[package]] name = "py" -version = "1.8.2" +version = "1.9.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "main" optional = false @@ -587,13 +603,14 @@ url = ["furl (>=0.4.1)"] [[package]] name = "stevedore" -version = "2.0.0" +version = "3.2.2" description = "Manage dynamic plugins for Python applications" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] @@ -606,7 +623,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.25.9" +version = "1.25.10" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -637,9 +654,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] watchdog = ["watchdog"] +[[package]] +name = "wtforms" +version = "2.3.3" +description = "A flexible forms validation and rendering library for Python web development." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +MarkupSafe = "*" + +[package.extras] +email = ["email-validator"] +ipaddress = ["ipaddress"] +locale = ["Babel (>=1.3)"] + [[package]] name = "zipp" -version = "3.1.0" +version = "3.3.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false @@ -647,12 +680,12 @@ python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "3d684aba718476745e528c6ae5a82ad8670241f8f7e25d90ac7cd66b3e740ab8" +content-hash = "932c490b9ff2df76d3c6f22ae7f8179fe88f35a95b72238be842d2b5dcc6d3cc" [metadata.files] alembic = [ @@ -668,46 +701,54 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, + {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] bandit = [ {file = "bandit-1.5.1-py2.py3-none-any.whl", hash = "sha256:6102b5d6afd9d966df5054e0bdfc2e73a24d0fea400ec25f2e54c134412158d7"}, {file = "bandit-1.5.1.tar.gz", hash = "sha256:9413facfe9de1e1bd291d525c784e1beb1a55c9916b51dae12979af63a69ba4c"}, ] certifi = [ - {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, - {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, + {file = "cffi-1.14.3-2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc"}, + {file = "cffi-1.14.3-2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768"}, + {file = "cffi-1.14.3-2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d"}, + {file = "cffi-1.14.3-2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1"}, + {file = "cffi-1.14.3-2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca"}, + {file = "cffi-1.14.3-2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c"}, + {file = "cffi-1.14.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730"}, + {file = "cffi-1.14.3-cp27-cp27m-win32.whl", hash = "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d"}, + {file = "cffi-1.14.3-cp27-cp27m-win_amd64.whl", hash = "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b"}, + {file = "cffi-1.14.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f"}, + {file = "cffi-1.14.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4"}, + {file = "cffi-1.14.3-cp35-cp35m-win32.whl", hash = "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d"}, + {file = "cffi-1.14.3-cp35-cp35m-win_amd64.whl", hash = "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808"}, + {file = "cffi-1.14.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537"}, + {file = "cffi-1.14.3-cp36-cp36m-win32.whl", hash = "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0"}, + {file = "cffi-1.14.3-cp36-cp36m-win_amd64.whl", hash = "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579"}, + {file = "cffi-1.14.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394"}, + {file = "cffi-1.14.3-cp37-cp37m-win32.whl", hash = "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc"}, + {file = "cffi-1.14.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828"}, + {file = "cffi-1.14.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9"}, + {file = "cffi-1.14.3-cp38-cp38-win32.whl", hash = "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522"}, + {file = "cffi-1.14.3-cp38-cp38-win_amd64.whl", hash = "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d"}, + {file = "cffi-1.14.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c"}, + {file = "cffi-1.14.3-cp39-cp39-win32.whl", hash = "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b"}, + {file = "cffi-1.14.3-cp39-cp39-win_amd64.whl", hash = "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3"}, + {file = "cffi-1.14.3.tar.gz", hash = "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -718,41 +759,44 @@ click = [ {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, - {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, - {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, - {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, - {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, - {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, - {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, - {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, - {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, - {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, - {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, - {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, - {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, - {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, - {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, - {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, - {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, - {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, - {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, - {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, - {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, - {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, - {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, - {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, - {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] cryptography = [ {file = "cryptography-3.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:9a8580c9afcdcddabbd064c0a74f337af74ff4529cdf3a12fa2e9782d677a2e5"}, @@ -786,6 +830,9 @@ flask = [ {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, ] +flask-admin = [ + {file = "Flask-Admin-1.5.6.tar.gz", hash = "sha256:68c761d8582d59b1f7702013e944a7ad11d7659a72f3006b89b68b0bd8df61b8"}, +] flask-cors = [ {file = "Flask-Cors-3.0.9.tar.gz", hash = "sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8"}, {file = "Flask_Cors-3.0.9-py2.py3-none-any.whl", hash = "sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"}, @@ -795,27 +842,28 @@ flask-migrate = [ {file = "Flask_Migrate-2.5.3-py2.py3-none-any.whl", hash = "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732"}, ] flask-sqlalchemy = [ - {file = "Flask-SQLAlchemy-2.4.3.tar.gz", hash = "sha256:0b656fbf87c5f24109d859bafa791d29751fabbda2302b606881ae5485b557a5"}, - {file = "Flask_SQLAlchemy-2.4.3-py2.py3-none-any.whl", hash = "sha256:fcfe6df52cd2ed8a63008ca36b86a51fa7a4b70cef1c39e5625f722fca32308e"}, + {file = "Flask-SQLAlchemy-2.4.4.tar.gz", hash = "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"}, + {file = "Flask_SQLAlchemy-2.4.4-py2.py3-none-any.whl", hash = "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e"}, ] gitdb = [ {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, ] gitpython = [ - {file = "GitPython-3.1.3-py3-none-any.whl", hash = "sha256:ef1d60b01b5ce0040ad3ec20bc64f783362d41fa0822a2742d3586e1f49bb8ac"}, - {file = "GitPython-3.1.3.tar.gz", hash = "sha256:e107af4d873daed64648b4f4beb89f89f0cfbe3ef558fc7821ed2331c2f8da1a"}, + {file = "GitPython-3.1.9-py3-none-any.whl", hash = "sha256:138016d519bf4dd55b22c682c904ed2fd0235c3612b2f8f65ce218ff358deed8"}, + {file = "GitPython-3.1.9.tar.gz", hash = "sha256:a03f728b49ce9597a6655793207c6ab0da55519368ff5961e4a74ae475b9fa8e"}, ] idna = [ - {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, - {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, - {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, + {file = "importlib_metadata-2.0.0-py2.py3-none-any.whl", hash = "sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3"}, + {file = "importlib_metadata-2.0.0.tar.gz", hash = "sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da"}, ] iniconfig = [ - {file = "iniconfig-1.0.0.tar.gz", hash = "sha256:aa0b40f50a00e72323cb5d41302f9c6165728fd764ac8822aa3fff00a40d56b4"}, + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, @@ -873,8 +921,8 @@ packaging = [ {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] pbr = [ - {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, - {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"}, + {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"}, + {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -920,8 +968,8 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, ] py = [ - {file = "py-1.8.2-py2.py3-none-any.whl", hash = "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44"}, - {file = "py-1.8.2.tar.gz", hash = "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"}, + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] py-healthcheck = [ {file = "py-healthcheck-1.10.1.tar.gz", hash = "sha256:60bbaab729a89098f0e6723ba5b6ab4ca8bde79b1a1bdb324f2b9e39df33780d"}, @@ -1038,16 +1086,16 @@ sqlalchemy-utils = [ {file = "SQLAlchemy-Utils-0.36.8.tar.gz", hash = "sha256:fb66e9956e41340011b70b80f898fde6064ec1817af77199ee21ace71d7d6ab0"}, ] stevedore = [ - {file = "stevedore-2.0.0-py3-none-any.whl", hash = "sha256:471c920412265cc809540ae6fb01f3f02aba89c79bbc7091372f4745a50f9691"}, - {file = "stevedore-2.0.0.tar.gz", hash = "sha256:001e90cd704be6470d46cc9076434e2d0d566c1379187e7013eb296d3a6032d9"}, + {file = "stevedore-3.2.2-py3-none-any.whl", hash = "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62"}, + {file = "stevedore-3.2.2.tar.gz", hash = "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"}, ] toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] urllib3 = [ - {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, - {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] uwsgi = [ {file = "uWSGI-2.0.19.1.tar.gz", hash = "sha256:faa85e053c0b1be4d5585b0858d3a511d2cd10201802e8676060fd0a109e5869"}, @@ -1056,7 +1104,11 @@ werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] +wtforms = [ + {file = "WTForms-2.3.3-py2.py3-none-any.whl", hash = "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c"}, + {file = "WTForms-2.3.3.tar.gz", hash = "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c"}, +] zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, + {file = "zipp-3.3.1-py3-none-any.whl", hash = "sha256:16522f69653f0d67be90e8baa4a46d66389145b734345d68a257da53df670903"}, + {file = "zipp-3.3.1.tar.gz", hash = "sha256:c1532a8030c32fd52ff6a288d855fe7adef5823ba1d26a29a68fd6314aa72baa"}, ] diff --git a/pyproject.toml b/pyproject.toml index c8d9713b..339b90e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ SQLAlchemy-Utils = "0.36.8" uWSGI = "2.0.19.1" Werkzeug = "1.0.1" pyjwt = "^1.7.1" -cryptography = "^3.2" +cryptography = "^3.1" +flask-admin = "^1.5.6" [tool.poetry.dev-dependencies] diff --git a/run.py b/run.py index cfe86401..e8df0c18 100644 --- a/run.py +++ b/run.py @@ -1,9 +1,11 @@ -from app import app, cli +from app import app, cli, admin from app.models import Category, Language, Resource, db from werkzeug.middleware.dispatcher import DispatcherMiddleware from prometheus_client import make_wsgi_app +admin.run_flask_admin(app) + if __name__ == "__main__": app.run() From 3e5e7ce533200e596ed057524757e5cf830d14fd Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Mon, 19 Oct 2020 23:55:59 +0530 Subject: [PATCH 2/8] added authentication to admin route using flask-security --- app/admin.py | 34 ++++++-- app/models.py | 40 ++++++++++ configs.py | 7 ++ poetry.lock | 211 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 + run.py | 6 +- 6 files changed, 292 insertions(+), 9 deletions(-) diff --git a/app/admin.py b/app/admin.py index ce969317..29fbd614 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,12 +1,32 @@ -from flask_admin import Admin +from flask_admin import Admin, AdminIndexView from flask_admin.contrib.sqla import ModelView - +from flask import Flask, url_for, redirect, render_template, request, abort from app import db -from .models import Resource, Category, Language +from .models import Resource, Category, Language, User, Role +from flask_security import current_user + + +class AdminView(ModelView): + def is_accessible(self): + return current_user.has_role("admin") + + def inaccessible_callback(self): + return redirect(url_for("security.login", next=request.url)) + +class HomeAdminView(AdminIndexView): + def is_accessible(self): + return current_user.has_role("admin") + + def inaccessible_callback(self, name): + return redirect(url_for("security.login", next=request.url)) def run_flask_admin(app): - admin = Admin(app, name="Admin") - admin.add_view(ModelView(Resource, db.session)) - admin.add_view(ModelView(Category, db.session)) - admin.add_view(ModelView(Language, db.session)) + admin = Admin(app, name="Resources_api", url='/', index_view=HomeAdminView(name="Home")) + admin.add_view(AdminView(Role, db.session)) + admin.add_view(AdminView(User, db.session)) + admin.add_view(AdminView(Resource, db.session)) + admin.add_view(AdminView(Category, db.session)) + admin.add_view(AdminView(Language, db.session)) + return admin + diff --git a/app/models.py b/app/models.py index 85e11bcb..15186fa2 100644 --- a/app/models.py +++ b/app/models.py @@ -2,6 +2,7 @@ from sqlalchemy import DateTime from sqlalchemy.sql import func from sqlalchemy_utils import URLType +from flask_security import RoleMixin, UserMixin language_identifier = db.Table('language_identifier', db.Column( @@ -203,3 +204,42 @@ class VoteInformation(db.Model): current_direction = db.Column(db.String, nullable=False) resource = db.relationship('Resource', back_populates='voters') voter = db.relationship('Key', back_populates='voted_resources') + + +roles_users = db.Table( + 'roles_users', + db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), + db.Column('role_id', db.Integer(), db.ForeignKey('role.id')) +) + + +# Role class +class Role(db.Model, RoleMixin): + # Our Role has three fields, ID, name and description + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.String(80), unique=True) + description = db.Column(db.String(255)) + + def __str__(self): + return self.name + + # __hash__ is required to avoid the exception TypeError: unhashable type: 'Role' when saving a User + def __hash__(self): + return hash(self.name) + + +# User class +class User(db.Model, UserMixin): + + # Our User has six fields: ID, email, password, active, confirmed_at and roles. The roles field represents a + # many-to-many relationship using the roles_users table. Each user may have no role, one role, or multiple roles. + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(255), unique=True) + password = db.Column(db.String(255)) + active = db.Column(db.Boolean()) + confirmed_at = db.Column(db.DateTime()) + roles = db.relationship( + 'Role', + secondary=roles_users, + backref=db.backref('users', lazy='dynamic') + ) diff --git a/configs.py b/configs.py index 65ac3bfa..22bb18e2 100644 --- a/configs.py +++ b/configs.py @@ -45,6 +45,13 @@ class Config: SQLALCHEMY_DATABASE_URI = f"postgresql://{pg_user}:{pg_pw}@{pg_host}:5432/{pg_db}" + SECRET_KEY = os.urandom(24) + # Set config values for Flask-Security. + # We're using PBKDF2 with salt. + SECURITY_PASSWORD_HASH = 'pbkdf2_sha512' + # Replace this with your own salt. + SECURITY_PASSWORD_SALT = os.urandom(140) + ALGOLIA_APP_ID = algolia_app_id ALGOLIA_API_KEY = algolia_api_key INDEX_NAME = index_name diff --git a/poetry.lock b/poetry.lock index dc308bdc..db0fd2d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -45,6 +45,17 @@ docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +[[package]] +name = "babel" +version = "2.8.0" +description = "Internationalization utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pytz = ">=2015.7" + [[package]] name = "bandit" version = "1.5.1" @@ -59,6 +70,14 @@ PyYAML = ">=3.12" six = ">=1.10.0" stevedore = ">=1.20.0" +[[package]] +name = "blinker" +version = "1.4" +description = "Fast, simple object-to-object and broadcast signaling" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "certifi" version = "2020.6.20" @@ -132,6 +151,33 @@ pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"] test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] +[[package]] +name = "dnspython" +version = "2.0.0" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dnssec = ["cryptography (>=2.6)"] +doh = ["requests", "requests-toolbelt"] +idna = ["idna (>=2.1)"] +curio = ["curio (>=1.2)", "sniffio (>=1.1)"] +trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] + +[[package]] +name = "email-validator" +version = "1.1.1" +description = "A robust email syntax and deliverability validation library for Python 2.x/3.x." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +dnspython = ">=1.15.0" +idna = ">=2.0.0" + [[package]] name = "flake8" version = "3.8.4" @@ -181,6 +227,20 @@ wtforms = "*" aws = ["boto"] azure = ["azure-storage-blob"] +[[package]] +name = "flask-babelex" +version = "0.9.4" +description = "Adds i18n/l10n support to Flask applications" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Babel = ">=1.0" +Flask = "*" +Jinja2 = ">=2.5" +speaklater = ">=1.2" + [[package]] name = "flask-cors" version = "3.0.9" @@ -193,6 +253,29 @@ python-versions = "*" Flask = ">=0.9" Six = "*" +[[package]] +name = "flask-login" +version = "0.5.0" +description = "User session management for Flask" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = "*" + +[[package]] +name = "flask-mail" +version = "0.9.1" +description = "Flask extension for sending email" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +blinker = "*" +Flask = "*" + [[package]] name = "flask-migrate" version = "2.5.3" @@ -206,6 +289,41 @@ alembic = ">=0.7" Flask = ">=0.9" Flask-SQLAlchemy = ">=1.0" +[[package]] +name = "flask-principal" +version = "0.4.0" +description = "Identity management for flask" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +blinker = "*" +Flask = "*" + +[[package]] +name = "flask-security" +version = "3.0.0" +description = "Simple security for Flask apps." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = ">=0.11" +Flask-BabelEx = ">=0.9.3" +Flask-Login = ">=0.3.0" +Flask-Mail = ">=0.7.3" +Flask-Principal = ">=0.3.3" +Flask-WTF = ">=0.13.1" +itsdangerous = ">=0.21" +passlib = ">=1.7" + +[package.extras] +all = ["Flask-Sphinx-Themes (>=1.0.1)", "Sphinx (>=1.4.2)", "Flask-CLI (>=0.4.0)", "Flask-Mongoengine (>=0.7.0)", "Flask-Peewee (>=0.6.5)", "Flask-SQLAlchemy (>=1.0)", "bcrypt (>=1.0.2)", "check-manifest (>=0.25)", "coverage (>=4.0)", "isort (>=4.2.2)", "mock (>=1.3.0)", "mongoengine (>=0.10.0)", "pony (>=0.7.1)", "pydocstyle (>=1.0.0)", "pytest-cache (>=1.0)", "pytest-cov (>=2.4.0)", "pytest-flakes (>=1.0.1)", "pytest-pep8 (>=1.0.6)", "pytest-translations (>=1.0.4)", "pytest (>=3.0.5)", "sqlalchemy (>=0.8.0)", "Flask-Sphinx-Themes (>=1.0.1)", "Sphinx (>=1.4.2)", "Flask-CLI (>=0.4.0)", "Flask-Mongoengine (>=0.7.0)", "Flask-Peewee (>=0.6.5)", "Flask-SQLAlchemy (>=1.0)", "bcrypt (>=1.0.2)", "check-manifest (>=0.25)", "coverage (>=4.0)", "isort (>=4.2.2)", "mock (>=1.3.0)", "mongoengine (>=0.10.0)", "pony (>=0.7.1)", "pydocstyle (>=1.0.0)", "pytest-cache (>=1.0)", "pytest-cov (>=2.4.0)", "pytest-flakes (>=1.0.1)", "pytest-pep8 (>=1.0.6)", "pytest-translations (>=1.0.4)", "pytest (>=3.0.5)", "sqlalchemy (>=0.8.0)"] +docs = ["Flask-Sphinx-Themes (>=1.0.1)", "Sphinx (>=1.4.2)"] +tests = ["Flask-CLI (>=0.4.0)", "Flask-Mongoengine (>=0.7.0)", "Flask-Peewee (>=0.6.5)", "Flask-SQLAlchemy (>=1.0)", "bcrypt (>=1.0.2)", "check-manifest (>=0.25)", "coverage (>=4.0)", "isort (>=4.2.2)", "mock (>=1.3.0)", "mongoengine (>=0.10.0)", "pony (>=0.7.1)", "pydocstyle (>=1.0.0)", "pytest-cache (>=1.0)", "pytest-cov (>=2.4.0)", "pytest-flakes (>=1.0.1)", "pytest-pep8 (>=1.0.6)", "pytest-translations (>=1.0.4)", "pytest (>=3.0.5)", "sqlalchemy (>=0.8.0)"] + [[package]] name = "flask-sqlalchemy" version = "2.4.4" @@ -218,6 +336,19 @@ python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*" Flask = ">=0.10" SQLAlchemy = ">=0.8.0" +[[package]] +name = "flask-wtf" +version = "0.14.3" +description = "Simple integration of Flask and WTForms." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = "*" +itsdangerous = "*" +WTForms = "*" + [[package]] name = "gitdb" version = "4.0.5" @@ -336,6 +467,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" pyparsing = ">=2.0.2" six = "*" +[[package]] +name = "passlib" +version = "1.7.4" +description = "comprehensive password hashing framework supporting over 30 schemes" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] +totp = ["cryptography"] + [[package]] name = "pbr" version = "5.5.0" @@ -512,6 +657,14 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pytz" +version = "2020.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pyyaml" version = "5.3.1" @@ -554,6 +707,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "speaklater" +version = "1.3" +description = "implements a lazy string for python useful for use with gettext" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "sqlalchemy" version = "1.3.20" @@ -685,7 +846,7 @@ testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "932c490b9ff2df76d3c6f22ae7f8179fe88f35a95b72238be842d2b5dcc6d3cc" +content-hash = "f9342c0e349c490622b22854a64c4a0900d5b00db03eb4671e7cb8c5f50faa16" [metadata.files] alembic = [ @@ -704,10 +865,17 @@ attrs = [ {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, ] +babel = [ + {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, + {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, +] bandit = [ {file = "bandit-1.5.1-py2.py3-none-any.whl", hash = "sha256:6102b5d6afd9d966df5054e0bdfc2e73a24d0fea400ec25f2e54c134412158d7"}, {file = "bandit-1.5.1.tar.gz", hash = "sha256:9413facfe9de1e1bd291d525c784e1beb1a55c9916b51dae12979af63a69ba4c"}, ] +blinker = [ + {file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"}, +] certifi = [ {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, @@ -822,6 +990,14 @@ cryptography = [ {file = "cryptography-3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e15ac84dcdb89f92424cbaca4b0b34e211e7ce3ee7b0ec0e4f3c55cee65fae5a"}, {file = "cryptography-3.2.tar.gz", hash = "sha256:e4789b84f8dedf190148441f7c5bfe7244782d9cbb194a36e17b91e7d3e1cca9"}, ] +dnspython = [ + {file = "dnspython-2.0.0-py3-none-any.whl", hash = "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"}, + {file = "dnspython-2.0.0.zip", hash = "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7"}, +] +email-validator = [ + {file = "email_validator-1.1.1-py2.py3-none-any.whl", hash = "sha256:5f246ae8d81ce3000eade06595b7bb55a4cf350d559e890182a1466a21f25067"}, + {file = "email_validator-1.1.1.tar.gz", hash = "sha256:63094045c3e802c3d3d575b18b004a531c36243ca8d1cec785ff6bfcb04185bb"}, +] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, @@ -833,18 +1009,40 @@ flask = [ flask-admin = [ {file = "Flask-Admin-1.5.6.tar.gz", hash = "sha256:68c761d8582d59b1f7702013e944a7ad11d7659a72f3006b89b68b0bd8df61b8"}, ] +flask-babelex = [ + {file = "Flask-BabelEx-0.9.4.tar.gz", hash = "sha256:39a59ccee9386a9d52d80b9101224402036aedc2c7873b11deef6e4e21cace27"}, + {file = "Flask_BabelEx-0.9.4-py3-none-any.whl", hash = "sha256:f744d0557cb04cafed733cfa96e7373b46263d4cf79a2c5988c65085f360d873"}, +] flask-cors = [ {file = "Flask-Cors-3.0.9.tar.gz", hash = "sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8"}, {file = "Flask_Cors-3.0.9-py2.py3-none-any.whl", hash = "sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"}, ] +flask-login = [ + {file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"}, + {file = "Flask_Login-0.5.0-py2.py3-none-any.whl", hash = "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"}, +] +flask-mail = [ + {file = "Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"}, +] flask-migrate = [ {file = "Flask-Migrate-2.5.3.tar.gz", hash = "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"}, {file = "Flask_Migrate-2.5.3-py2.py3-none-any.whl", hash = "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732"}, ] +flask-principal = [ + {file = "Flask-Principal-0.4.0.tar.gz", hash = "sha256:f5d6134b5caebfdbb86f32d56d18ee44b080876a27269560a96ea35f75c99453"}, +] +flask-security = [ + {file = "Flask-Security-3.0.0.tar.gz", hash = "sha256:d61daa5f5a48f89f30f50555872bdf581b2c65804668b0313345cd7beff26432"}, + {file = "Flask_Security-3.0.0-py2.py3-none-any.whl", hash = "sha256:ef837c03558db41335c8dabd16ae4977af0a5ef0c2cdecf738e33ef5202ce489"}, +] flask-sqlalchemy = [ {file = "Flask-SQLAlchemy-2.4.4.tar.gz", hash = "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"}, {file = "Flask_SQLAlchemy-2.4.4-py2.py3-none-any.whl", hash = "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e"}, ] +flask-wtf = [ + {file = "Flask-WTF-0.14.3.tar.gz", hash = "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720"}, + {file = "Flask_WTF-0.14.3-py2.py3-none-any.whl", hash = "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2"}, +] gitdb = [ {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, @@ -920,6 +1118,10 @@ packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] +passlib = [ + {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, + {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, +] pbr = [ {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"}, {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"}, @@ -1017,6 +1219,10 @@ python-editor = [ {file = "python_editor-1.0.4-py3-none-any.whl", hash = "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d"}, {file = "python_editor-1.0.4-py3.5.egg", hash = "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77"}, ] +pytz = [ + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, +] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, @@ -1042,6 +1248,9 @@ smmap = [ {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"}, {file = "smmap-3.0.4.tar.gz", hash = "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"}, ] +speaklater = [ + {file = "speaklater-1.3.tar.gz", hash = "sha256:59fea336d0eed38c1f0bf3181ee1222d0ef45f3a9dd34ebe65e6bfffdd6a65a9"}, +] sqlalchemy = [ {file = "SQLAlchemy-1.3.20-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bad73f9888d30f9e1d57ac8829f8a12091bdee4949b91db279569774a866a18e"}, {file = "SQLAlchemy-1.3.20-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e32e3455db14602b6117f0f422f46bc297a3853ae2c322ecd1e2c4c04daf6ed5"}, diff --git a/pyproject.toml b/pyproject.toml index 339b90e3..e84ae0ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ Werkzeug = "1.0.1" pyjwt = "^1.7.1" cryptography = "^3.1" flask-admin = "^1.5.6" +Flask-Login = "^0.5.0" +Flask-Security = "^3.0.0" +email-validator = "^1.1.1" [tool.poetry.dev-dependencies] diff --git a/run.py b/run.py index e8df0c18..025b8b5d 100644 --- a/run.py +++ b/run.py @@ -1,11 +1,15 @@ from app import app, cli, admin -from app.models import Category, Language, Resource, db +from app.models import Category, Language, Resource, db, User, Role +from flask_security import Security, SQLAlchemyUserDatastore from werkzeug.middleware.dispatcher import DispatcherMiddleware from prometheus_client import make_wsgi_app admin.run_flask_admin(app) +user_datastore = SQLAlchemyUserDatastore(db, User, Role) +security = Security(app, user_datastore) + if __name__ == "__main__": app.run() From 8b188852db7839c23af5f8255fcdc0aeecec7353 Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Tue, 20 Oct 2020 10:41:46 +0530 Subject: [PATCH 3/8] added migrations for authentication --- app/models.py | 27 ++++++++------- migrations/versions/824f1576e904_.py | 52 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 migrations/versions/824f1576e904_.py diff --git a/app/models.py b/app/models.py index 15186fa2..f2744182 100644 --- a/app/models.py +++ b/app/models.py @@ -5,15 +5,15 @@ from flask_security import RoleMixin, UserMixin language_identifier = db.Table('language_identifier', - db.Column( - 'resource_id', - db.Integer, - db.ForeignKey('resource.id')), - db.Column( - 'language_id', - db.Integer, - db.ForeignKey('language.id')) - ) + db.Column( + 'resource_id', + db.Integer, + db.ForeignKey('resource.id')), + db.Column( + 'language_id', + db.Integer, + db.ForeignKey('language.id')) + ) class TimestampMixin: @@ -223,7 +223,8 @@ class Role(db.Model, RoleMixin): def __str__(self): return self.name - # __hash__ is required to avoid the exception TypeError: unhashable type: 'Role' when saving a User + # __hash__ is required to avoid the exception + # TypeError: unhashable type: 'Role' when saving a User def __hash__(self): return hash(self.name) @@ -231,8 +232,10 @@ def __hash__(self): # User class class User(db.Model, UserMixin): - # Our User has six fields: ID, email, password, active, confirmed_at and roles. The roles field represents a - # many-to-many relationship using the roles_users table. Each user may have no role, one role, or multiple roles. + # Our User has six fields: ID, email, password, active, confirmed_at + # and roles. The roles field represents a many-to-many relationship + # using the roles_users table. Each user may have no role, one role, + # or multiple roles. id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(255), unique=True) password = db.Column(db.String(255)) diff --git a/migrations/versions/824f1576e904_.py b/migrations/versions/824f1576e904_.py new file mode 100644 index 00000000..16669d4a --- /dev/null +++ b/migrations/versions/824f1576e904_.py @@ -0,0 +1,52 @@ +"""empty message + +Revision ID: 824f1576e904 +Revises: 205742d3b3f5 +Create Date: 2020-10-20 10:36:16.978231 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils + + +# revision identifiers, used by Alembic. +revision = '824f1576e904' +down_revision = '205742d3b3f5' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('role', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=80), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('password', sa.String(length=255), nullable=True), + sa.Column('active', sa.Boolean(), nullable=True), + sa.Column('confirmed_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email') + ) + op.create_table('roles_users', + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('role_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['role_id'], ['role.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('roles_users') + op.drop_table('user') + op.drop_table('role') + # ### end Alembic commands ### From c225b58e336beba710a1d4ea21a2907592433630 Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Tue, 20 Oct 2020 10:50:38 +0530 Subject: [PATCH 4/8] lint fixes --- app/admin.py | 11 ++++++----- app/models.py | 18 +++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/admin.py b/app/admin.py index 29fbd614..130be7cc 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,6 +1,6 @@ from flask_admin import Admin, AdminIndexView from flask_admin.contrib.sqla import ModelView -from flask import Flask, url_for, redirect, render_template, request, abort +from flask import url_for, redirect, request from app import db from .models import Resource, Category, Language, User, Role from flask_security import current_user @@ -9,24 +9,25 @@ class AdminView(ModelView): def is_accessible(self): return current_user.has_role("admin") - + def inaccessible_callback(self): return redirect(url_for("security.login", next=request.url)) + class HomeAdminView(AdminIndexView): def is_accessible(self): return current_user.has_role("admin") - + def inaccessible_callback(self, name): return redirect(url_for("security.login", next=request.url)) def run_flask_admin(app): - admin = Admin(app, name="Resources_api", url='/', index_view=HomeAdminView(name="Home")) + admin = Admin(app, name="Resources_api", url='/', + index_view=HomeAdminView(name="Home")) admin.add_view(AdminView(Role, db.session)) admin.add_view(AdminView(User, db.session)) admin.add_view(AdminView(Resource, db.session)) admin.add_view(AdminView(Category, db.session)) admin.add_view(AdminView(Language, db.session)) return admin - diff --git a/app/models.py b/app/models.py index f2744182..8fd4a2b9 100644 --- a/app/models.py +++ b/app/models.py @@ -5,15 +5,15 @@ from flask_security import RoleMixin, UserMixin language_identifier = db.Table('language_identifier', - db.Column( - 'resource_id', - db.Integer, - db.ForeignKey('resource.id')), - db.Column( - 'language_id', - db.Integer, - db.ForeignKey('language.id')) - ) + db.Column( + 'resource_id', + db.Integer, + db.ForeignKey('resource.id')), + db.Column( + 'language_id', + db.Integer, + db.ForeignKey('language.id')) + ) class TimestampMixin: From 1be3dcb2aa4630236b96f350c0b48dcbddb92680 Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Thu, 29 Oct 2020 20:16:04 +0530 Subject: [PATCH 5/8] create admin user before first request --- README.md | 6 +++++ migrations/versions/824f1576e904_.py | 2 +- run.py | 36 ++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c4b94e5..c8d019ef 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,12 @@ If you encounter any errors, please open an issue or contact us on slack in #oc- }' ``` +## Admin Panel Access + +This project has an admin panel which can be used to view and manually edit categories and languages at a higher level than the API allows. +In order to create admin user, set following environment variables before starting the application - +`ADMIN_EMAIL` and `ADMIN_PASSWORD` + ## Development Notes If you make changes to the models.py or other schemas, you need to run a migration and upgrade again: diff --git a/migrations/versions/824f1576e904_.py b/migrations/versions/824f1576e904_.py index 16669d4a..969263ae 100644 --- a/migrations/versions/824f1576e904_.py +++ b/migrations/versions/824f1576e904_.py @@ -12,7 +12,7 @@ # revision identifiers, used by Alembic. revision = '824f1576e904' -down_revision = '205742d3b3f5' +down_revision = 'fc34137ad3ba' branch_labels = None depends_on = None diff --git a/run.py b/run.py index 025b8b5d..a8a3eb1e 100644 --- a/run.py +++ b/run.py @@ -1,6 +1,7 @@ from app import app, cli, admin +import os from app.models import Category, Language, Resource, db, User, Role -from flask_security import Security, SQLAlchemyUserDatastore +from flask_security import Security, SQLAlchemyUserDatastore, utils from werkzeug.middleware.dispatcher import DispatcherMiddleware from prometheus_client import make_wsgi_app @@ -23,4 +24,35 @@ @app.shell_context_processor def make_shell_context(): - return {'db': db, 'Resource': Resource, 'Category': Category, 'Language': Language} + return {'db': db, 'Resource': Resource, 'Category': Category, + 'Language': Language, 'User': User, 'Role': Role} + +# Create Admin user and role. +@app.before_first_request +def before_first_request(): + # Create any database tables that don't exist yet. + db.create_all() + + # Create the Roles "admin" and "user" -- unless they already exist + user_datastore.find_or_create_role(name='admin', description='Administrator') + user_datastore.find_or_create_role(name='user', description='End user') + + admin_email = os.environ.get('ADMIN_EMAIL', 'admin@example.com') + admin_password = os.environ.get('ADMIN_PASSWORD', 'password') + + # Create two Users for testing purposes -- unless they already exists. + # In each case, use Flask-Security utility function to encrypt the password. + encrypted_password = utils.encrypt_password(admin_password) + if not user_datastore.get_user(admin_email): + user_datastore.create_user(admin_email, password=encrypted_password) + # Add more users. + + # Commit any database changes; the User and Roles must exist before we + # can add a Role to the User + db.session.commit() + + # Give one User has the "end-user" role, while the other has the "admin" + # role. (This will have no effect if the + # Users already have these Roles.) Again, commit any database changes. + user_datastore.add_role_to_user(admin_email, 'admin') + db.session.commit() From 6f1de5141eb889940418e695b6b98bce2f2bb5f8 Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Sat, 31 Oct 2020 00:12:25 +0530 Subject: [PATCH 6/8] fixed admin user generation --- configs.py | 19 +++++++++++++------ run.py | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/configs.py b/configs.py index 22bb18e2..9dce8d2c 100644 --- a/configs.py +++ b/configs.py @@ -37,6 +37,16 @@ def get_sys_exec_root_or_drive(): if not all([algolia_app_id, algolia_api_key]): print("Application requires 'ALGOLIA_APP_ID' and 'ALGOLIA_API_KEY' for search") + +secret_key = os.environ.get('SECRET_KEY') + +security_password_hash = 'pbkdf2_sha512' +# Replace this with your own salt. +security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT') +if not all([secret_key, security_password_salt]): + raise KeyError("Keys'SECRET_KEY' and 'SECURITY_PASSWORD_SALT' missing") + + index_name = os.environ.get("INDEX_NAME") @@ -45,12 +55,9 @@ class Config: SQLALCHEMY_DATABASE_URI = f"postgresql://{pg_user}:{pg_pw}@{pg_host}:5432/{pg_db}" - SECRET_KEY = os.urandom(24) - # Set config values for Flask-Security. - # We're using PBKDF2 with salt. - SECURITY_PASSWORD_HASH = 'pbkdf2_sha512' - # Replace this with your own salt. - SECURITY_PASSWORD_SALT = os.urandom(140) + SECRET_KEY = secret_key + SECURITY_PASSWORD_HASH = security_password_hash + SECURITY_PASSWORD_SALT = security_password_salt ALGOLIA_APP_ID = algolia_app_id ALGOLIA_API_KEY = algolia_api_key diff --git a/run.py b/run.py index a8a3eb1e..04f82393 100644 --- a/run.py +++ b/run.py @@ -27,6 +27,7 @@ def make_shell_context(): return {'db': db, 'Resource': Resource, 'Category': Category, 'Language': Language, 'User': User, 'Role': Role} + # Create Admin user and role. @app.before_first_request def before_first_request(): @@ -44,7 +45,8 @@ def before_first_request(): # In each case, use Flask-Security utility function to encrypt the password. encrypted_password = utils.encrypt_password(admin_password) if not user_datastore.get_user(admin_email): - user_datastore.create_user(admin_email, password=encrypted_password) + user_datastore.create_user(email=admin_email, + password=encrypted_password) # Add more users. # Commit any database changes; the User and Roles must exist before we From aa80e29339a76cc7d7fae8253f4f6a07b3bb39dc Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Sat, 31 Oct 2020 17:42:59 +0530 Subject: [PATCH 7/8] added default values for secret_key and security_hash --- configs.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/configs.py b/configs.py index 9dce8d2c..bf44d010 100644 --- a/configs.py +++ b/configs.py @@ -38,13 +38,11 @@ def get_sys_exec_root_or_drive(): print("Application requires 'ALGOLIA_APP_ID' and 'ALGOLIA_API_KEY' for search") -secret_key = os.environ.get('SECRET_KEY') +secret_key = os.environ.get('SECRET_KEY', 'change_secret_key') security_password_hash = 'pbkdf2_sha512' # Replace this with your own salt. -security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT') -if not all([secret_key, security_password_salt]): - raise KeyError("Keys'SECRET_KEY' and 'SECURITY_PASSWORD_SALT' missing") +security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT', '!@#$!!@$%@') index_name = os.environ.get("INDEX_NAME") From 61724f6defe3e4b46d6c15fd63cc04bf8877b7ff Mon Sep 17 00:00:00 2001 From: Damanpreet Singh Date: Sat, 7 Nov 2020 15:17:29 +0530 Subject: [PATCH 8/8] moved default secret_key and security_password to .env --- .env | 2 ++ configs.py | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 033b04cc..c361abba 100644 --- a/.env +++ b/.env @@ -7,3 +7,5 @@ FLASK_DEBUG=1 ALGOLIA_APP_ID=search_id ALGOLIA_API_KEY=search_key INDEX_NAME=resources_api +SECRET_KEY=change_secret_key +SECURITY_PASSWORD_SALT=!@#$!!@$%@ \ No newline at end of file diff --git a/configs.py b/configs.py index bf44d010..ab4e1d0e 100644 --- a/configs.py +++ b/configs.py @@ -38,11 +38,14 @@ def get_sys_exec_root_or_drive(): print("Application requires 'ALGOLIA_APP_ID' and 'ALGOLIA_API_KEY' for search") -secret_key = os.environ.get('SECRET_KEY', 'change_secret_key') +secret_key = os.environ.get('SECRET_KEY', None) security_password_hash = 'pbkdf2_sha512' # Replace this with your own salt. -security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT', '!@#$!!@$%@') +security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT', None) + +if not all([secret_key, security_password_salt]): + print("Application requires 'SECRET_KEY' and 'SECURITY HASH'") index_name = os.environ.get("INDEX_NAME")