From cba368d45303090c981e6153e99302108d33c88f Mon Sep 17 00:00:00 2001 From: Lawrence Kimsey Date: Thu, 26 Mar 2020 21:10:46 -0600 Subject: [PATCH] Finished Module 3 --- .gitignore | 7 + Pipfile | 19 ++ Pipfile.lock | 346 ++++++++++++++++++++++ migrations/README | 1 + migrations/alembic.ini | 45 +++ migrations/env.py | 96 ++++++ migrations/script.py.mako | 24 ++ migrations/versions/505cb269b18c_.py | 45 +++ notes.txt | 7 + statmodels/latest_model.pkl | Bin 0 -> 900 bytes web_app/__init__.py | 32 ++ web_app/iris_classifier.py | 43 +++ web_app/models.py | 56 ++++ web_app/routes/admin_routes.py | 26 ++ web_app/routes/book_routes.py | 33 +++ web_app/routes/home_routes.py | 23 ++ web_app/routes/stats_routes.py | 55 ++++ web_app/routes/twitter_routes.py | 59 ++++ web_app/services/basilica_service.py | 34 +++ web_app/services/stocks_service.py | 24 ++ web_app/services/twitter_service.py | 41 +++ web_app/templates/books.html | 26 ++ web_app/templates/layout.html | 38 +++ web_app/templates/new_book.html | 24 ++ web_app/templates/prepare_to_predict.html | 33 +++ web_app/templates/results.html | 10 + web_app/templates/twbs_layout.html | 88 ++++++ web_app/templates/user.html | 21 ++ 28 files changed, 1256 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/505cb269b18c_.py create mode 100644 notes.txt create mode 100644 statmodels/latest_model.pkl create mode 100644 web_app/__init__.py create mode 100644 web_app/iris_classifier.py create mode 100644 web_app/models.py create mode 100644 web_app/routes/admin_routes.py create mode 100644 web_app/routes/book_routes.py create mode 100644 web_app/routes/home_routes.py create mode 100644 web_app/routes/stats_routes.py create mode 100644 web_app/routes/twitter_routes.py create mode 100644 web_app/services/basilica_service.py create mode 100644 web_app/services/stocks_service.py create mode 100644 web_app/services/twitter_service.py create mode 100644 web_app/templates/books.html create mode 100644 web_app/templates/layout.html create mode 100644 web_app/templates/new_book.html create mode 100644 web_app/templates/prepare_to_predict.html create mode 100644 web_app/templates/results.html create mode 100644 web_app/templates/twbs_layout.html create mode 100644 web_app/templates/user.html diff --git a/.gitignore b/.gitignore index 894a44cc..ed773998 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,10 @@ venv.bak/ # mypy .mypy_cache/ + +# PyCharm project files +.idea + +# Development database: +*.db +*.sqlite3 diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..7ca2df06 --- /dev/null +++ b/Pipfile @@ -0,0 +1,19 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +flask = "*" +flask-sqlalchemy = "*" +flask-migrate = "*" +basilica = "*" +python-dotenv = "*" +requests = "*" +tweepy = "*" +scikit-learn = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..94c4fc2d --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,346 @@ +{ + "_meta": { + "hash": { + "sha256": "321592106e69584d28f8c56e881099adb590ff96316d3de2b8d46d5dc054c7c5" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "alembic": { + "hashes": [ + "sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf" + ], + "version": "==1.4.2" + }, + "basilica": { + "hashes": [ + "sha256:10d111538e7fee4ece43b9a2f0f33c8b6a34dc1a061e44c035b53ac4297e3a4d" + ], + "index": "pypi", + "version": "==0.2.8" + }, + "certifi": { + "hashes": [ + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + ], + "version": "==2019.11.28" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" + ], + "version": "==7.1.1" + }, + "flask": { + "hashes": [ + "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", + "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" + ], + "index": "pypi", + "version": "==1.1.1" + }, + "flask-migrate": { + "hashes": [ + "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", + "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" + ], + "index": "pypi", + "version": "==2.5.3" + }, + "flask-sqlalchemy": { + "hashes": [ + "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", + "sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" + ], + "index": "pypi", + "version": "==2.4.1" + }, + "idna": { + "hashes": [ + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + ], + "version": "==2.9" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + ], + "version": "==2.11.1" + }, + "joblib": { + "hashes": [ + "sha256:0630eea4f5664c463f23fbf5dcfc54a2bc6168902719fa8e19daf033022786c8", + "sha256:bdb4fd9b72915ffb49fde2229ce482dd7ae79d842ed8c2b4c932441495af1403" + ], + "version": "==0.14.1" + }, + "mako": { + "hashes": [ + "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", + "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" + ], + "version": "==1.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + ], + "version": "==1.1.1" + }, + "numpy": { + "hashes": [ + "sha256:1598a6de323508cfeed6b7cd6c4efb43324f4692e20d1f76e1feec7f59013448", + "sha256:1b0ece94018ae21163d1f651b527156e1f03943b986188dd81bc7e066eae9d1c", + "sha256:2e40be731ad618cb4974d5ba60d373cdf4f1b8dcbf1dcf4d9dff5e212baf69c5", + "sha256:4ba59db1fcc27ea31368af524dcf874d9277f21fd2e1f7f1e2e0c75ee61419ed", + "sha256:59ca9c6592da581a03d42cc4e270732552243dc45e87248aa8d636d53812f6a5", + "sha256:5e0feb76849ca3e83dd396254e47c7dba65b3fa9ed3df67c2556293ae3e16de3", + "sha256:6d205249a0293e62bbb3898c4c2e1ff8a22f98375a34775a259a0523111a8f6c", + "sha256:6fcc5a3990e269f86d388f165a089259893851437b904f422d301cdce4ff25c8", + "sha256:82847f2765835c8e5308f136bc34018d09b49037ec23ecc42b246424c767056b", + "sha256:87902e5c03355335fc5992a74ba0247a70d937f326d852fc613b7f53516c0963", + "sha256:9ab21d1cb156a620d3999dd92f7d1c86824c622873841d6b080ca5495fa10fef", + "sha256:a1baa1dc8ecd88fb2d2a651671a84b9938461e8a8eed13e2f0a812a94084d1fa", + "sha256:a244f7af80dacf21054386539699ce29bcc64796ed9850c99a34b41305630286", + "sha256:a35af656a7ba1d3decdd4fae5322b87277de8ac98b7d9da657d9e212ece76a61", + "sha256:b1fe1a6f3a6f355f6c29789b5927f8bd4f134a4bd9a781099a7c4f66af8850f5", + "sha256:b5ad0adb51b2dee7d0ee75a69e9871e2ddfb061c73ea8bc439376298141f77f5", + "sha256:ba3c7a2814ec8a176bb71f91478293d633c08582119e713a0c5351c0f77698da", + "sha256:cd77d58fb2acf57c1d1ee2835567cd70e6f1835e32090538f17f8a3a99e5e34b", + "sha256:cdb3a70285e8220875e4d2bc394e49b4988bdb1298ffa4e0bd81b2f613be397c", + "sha256:deb529c40c3f1e38d53d5ae6cd077c21f1d49e13afc7936f7f868455e16b64a0", + "sha256:e7894793e6e8540dbeac77c87b489e331947813511108ae097f1715c018b8f3d" + ], + "version": "==1.18.2" + }, + "oauthlib": { + "hashes": [ + "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", + "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" + ], + "version": "==3.1.0" + }, + "pillow": { + "hashes": [ + "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", + "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", + "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", + "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", + "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", + "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", + "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", + "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", + "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", + "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", + "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", + "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", + "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", + "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", + "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", + "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", + "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", + "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", + "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", + "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", + "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", + "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" + ], + "version": "==7.0.0" + }, + "pysocks": { + "hashes": [ + "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", + "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", + "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" + ], + "version": "==1.7.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, + "python-dotenv": { + "hashes": [ + "sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f", + "sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed" + ], + "index": "pypi", + "version": "==0.12.0" + }, + "python-editor": { + "hashes": [ + "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", + "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", + "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" + ], + "version": "==1.0.4" + }, + "requests": { + "hashes": [ + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + ], + "index": "pypi", + "version": "==2.23.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" + ], + "version": "==1.3.0" + }, + "scikit-learn": { + "hashes": [ + "sha256:1bf45e62799b6938357cfce19f72e3751448c4b27010e4f98553da669b5bbd86", + "sha256:267ad874b54c67b479c3b45eb132ef4a56ab2b27963410624a413a4e2a3fc388", + "sha256:2d1bb83d6c51a81193d8a6b5f31930e2959c0e1019d49bdd03f54163735dae4b", + "sha256:349ba3d837fb3f7cb2b91486c43713e4b7de17f9e852f165049b1b7ac2f81478", + "sha256:3f4d8eea3531d3eaf613fa33f711113dfff6021d57a49c9d319af4afb46f72f0", + "sha256:4990f0e166292d2a0f0ee528233723bcfd238bfdb3ec2512a9e27f5695362f35", + "sha256:57538d138ba54407d21e27c306735cbd42a6aae0df6a5a30c7a6edde46b0017d", + "sha256:5b722e8bb708f254af028dc2da86d23df5371cba57e24f889b672e7b15423caa", + "sha256:6043e2c4ccfc68328c331b0fc19691be8fb02bd76d694704843a23ad651de902", + "sha256:672ea38eb59b739a8907ec063642b486bcb5a2073dda5b72b7983eeaf1fd67c1", + "sha256:73207dca6e70f8f611f28add185cf3a793c8232a1722f21d82259560dc35cd50", + "sha256:83fc104a799cb340054e485c25dfeee712b36f5638fb374eba45a9db490f16ff", + "sha256:8416150ab505f1813da02cdbdd9f367b05bfc75cf251235015bb09f8674358a0", + "sha256:84e759a766c315deb5c85139ff879edbb0aabcddb9358acf499564ed1c21e337", + "sha256:8ed66ab27b3d68e57bb1f315fc35e595a5c4a1f108c3420943de4d18fc40e615", + "sha256:a7f8aa93f61aaad080b29a9018db93ded0586692c03ddf2122e47dd1d3a14e1b", + "sha256:ddd3bf82977908ff69303115dd5697606e669d8a7eafd7d83bb153ef9e11bd5e", + "sha256:de9933297f8659ee3bb330eafdd80d74cd73d5dab39a9026b65a4156bc479063", + "sha256:ea91a70a992ada395efc3d510cf011dc2d99dc9037bb38cd1cb00e14745005f5", + "sha256:eb4c9f0019abb374a2e55150f070a333c8f990b850d1eb4dfc2765fc317ffc7c", + "sha256:ffce8abfdcd459e72e5b91727b247b401b22253cbd18d251f842a60e26262d6f" + ], + "index": "pypi", + "version": "==0.22.2.post1" + }, + "scipy": { + "hashes": [ + "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", + "sha256:0902a620a381f101e184a958459b36d3ee50f5effd186db76e131cbefcbb96f7", + "sha256:1e3190466d669d658233e8a583b854f6386dd62d655539b77b3fa25bfb2abb70", + "sha256:2cce3f9847a1a51019e8c5b47620da93950e58ebc611f13e0d11f4980ca5fecb", + "sha256:3092857f36b690a321a662fe5496cb816a7f4eecd875e1d36793d92d3f884073", + "sha256:386086e2972ed2db17cebf88610aab7d7f6e2c0ca30042dc9a89cf18dcc363fa", + "sha256:71eb180f22c49066f25d6df16f8709f215723317cc951d99e54dc88020ea57be", + "sha256:770254a280d741dd3436919d47e35712fb081a6ff8bafc0f319382b954b77802", + "sha256:787cc50cab3020a865640aba3485e9fbd161d4d3b0d03a967df1a2881320512d", + "sha256:8a07760d5c7f3a92e440ad3aedcc98891e915ce857664282ae3c0220f3301eb6", + "sha256:8d3bc3993b8e4be7eade6dcc6fd59a412d96d3a33fa42b0fa45dc9e24495ede9", + "sha256:9508a7c628a165c2c835f2497837bf6ac80eb25291055f56c129df3c943cbaf8", + "sha256:a144811318853a23d32a07bc7fd5561ff0cac5da643d96ed94a4ffe967d89672", + "sha256:a1aae70d52d0b074d8121333bc807a485f9f1e6a69742010b33780df2e60cfe0", + "sha256:a2d6df9eb074af7f08866598e4ef068a2b310d98f87dc23bd1b90ec7bdcec802", + "sha256:bb517872058a1f087c4528e7429b4a44533a902644987e7b2fe35ecc223bc408", + "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d", + "sha256:cc971a82ea1170e677443108703a2ec9ff0f70752258d0e9f5433d00dda01f59", + "sha256:dba8306f6da99e37ea08c08fef6e274b5bf8567bb094d1dbe86a20e532aca088", + "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", + "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" + ], + "version": "==1.4.1" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" + ], + "version": "==1.3.15" + }, + "tweepy": { + "hashes": [ + "sha256:8abd828ba51a85a2b5bb7373715d6d3bb32d18ac624e3a4db02e4ef8ab48316b", + "sha256:ecc7f200c86127903017e48824efd008734814e95f3e8e9b45ce0f4120dd08db" + ], + "index": "pypi", + "version": "==3.8.0" + }, + "urllib3": { + "hashes": [ + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + ], + "version": "==1.25.8" + }, + "werkzeug": { + "hashes": [ + "sha256:169ba8a33788476292d04186ab33b01d6add475033dfc07215e6d219cc077096", + "sha256:6dc65cf9091cf750012f56f2cad759fa9e879f511b5ff8685e456b4e3bf90d16" + ], + "version": "==1.0.0" + } + }, + "develop": {} +} diff --git a/migrations/README b/migrations/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 00000000..f8ed4801 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 00000000..94521792 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/505cb269b18c_.py b/migrations/versions/505cb269b18c_.py new file mode 100644 index 00000000..3e4ee032 --- /dev/null +++ b/migrations/versions/505cb269b18c_.py @@ -0,0 +1,45 @@ +"""empty message + +Revision ID: 505cb269b18c +Revises: +Create Date: 2020-03-26 16:16:14.208086 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '505cb269b18c' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('screen_name', sa.String(length=128), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('location', sa.String(), nullable=True), + sa.Column('followers_count', sa.Integer(), nullable=True), + sa.Column('latest_tweet_id', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tweet', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('user_id', sa.BigInteger(), nullable=True), + sa.Column('full_text', sa.String(length=500), nullable=True), + sa.Column('embedding', sa.PickleType(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('tweet') + op.drop_table('user') + # ### end Alembic commands ### diff --git a/notes.txt b/notes.txt new file mode 100644 index 00000000..3f29dbd0 --- /dev/null +++ b/notes.txt @@ -0,0 +1,7 @@ +Flask commands: + +set FLASK_APP=web_app +flask db init +flask db migrate +flask db upgrade +flask run \ No newline at end of file diff --git a/statmodels/latest_model.pkl b/statmodels/latest_model.pkl new file mode 100644 index 0000000000000000000000000000000000000000..ab3efa10f2f641d8cbcb98fbe3529590ac01047e GIT binary patch literal 900 zcmZXTPe>F|9LHze^-trPnrWM9nx?I$lWuC3)^2PdHnE3|U=G1FGdsI8H9NDvnK#-( zLJ^xTT{;I5L5GMA{W;Vj*fk*vyc9ts6%i3ZCRqnBLA`mq)}eQJ^LxMdgNe_aT_hKXNh&q(#cn`LbsYAs?sCne$*Oo{>Ph(dEDknnUv4Iy~gSUi+1kFH- zB`awJRdakjcK&q;wk+a5Ou`0^=6(1^)VD zraq-u;N#2jL6;Iq8*2C}Y^TJtf`)Ohk&E-yuEWuhCJ z>n%X{qn~s9YTmbU^V#s)hfv2$pL4zKRi3A&uKl?DJn!$CfAH|cO8&~~(&)L{kMo5u zPUVfapYl)MeXG5?P#(^nTuj_QOof3(uty^O`8PN)u%5x7n?W!JdrP$&Qu}fd|J@@m zme=ldhTlBBRsXs`ha0EH?)A3@3NVadL?X>M$e;+YZ(V8BRoWke1FjOCg)y}~7xBj3 n`i&zvDDidJ6iP2kz_a7N8#qLUbbt*8*&v%XZ8QiWM`Hf~vpg#Y literal 0 HcmV?d00001 diff --git a/web_app/__init__.py b/web_app/__init__.py new file mode 100644 index 00000000..026bf141 --- /dev/null +++ b/web_app/__init__.py @@ -0,0 +1,32 @@ +# web_app/__init__.py + +from flask import Flask + +from web_app.models import db, migrate +from web_app.routes.home_routes import home_routes +from web_app.routes.book_routes import book_routes +from web_app.routes.twitter_routes import twitter_routes +from web_app.routes.admin_routes import admin_routes +from web_app.routes.stats_routes import stats_routes + + +def create_app(): + app = Flask(__name__) + + app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///web_app_12.db" + + db.init_app(app) + migrate.init_app(app, db) + + app.register_blueprint(home_routes) + app.register_blueprint(book_routes) + app.register_blueprint(twitter_routes) + app.register_blueprint(admin_routes) + app.register_blueprint(stats_routes) + + return app + + +if __name__ == "__main__": + my_app = create_app() + my_app.run(debug=True) diff --git a/web_app/iris_classifier.py b/web_app/iris_classifier.py new file mode 100644 index 00000000..72a95801 --- /dev/null +++ b/web_app/iris_classifier.py @@ -0,0 +1,43 @@ +# web_app/iris_classifier.py + +import os +import pickle + +from sklearn.datasets import load_iris +from sklearn.linear_model import LogisticRegression + +MODEL_FILEPATH = os.path.join(os.path.dirname(__file__), "..", "statmodels", "latest_model.pkl") + + +def train_and_save_model(): + print("TRAINING THE MODEL...") + X, y = load_iris(return_X_y=True) + classifier = LogisticRegression() # for example + classifier.fit(X, y) + + print("SAVING THE MODEL...") + with open(MODEL_FILEPATH, "wb") as model_file: + pickle.dump(classifier, model_file) + + return classifier + + +def load_model(): + print("LOADING THE MODEL...") + with open(MODEL_FILEPATH, "rb") as model_file: + saved_model = pickle.load(model_file) + return saved_model + + +if __name__ == "__main__": + train_and_save_model() + + clf = load_model() + print("CLASSIFIER:", clf) + + X, y = load_iris(return_X_y=True) # just to have some data to use when predicting + inputs = X[:2, :] + print(type(inputs), inputs) + + result = clf.predict(inputs) + print("RESULT:", result) diff --git a/web_app/models.py b/web_app/models.py new file mode 100644 index 00000000..cbaf4029 --- /dev/null +++ b/web_app/models.py @@ -0,0 +1,56 @@ +# web_app/models.py + +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate + +db = SQLAlchemy() + +migrate = Migrate() + + +class Book(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(128)) + author_id = db.Column(db.String(128)) + + +class User(db.Model): + id = db.Column(db.BigInteger, primary_key=True) + screen_name = db.Column(db.String(128), nullable=False) + name = db.Column(db.String) + location = db.Column(db.String) + followers_count = db.Column(db.Integer) + latest_tweet_id = db.Column(db.BigInteger) + + +class Tweet(db.Model): + id = db.Column(db.BigInteger, primary_key=True) + user_id = db.Column(db.BigInteger, db.ForeignKey("user.id")) + full_text = db.Column(db.String(500)) + embedding = db.Column(db.PickleType) + + user = db.relationship("User", backref=db.backref("tweets", lazy=True)) + + +def parse_records(database_records): + """ + A helper method for converting a list of database record objects into a list of dictionaries, so they can be returned as JSON + + Param: database_records (a list of db.Model instances) + + Example: parse_records(User.query.all()) + + Returns: a list of dictionaries, each corresponding to a record, like... + [ + {"id": 1, "title": "Book 1"}, + {"id": 2, "title": "Book 2"}, + {"id": 3, "title": "Book 3"}, + ] + """ + parsed_records = [] + for record in database_records: + print(record) + parsed_record = record.__dict__ + del parsed_record["_sa_instance_state"] + parsed_records.append(parsed_record) + return parsed_records diff --git a/web_app/routes/admin_routes.py b/web_app/routes/admin_routes.py new file mode 100644 index 00000000..cdb87e93 --- /dev/null +++ b/web_app/routes/admin_routes.py @@ -0,0 +1,26 @@ +# web_app/routes/admin_routes.py + +from flask import Blueprint, jsonify, request, render_template, flash, redirect + +from web_app.models import db +from web_app.routes.twitter_routes import store_twitter_user_data + +admin_routes = Blueprint("admin_routes", __name__) + + +@admin_routes.route("/admin/db/reset") +def reset_db(): + print(type(db)) + db.drop_all() + db.create_all() + return jsonify({"message": "DB RESET OK"}) + + +@admin_routes.route("/admin/db/seed") +def seed_db(): + print(type(db)) + default_users = ["elonmusk", "justinbieber", "s2t2", "austen", "nbcnews"] + for screen_name in default_users: + db_user, statuses = store_twitter_user_data(screen_name) + + return jsonify({"message": f"DB SEEDED OK (w/ {len(default_users)})"}) diff --git a/web_app/routes/book_routes.py b/web_app/routes/book_routes.py new file mode 100644 index 00000000..60b89962 --- /dev/null +++ b/web_app/routes/book_routes.py @@ -0,0 +1,33 @@ +from flask import Blueprint, jsonify, render_template, request, redirect + +from web_app.models import db, Book, parse_records + +book_routes = Blueprint("book_routes", __name__) + + +@book_routes.route("/books.json") +def list_books(): + book_records = Book.query.all() + books = parse_records(book_records) + return jsonify(books) + + +@book_routes.route("/books") +def books(): + book_records = Book.query.all() + return render_template("books.html", books=book_records) + + +@book_routes.route("/books/new") +def new_book(): + return render_template("new_book.html") + + +@book_routes.route("/books/create", methods=["POST"]) +def create_book(): + print("FORM DATA:", dict(request.form)) + new_book = Book(title=request.form["book_title"], author_id=request.form["author_name"]) + print(new_book) + db.session.add(new_book) + db.session.commit() + return redirect("/books") diff --git a/web_app/routes/home_routes.py b/web_app/routes/home_routes.py new file mode 100644 index 00000000..d6200178 --- /dev/null +++ b/web_app/routes/home_routes.py @@ -0,0 +1,23 @@ +# web_app/routes/home_routes.py + +from flask import Blueprint, render_template + +home_routes = Blueprint("home_routes", __name__) + + +@home_routes.route("/") +def index(): + print("VISITED THE HOME PAGE") + return render_template("prepare_to_predict.html") + + +@home_routes.route("/hello") +def hello(): + print("VISITED THE HELLO PAGE") + return "Hello World!" + + +@home_routes.route("/about") +def about(): + print("VISITED THE ABOUT PAGE") + return "About Me!" diff --git a/web_app/routes/stats_routes.py b/web_app/routes/stats_routes.py new file mode 100644 index 00000000..7b423076 --- /dev/null +++ b/web_app/routes/stats_routes.py @@ -0,0 +1,55 @@ +# web_app/routes/stats_routes.py + +from flask import Blueprint, request, render_template +from sklearn.linear_model import LogisticRegression +from web_app.models import User +from web_app.services.basilica_service import basilica_api_client + +stats_routes = Blueprint("stats_routes", __name__) + + +@stats_routes.route("/predict", methods=["POST"]) +def predict(): + print("PREDICT ROUTE...") + print("FORM DATA:", dict(request.form)) + screen_name_a = request.form["screen_name_a"] + screen_name_b = request.form["screen_name_b"] + tweet_text = request.form["tweet_text"] + + print("-----------------") + print("FETCHING TWEETS FROM THE DATABASE...") + user_a = User.query.filter(User.screen_name == screen_name_a).one() + user_b = User.query.filter(User.screen_name == screen_name_b).one() + user_a_tweets = user_a.tweets + user_b_tweets = user_b.tweets + print("USER A", user_a.screen_name, len(user_a.tweets)) + print("USER B", user_b.screen_name, len(user_b.tweets)) + + print("-----------------") + print("TRAINING THE MODEL...") + embeddings = [] + labels = [] + for tweet in user_a_tweets: + labels.append(user_a.screen_name) + embeddings.append(tweet.embedding) + + for tweet in user_b_tweets: + labels.append(user_b.screen_name) + embeddings.append(tweet.embedding) + + classifier = LogisticRegression(random_state=0, solver='lbfgs') # for example + classifier.fit(embeddings, labels) + + print("-----------------") + print("MAKING A PREDICTION...") + + basilica_conn = basilica_api_client() + example_embedding = basilica_conn.embed_sentence(tweet_text, model="twitter") + result = classifier.predict([example_embedding]) + + return render_template("results.html", + screen_name_a=screen_name_a, + screen_name_b=screen_name_b, + tweet_text=tweet_text, + screen_name_most_likely=result[0] + ) diff --git a/web_app/routes/twitter_routes.py b/web_app/routes/twitter_routes.py new file mode 100644 index 00000000..fafb9912 --- /dev/null +++ b/web_app/routes/twitter_routes.py @@ -0,0 +1,59 @@ +# web_app/routes/twitter_routes.py + +from flask import Blueprint, render_template, jsonify + +from web_app.models import db, User, Tweet, parse_records +from web_app.services.twitter_service import twitter_api_client +from web_app.services.basilica_service import basilica_api_client + +twitter_routes = Blueprint("twitter_routes", __name__) + + +def store_twitter_user_data(screen_name): + api = twitter_api_client() + twitter_user = api.get_user(screen_name) + statuses = api.user_timeline(screen_name, tweet_mode="extended", count=150) + + db_user = User.query.get(twitter_user.id) or User(id=twitter_user.id) + db_user.screen_name = twitter_user.screen_name + db_user.name = twitter_user.name + db_user.location = twitter_user.location + db_user.followers_count = twitter_user.followers_count + db.session.add(db_user) + db.session.commit() + + print("STATUS COUNT:", len(statuses)) + basilica_api = basilica_api_client() + all_tweet_texts = [status.full_text for status in statuses] + embeddings = list(basilica_api.embed_sentences(all_tweet_texts, model="twitter")) + print("NUMBER OF EMBEDDINGS", len(embeddings)) + counter = 0 + for status in statuses: + print(status.full_text) + print("----") + db_tweet = Tweet.query.get(status.id) or Tweet(id=status.id) + db_tweet.user_id = status.author.id + db_tweet.full_text = status.full_text + embedding = embeddings[counter] + print(len(embedding)) + db_tweet.embedding = embedding + db.session.add(db_tweet) + counter += 1 + db.session.commit() + + return db_user, statuses + + +@twitter_routes.route("/users") +@twitter_routes.route("/users.json") +def list_users(): + db_users = User.query.all() + users = parse_records(db_users) + return jsonify(users) + + +@twitter_routes.route("/users/") +def get_user(screen_name=None): + print(screen_name) + db_user, statuses = store_twitter_user_data(screen_name) + return render_template("user.html", user=db_user, tweets=statuses) diff --git a/web_app/services/basilica_service.py b/web_app/services/basilica_service.py new file mode 100644 index 00000000..00d96f3f --- /dev/null +++ b/web_app/services/basilica_service.py @@ -0,0 +1,34 @@ +# web_app/services/basilica_service.py + +import basilica +import os +from dotenv import load_dotenv + +load_dotenv() + +API_KEY = os.getenv("BASILICA_API_KEY") + + +def basilica_api_client(): + connection = basilica.Connection(API_KEY) + print(type(connection)) # > + return connection + + +if __name__ == "__main__": + print("---------") + connection = basilica_api_client() + + print("---------") + sentence = "Hello again" + sent_embeddings = connection.embed_sentence(sentence) + print(list(sent_embeddings)) + + print("---------") + sentences = ["Hello world!", "How are you?"] + print(sentences) + + embeddings = connection.embed_sentences(sentences) + print("EMBEDDINGS...") + print(type(embeddings)) + print(list(embeddings)) # [[0.8556405305862427, ...], ...] diff --git a/web_app/services/stocks_service.py b/web_app/services/stocks_service.py new file mode 100644 index 00000000..7aefd8ec --- /dev/null +++ b/web_app/services/stocks_service.py @@ -0,0 +1,24 @@ +# web_app/services/stocks_service.py + +import requests +import json +import os +from dotenv import load_dotenv + +API_KEY = os.getenv("ALPHAVANTAGE_API_KEY") + +request_url = f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=TSLA&apikey={API_KEY}" +print(request_url) + +response = requests.get(request_url) +print(type(response)) # > +print(response.status_code) # > 200 +print(type(response.text)) # > + +parsed_response = json.loads(response.text) +print(type(parsed_response)) # > + +latest_close = parsed_response["Time Series (Daily)"]["2020-02-25"]["4. close"] +print("LATEST CLOSING PRICE:", latest_close) + +# breakpoint() diff --git a/web_app/services/twitter_service.py b/web_app/services/twitter_service.py new file mode 100644 index 00000000..a859ae35 --- /dev/null +++ b/web_app/services/twitter_service.py @@ -0,0 +1,41 @@ +# web_app/services/twitter_service.py + +import tweepy +import os +from dotenv import load_dotenv + +load_dotenv() + +TWITTER_API_KEY = os.getenv("TWITTER_API_KEY") +TWITTER_API_SECRET = os.getenv("TWITTER_API_SECRET") +TWITTER_ACCESS_TOKEN = os.getenv("TWITTER_ACCESS_TOKEN") +TWITTER_ACCESS_TOKEN_SECRET = os.getenv("TWITTER_ACCESS_TOKEN_SECRET") + + +def twitter_api_client(): + auth = tweepy.OAuthHandler(TWITTER_API_KEY, TWITTER_API_SECRET) + auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET) + print("AUTH", auth) + api = tweepy.API(auth) + print("API", api) + # print(dir(api)) + return api + + +if __name__ == "__main__": + api = twitter_api() + user = api.get_user("elonmusk") + print("USER", user) + print(user.screen_name) + print(user.name) + print(user.followers_count) + + # breakpoint() + + # public_tweets = api.home_timeline() + # + # for tweet in public_tweets: + # print(type(tweet)) #> + # #print(dir(tweet)) + # print(tweet.text) + # print("-------------") diff --git a/web_app/templates/books.html b/web_app/templates/books.html new file mode 100644 index 00000000..3c766911 --- /dev/null +++ b/web_app/templates/books.html @@ -0,0 +1,26 @@ + + +{% extends "layout.html" %} + +{% block title %} + Books Page +{% endblock %} + +{% block content %} + +

