diff --git a/twitoff-kristine/Pipfile b/twitoff-kristine/Pipfile new file mode 100644 index 00000000..62b71683 --- /dev/null +++ b/twitoff-kristine/Pipfile @@ -0,0 +1,242 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +flake8 = "*" + +[packages] +alabaster = "==0.7.12" +anaconda-client = "==1.7.2" +anaconda-navigator = "==1.9.12" +anaconda-project = "==0.8.3" +applaunchservices = "==0.2.1" +appnope = "==0.1.0" +appscript = "==1.0.1" +argh = "==0.26.2" +asn1crypto = "==1.3.0" +astroid = "==2.3.3" +astropy = "==4.0" +atomicwrites = "==1.3.0" +attrs = "==19.3.0" +autopep8 = "==1.4.4" +backcall = "==0.1.0" +beautifulsoup4 = "==4.8.2" +bitarray = "==1.2.1" +bkcharts = "==0.2" +bleach = "==3.1.0" +bokeh = "==1.4.0" +boto = "==2.49.0" +certifi = "==2019.11.28" +cffi = "==1.14.0" +chardet = "==3.0.4" +click = "==7.0" +cloudpickle = "==1.3.0" +clyent = "==1.2.2" +colorama = "==0.4.3" +conda = "==4.8.2" +conda-build = "==3.18.11" +conda-package-handling = "==1.6.0" +conda-verify = "==3.4.2" +contextlib2 = "==0.6.0.post1" +cryptography = "==2.8" +cytoolz = "==0.10.1" +dask = "==2.11.0" +decorator = "==4.4.1" +defusedxml = "==0.6.0" +diff-match-patch = "==20181111" +distributed = "==2.11.0" +docutils = "==0.16" +entrypoints = "==0.3" +fastcache = "==1.1.0" +filelock = "==3.0.12" +flake8 = "==3.7.9" +fsspec = "==0.6.2" +future = "==0.18.2" +gevent = "==1.4.0" +glob2 = "==0.7" +gmpy2 = "==2.0.8" +greenlet = "==0.4.15" +h5py = "==2.10.0" +html5lib = "==1.0.1" +hypothesis = "==5.5.4" +idna = "==2.8" +imageio = "==2.6.1" +imagesize = "==1.2.0" +importlib-metadata = "==1.5.0" +intervaltree = "==3.0.2" +ipykernel = "==5.1.4" +ipython = "==7.12.0" +ipywidgets = "==7.5.1" +isort = "==4.3.21" +itsdangerous = "==1.1.0" +jdcal = "==1.4.1" +jedi = "==0.14.1" +joblib = "==0.14.1" +json5 = "==0.9.1" +jsonschema = "==3.2.0" +jupyter = "==1.0.0" +jupyter-client = "==5.3.4" +jupyter-console = "==6.1.0" +jupyter-core = "==4.6.1" +jupyterlab = "==1.2.6" +jupyterlab-server = "==1.0.6" +keyring = "==21.1.0" +kiwisolver = "==1.1.0" +lazy-object-proxy = "==1.4.3" +libarchive-c = "==2.8" +lief = "==0.9.0" +llvmlite = "==0.31.0" +locket = "==0.2.0" +lxml = "==4.5.0" +matplotlib = "==3.1.3" +mccabe = "==0.6.1" +mistune = "==0.8.4" +mkl-fft = "==1.0.15" +mkl-random = "==1.1.0" +mkl-service = "==2.3.0" +mock = "==4.0.1" +more-itertools = "==8.2.0" +mpmath = "==1.1.0" +msgpack = "==0.6.1" +multipledispatch = "==0.6.0" +navigator-updater = "==0.2.1" +nbconvert = "==5.6.1" +nbformat = "==5.0.4" +networkx = "==2.4" +nltk = "==3.4.5" +nose = "==1.3.7" +notebook = "==6.0.3" +numba = "==0.48.0" +numexpr = "==2.7.1" +numpy = "==1.18.1" +numpydoc = "==0.9.2" +olefile = "==0.46" +openpyxl = "==3.0.3" +pandas = "==1.0.1" +pandocfilters = "==1.4.2" +parso = "==0.5.2" +partd = "==1.1.0" +path = "==13.1.0" +pathlib2 = "==2.3.5" +pathtools = "==0.1.2" +patsy = "==0.5.1" +pep8 = "==1.7.1" +pexpect = "==4.8.0" +pickleshare = "==0.7.5" +pkginfo = "==1.5.0.1" +pluggy = "==0.13.1" +ply = "==3.11" +prometheus-client = "==0.7.1" +prompt-toolkit = "==3.0.3" +psutil = "==5.6.7" +ptyprocess = "==0.6.0" +py = "==1.8.1" +pycodestyle = "==2.5.0" +pycosat = "==0.6.3" +pycparser = "==2.19" +pycrypto = "==2.6.1" +pycurl = "==7.43.0.5" +pydocstyle = "==4.0.1" +pyflakes = "==2.1.1" +pylint = "==2.4.4" +pyodbc = "===4.0.0-unsupported" +pyparsing = "==2.4.6" +pyrsistent = "==0.15.7" +pytest = "==5.3.5" +pytest-arraydiff = "==0.3" +pytest-astropy = "==0.8.0" +pytest-astropy-header = "==0.1.2" +pytest-doctestplus = "==0.5.0" +pytest-openfiles = "==0.4.0" +pytest-remotedata = "==0.3.2" +python-dateutil = "==2.8.1" +python-jsonrpc-server = "==0.3.4" +python-language-server = "==0.31.7" +pytz = "==2019.3" +pyzmq = "==18.1.1" +qtconsole = "==4.6.0" +requests = "==2.22.0" +rope = "==0.16.0" +scikit-image = "==0.16.2" +scikit-learn = "==0.22.1" +scipy = "==1.4.1" +seaborn = "==0.10.0" +simplegeneric = "==0.8.1" +singledispatch = "==3.4.0.3" +six = "==1.14.0" +snowballstemmer = "==2.0.0" +sortedcollections = "==1.1.2" +sortedcontainers = "==2.1.0" +soupsieve = "==1.9.5" +sphinxcontrib-applehelp = "==1.0.1" +sphinxcontrib-devhelp = "==1.0.1" +sphinxcontrib-htmlhelp = "==1.0.2" +sphinxcontrib-jsmath = "==1.0.1" +sphinxcontrib-qthelp = "==1.0.2" +sphinxcontrib-serializinghtml = "==1.1.3" +sphinxcontrib-websupport = "==1.2.0" +spyder = "==4.0.1" +spyder-kernels = "==1.8.1" +statsmodels = "==0.11.0" +sympy = "==1.5.1" +tables = "==3.6.1" +tblib = "==1.6.0" +terminado = "==0.8.3" +testpath = "==0.4.4" +toolz = "==0.10.0" +tornado = "==6.0.3" +tqdm = "==4.42.1" +traitlets = "==4.3.3" +ujson = "==1.35" +unicodecsv = "==0.14.1" +urllib3 = "==1.25.8" +watchdog = "==0.10.2" +wcwidth = "==0.1.8" +webencodings = "==0.5.1" +widgetsnbextension = "==3.5.1" +wrapt = "==1.11.2" +wurlitzer = "==2.0.0" +xlrd = "==1.2.0" +xlwings = "==0.17.1" +xlwt = "==1.3.0" +xmltodict = "==0.12.0" +yapf = "==0.28.0" +zict = "==1.0.0" +zipp = "==2.2.0" +Babel = "==2.8.0" +"backports.functools-lru-cache" = "==1.6.1" +"backports.shutil_get_terminal_size" = "==1.0.0" +"backports.tempfile" = "==1.0" +"backports.weakref" = "==1.0.post1" +Bottleneck = "==1.3.2" +Cycler = "==0.10.0" +Cython = "==0.29.15" +et_xmlfile = "==1.0.1" +Flask = "==1.1.1" +HeapDict = "==1.0.1" +ipython_genutils = "==0.2.0" +Jinja2 = "==2.11.1" +MarkupSafe = "==1.1.1" +Pillow = "==7.0.0" +Pygments = "==2.5.2" +pyOpenSSL = "==19.1.0" +PySocks = "==1.7.1" +PyWavelets = "==1.1.1" +PyYAML = "==5.3" +QDarkStyle = "==2.8" +QtAwesome = "==0.6.1" +QtPy = "==1.9.0" +Rtree = "==0.9.3" +"ruamel.yaml" = "==0.15.87" +Send2Trash = "==1.5.0" +Sphinx = "==2.4.0" +SQLAlchemy = "==1.3.13" +Werkzeug = "==1.0.0" +XlsxWriter = "==1.2.7" +flask-sqlalchemy = "*" +flask-migrate = "*" + +[requires] +python_version = "3.7" diff --git a/twitoff-kristine/README.md b/twitoff-kristine/README.md new file mode 100644 index 00000000..3d5cb7fc --- /dev/null +++ b/twitoff-kristine/README.md @@ -0,0 +1,38 @@ +# twitoff-kristine + +## Installation + +```sh +git clone https://github.com/KristineYW/twitoff-kristine.git +cd twitoff-kristine/ +``` + +## Setup + +Setup and activate virtual encvironment: + +```sh +pipenv install +pipenv shell +``` + +Setup the database: + +```sh +# Windows users can omit the "FLASK_APP=web_app" part... + +FLASK_APP=web_app flask db init #> generates app/migrations dir + +# run both when changing the schema: +FLASK_APP=web_app flask db migrate #> creates the db (with "alembic_version" table) +FLASK_APP=web_app flask db upgrade #> creates the specified tables +``` + +## Usage + +Run the web app: + +```sh +FLASK_APP=web_app flask run +``` + diff --git a/twitoff-kristine/__pycache__/hello.cpython-37.pyc b/twitoff-kristine/__pycache__/hello.cpython-37.pyc new file mode 100644 index 00000000..b7720695 Binary files /dev/null and b/twitoff-kristine/__pycache__/hello.cpython-37.pyc differ diff --git a/twitoff-kristine/migrations/README b/twitoff-kristine/migrations/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/twitoff-kristine/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/twitoff-kristine/migrations/__pycache__/env.cpython-37.pyc b/twitoff-kristine/migrations/__pycache__/env.cpython-37.pyc new file mode 100644 index 00000000..34dd00c6 Binary files /dev/null and b/twitoff-kristine/migrations/__pycache__/env.cpython-37.pyc differ diff --git a/twitoff-kristine/migrations/alembic.ini b/twitoff-kristine/migrations/alembic.ini new file mode 100644 index 00000000..f8ed4801 --- /dev/null +++ b/twitoff-kristine/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/twitoff-kristine/migrations/env.py b/twitoff-kristine/migrations/env.py new file mode 100644 index 00000000..94521792 --- /dev/null +++ b/twitoff-kristine/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/twitoff-kristine/migrations/script.py.mako b/twitoff-kristine/migrations/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/twitoff-kristine/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/twitoff-kristine/migrations/versions/4c94855c06c7_.py b/twitoff-kristine/migrations/versions/4c94855c06c7_.py new file mode 100644 index 00000000..1baacc15 --- /dev/null +++ b/twitoff-kristine/migrations/versions/4c94855c06c7_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: 4c94855c06c7 +Revises: +Create Date: 2020-06-15 18:25:17.488417 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '4c94855c06c7' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('book', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=128), nullable=True), + sa.Column('author_id', sa.String(length=128), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('book') + # ### end Alembic commands ### diff --git a/twitoff-kristine/migrations/versions/__pycache__/4c94855c06c7_.cpython-37.pyc b/twitoff-kristine/migrations/versions/__pycache__/4c94855c06c7_.cpython-37.pyc new file mode 100644 index 00000000..b3669665 Binary files /dev/null and b/twitoff-kristine/migrations/versions/__pycache__/4c94855c06c7_.cpython-37.pyc differ diff --git a/twitoff-kristine/web_app/__init__.py b/twitoff-kristine/web_app/__init__.py new file mode 100644 index 00000000..5d5a2839 --- /dev/null +++ b/twitoff-kristine/web_app/__init__.py @@ -0,0 +1,26 @@ +# 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 + +DATABASE_URL = "sqlite:///twitoff_kristine_development.db" # using relative filepath + +def create_app(): + app = Flask(__name__) + + app.config["SQLALCHEMY_DATABASE_URI"] = DATABASE_URL + app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + db.init_app(app) + migrate.init_app(app, db) + + app.register_blueprint(home_routes) + app.register_blueprint(book_routes) + return app + + +if __name__ == "__main__": + my_app = create_app() + my_app.run(debug=True) diff --git a/twitoff-kristine/web_app/__pycache__/__init__.cpython-37.pyc b/twitoff-kristine/web_app/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 00000000..9745702b Binary files /dev/null and b/twitoff-kristine/web_app/__pycache__/__init__.cpython-37.pyc differ diff --git a/twitoff-kristine/web_app/__pycache__/models.cpython-37.pyc b/twitoff-kristine/web_app/__pycache__/models.cpython-37.pyc new file mode 100644 index 00000000..19f75675 Binary files /dev/null and b/twitoff-kristine/web_app/__pycache__/models.cpython-37.pyc differ diff --git a/twitoff-kristine/web_app/models.py b/twitoff-kristine/web_app/models.py new file mode 100644 index 00000000..042ce275 --- /dev/null +++ b/twitoff-kristine/web_app/models.py @@ -0,0 +1,41 @@ +# 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)) + + def __repr__(self): + return f"" + + +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/twitoff-kristine/web_app/routes/__pycache__/book_routes.cpython-37.pyc b/twitoff-kristine/web_app/routes/__pycache__/book_routes.cpython-37.pyc new file mode 100644 index 00000000..b37fa30a Binary files /dev/null and b/twitoff-kristine/web_app/routes/__pycache__/book_routes.cpython-37.pyc differ diff --git a/twitoff-kristine/web_app/routes/__pycache__/home_routes.cpython-37.pyc b/twitoff-kristine/web_app/routes/__pycache__/home_routes.cpython-37.pyc new file mode 100644 index 00000000..5370629b Binary files /dev/null and b/twitoff-kristine/web_app/routes/__pycache__/home_routes.cpython-37.pyc differ diff --git a/twitoff-kristine/web_app/routes/book_routes.py b/twitoff-kristine/web_app/routes/book_routes.py new file mode 100644 index 00000000..a90c7da6 --- /dev/null +++ b/twitoff-kristine/web_app/routes/book_routes.py @@ -0,0 +1,45 @@ +# web_app/routes/book_routes.py + +from flask import Blueprint, jsonify, request, render_template #, flash, redirect +from web_app.models import Book, db, parse_records + +book_routes = Blueprint("book_routes", __name__) + + +@book_routes.route("/books.json") +def list_books(): + book_records = Book.query.all() + print(book_records) + books = parse_records(book_records) + return jsonify(books) + + +@book_routes.route("/books") +def list_books_for_humans(): + books = [ + {"id": 1, "title": "Book 1"}, + {"id": 2, "title": "Book 2"}, + {"id": 3, "title": "Book 3"}, + ] + return render_template("books.html", message="Here's some books", books=books) + + +@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"]) + db.session.add(new_book) + db.session.commit() + + return jsonify({ + "message": "BOOK CREATED OK", + "book": dict(request.form) + }) + #flash(f"Book '{new_book.title}' created successfully!", "success") + #return redirect(f"/books") diff --git a/twitoff-kristine/web_app/routes/home_routes.py b/twitoff-kristine/web_app/routes/home_routes.py new file mode 100644 index 00000000..adb359d6 --- /dev/null +++ b/twitoff-kristine/web_app/routes/home_routes.py @@ -0,0 +1,16 @@ +# web_app/routes/home_routes.py + +from flask import Blueprint + +home_routes = Blueprint("home_routes", __name__) + + +@home_routes.route("/") +def index(): + x = 2 + 2 + return f"Hello World! {x}" + + +@home_routes.route("/about") +def about(): + return "About me" diff --git a/twitoff-kristine/web_app/templates/books.html b/twitoff-kristine/web_app/templates/books.html new file mode 100644 index 00000000..d50d80b3 --- /dev/null +++ b/twitoff-kristine/web_app/templates/books.html @@ -0,0 +1,27 @@ + + + + + + + Our Books Page! + + + +

Welcome to the Books Page

+ +

{{ message }}

+ + {% if books %} + + + {% else %} +

Books not found.

+ {% endif %} + + + \ No newline at end of file diff --git a/twitoff-kristine/web_app/templates/new_book.html b/twitoff-kristine/web_app/templates/new_book.html new file mode 100644 index 00000000..8df506b9 --- /dev/null +++ b/twitoff-kristine/web_app/templates/new_book.html @@ -0,0 +1,31 @@ + + + + + + + Document + + + +

New Book Page

+ +

Please fill out the form and submit to create a new book!

+ +
+ + + + + + + + +
+ + + \ No newline at end of file diff --git a/twitoff-kristine/web_app/twitoff_kristine_development.db b/twitoff-kristine/web_app/twitoff_kristine_development.db new file mode 100644 index 00000000..fae3b743 Binary files /dev/null and b/twitoff-kristine/web_app/twitoff_kristine_development.db differ