Welcome to the Books Page

+ +

{{ message }}

+ + {% if books %} +
    + {% for book in books %} +
  • {{ book.title }}
  • + {% endfor %} +
+ + {% else %} +

Books not found.

+ {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/web_app/templates/layout.html b/web_app/templates/layout.html new file mode 100644 index 00000000..ee32c6d8 --- /dev/null +++ b/web_app/templates/layout.html @@ -0,0 +1,38 @@ + + + + {% block title %} + My Starter Web App | Helps students learn how to use the Flask Python package. + {% endblock %} + + + + + +
+ +
+ + +
+ {% block content %} + {% endblock %} +
+ + + +
+ + + \ No newline at end of file diff --git a/web_app/templates/new_book.html b/web_app/templates/new_book.html new file mode 100644 index 00000000..443ba66e --- /dev/null +++ b/web_app/templates/new_book.html @@ -0,0 +1,24 @@ +{% extends "layout.html" %} + +{% block content %} + +

New Book Form

+ +

Hello, use the form below to create a new book...

+ +
+ + + + + + + + +
+ +{% endblock %} \ No newline at end of file diff --git a/web_app/templates/prepare_to_predict.html b/web_app/templates/prepare_to_predict.html new file mode 100644 index 00000000..b9e53013 --- /dev/null +++ b/web_app/templates/prepare_to_predict.html @@ -0,0 +1,33 @@ +{% extends "twbs_layout.html" %} +{% set active_page = "index" %} + +{% block content %} +

Let's make a prediction

+ +

Use the form below to predict which user is more likely to say a given tweet...

+ +
+ + + +
+ + + +
+ + + +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/web_app/templates/results.html b/web_app/templates/results.html new file mode 100644 index 00000000..3150ae14 --- /dev/null +++ b/web_app/templates/results.html @@ -0,0 +1,10 @@ +{% extends "twbs_layout.html" %} + +{% block content %} +

Prediction Results!

+ +

Between '@{{ screen_name_a }}' and '@{{ screen_name_b }}', + the user who is most likely to say '{{ tweet_text }}' + is '@{{ screen_name_most_likely }}' +

+{% endblock %} \ No newline at end of file diff --git a/web_app/templates/twbs_layout.html b/web_app/templates/twbs_layout.html new file mode 100644 index 00000000..246f873c --- /dev/null +++ b/web_app/templates/twbs_layout.html @@ -0,0 +1,88 @@ + + + + {% block title %} + My Starter Web App | Helps students learn how to use the Flask Python package. + {% endblock %} + + + + + + + + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + + + + {% set nav_links = [ + ('/', 'index', 'Home'), + ('/books', 'books', 'Books'), + ('/books/new', 'new_book', 'New Book') + ] -%} + {% set active_page = active_page|default('index') -%} + + + + + +
+ +
+ {% block content %} + {% endblock %} +
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/web_app/templates/user.html b/web_app/templates/user.html new file mode 100644 index 00000000..8896fc2b --- /dev/null +++ b/web_app/templates/user.html @@ -0,0 +1,21 @@ +{% extends "layout.html" %} + +{% block content %} +

Twitter User: {{ user.screen_name }}

+ +

Name: {{ user.name }}

+

Location: {{ user.location }}

+

Followers: {{ user.followers_count }}

+ + {% if tweets %} +
    + {% for status in tweets %} +
  • {{ status.full_text }}
  • + {% endfor %} +
+ + {% else %} +

No tweets found.

+ {% endif %} + +{% endblock %} \ No newline at end of file