From e8c7753cdad4fea9c5245f71079abe717d70c1a0 Mon Sep 17 00:00:00 2001 From: Courtney Date: Mon, 28 Jun 2021 16:24:01 -0700 Subject: [PATCH 01/16] adds card model --- app/models/card.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/card.py b/app/models/card.py index 147eb748..8db8dc29 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1 +1,6 @@ from app import db + +class Card(db.Model): + card_id = db.Column(db.Integer, primary_key=True, autincrement=True) + message = db.Column(db.String) + likes_count = db.Column(db.Integer) \ No newline at end of file From 3f1c894a0d9b1712bbf6b5a93a202af80c5b5fa9 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Mon, 28 Jun 2021 16:24:21 -0700 Subject: [PATCH 02/16] board model updated with id, title, and owner attributes --- app/models/board.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/board.py b/app/models/board.py index 147eb748..4dd789ea 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -1 +1,6 @@ from app import db + +class Board(db.Model): + board_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + title = db.Column(db.String) + owner = db.Column(db.String) \ No newline at end of file From a052165682382321d2b6919da564bdc9a7bf6633 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 09:22:47 -0700 Subject: [PATCH 03/16] models added to app/__init__ file --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index 1c821436..cf780d52 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,6 +19,8 @@ def create_app(): # Import models here for Alembic setup # from app.models.ExampleModel import ExampleModel + from app.models.board import Board + from app.models.card import Card db.init_app(app) migrate.init_app(app, db) From 6f52b29ada6237e6fad3159ea6f1254782fcff12 Mon Sep 17 00:00:00 2001 From: Courtney Date: Tue, 29 Jun 2021 09:46:21 -0700 Subject: [PATCH 04/16] corrects spelling of autoincrement --- app/models/card.py | 2 +- migrations/README | 1 + migrations/alembic.ini | 45 ++++++++++++++++++ migrations/env.py | 96 +++++++++++++++++++++++++++++++++++++++ migrations/script.py.mako | 24 ++++++++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako diff --git a/app/models/card.py b/app/models/card.py index 8db8dc29..4063dd86 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1,6 +1,6 @@ from app import db class Card(db.Model): - card_id = db.Column(db.Integer, primary_key=True, autincrement=True) + card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) likes_count = db.Column(db.Integer) \ No newline at end of file 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..8b3fb335 --- /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 flask import current_app + +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 +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"} From e7b4ee207061e56d7fc46eb31228cf3f3360beb5 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 11:02:45 -0700 Subject: [PATCH 05/16] board blueprint and route added to init file, board post route begun in routes.py --- app/__init__.py | 4 ++-- app/routes.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index cf780d52..d7dbe9a1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -26,8 +26,8 @@ def create_app(): migrate.init_app(app, db) # Register Blueprints here - # from .routes import example_bp - # app.register_blueprint(example_bp) + from .routes import board_bp + app.register_blueprint(board_bp) CORS(app) return app diff --git a/app/routes.py b/app/routes.py index 480b8c4b..2fd5d06a 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,16 @@ from flask import Blueprint, request, jsonify, make_response from app import db +from .models.board import Board -# example_bp = Blueprint('example_bp', __name__) +board_bp = Blueprint("boards", __name__, url_prefix="/boards") + +@board_bp.route("", methods=["POST"], strict_slashes=False) +def create_board(): + request_body=request.get_json() + if "title" in request_body and "owner" in request_body: + new_board = Board(title= request_body["title"], + owner= request_body["owner"]) + db.session.add(new_board) + db.session.commit() + + return \ No newline at end of file From ee56eb6a54b3c9d6a466f3e25138ce5f9193ecfa Mon Sep 17 00:00:00 2001 From: Courtney Date: Tue, 29 Jun 2021 11:24:36 -0700 Subject: [PATCH 06/16] POST boards route complete --- app/models/board.py | 9 ++++- app/routes.py | 8 +++- ...84140d7d4f88_adds_board_and_card_models.py | 40 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/84140d7d4f88_adds_board_and_card_models.py diff --git a/app/models/board.py b/app/models/board.py index 4dd789ea..6d92878b 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -3,4 +3,11 @@ class Board(db.Model): board_id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String) - owner = db.Column(db.String) \ No newline at end of file + owner = db.Column(db.String) + + def to_dict(self): + return { + "id": self.board_id, + "title": self.title, + "owner": self.owner + } \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 2fd5d06a..fce1345b 100644 --- a/app/routes.py +++ b/app/routes.py @@ -13,4 +13,10 @@ def create_board(): db.session.add(new_board) db.session.commit() - return \ No newline at end of file + return make_response({"board": new_board.to_dict()}, 201) + + elif "title" not in request_body: + return make_response({"details": "Title data invalid"}, 400) + + elif "owner" not in request_body: + return make_response({"details": "Owner data invalid"}, 400) \ No newline at end of file diff --git a/migrations/versions/84140d7d4f88_adds_board_and_card_models.py b/migrations/versions/84140d7d4f88_adds_board_and_card_models.py new file mode 100644 index 00000000..6139182a --- /dev/null +++ b/migrations/versions/84140d7d4f88_adds_board_and_card_models.py @@ -0,0 +1,40 @@ +"""adds Board and Card models + +Revision ID: 84140d7d4f88 +Revises: +Create Date: 2021-06-29 09:49:13.108519 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '84140d7d4f88' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('board', + sa.Column('board_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('owner', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('board_id') + ) + op.create_table('card', + sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('message', sa.String(), nullable=True), + sa.Column('likes_count', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('card_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('card') + op.drop_table('board') + # ### end Alembic commands ### From 23d0083cc09715a31a393d328ec1f208d7e502d8 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 11:55:52 -0700 Subject: [PATCH 07/16] board GET route complete --- app/routes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index fce1345b..9ebd3b61 100644 --- a/app/routes.py +++ b/app/routes.py @@ -19,4 +19,10 @@ def create_board(): return make_response({"details": "Title data invalid"}, 400) elif "owner" not in request_body: - return make_response({"details": "Owner data invalid"}, 400) \ No newline at end of file + return make_response({"details": "Owner data invalid"}, 400) + +@board_bp.route("", methods=["GET"], strict_slashes=False) +def get_boards(): + boards = Board.query.all() + boards_response = [board.to_dict() for board in boards] + return make_response(jsonify(boards_response), 200) \ No newline at end of file From 5275d8bd21df8fb89fa59f32040c03adf1e0cb67 Mon Sep 17 00:00:00 2001 From: DanSan <64047600+danisandiaz@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:27:57 -0700 Subject: [PATCH 08/16] Delete migrations directory --- migrations/README | 1 - migrations/alembic.ini | 45 --------- migrations/env.py | 96 ------------------- migrations/script.py.mako | 24 ----- ...84140d7d4f88_adds_board_and_card_models.py | 40 -------- 5 files changed, 206 deletions(-) delete mode 100644 migrations/README delete mode 100644 migrations/alembic.ini delete mode 100644 migrations/env.py delete mode 100644 migrations/script.py.mako delete mode 100644 migrations/versions/84140d7d4f88_adds_board_and_card_models.py diff --git a/migrations/README b/migrations/README deleted file mode 100644 index 98e4f9c4..00000000 --- a/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini deleted file mode 100644 index f8ed4801..00000000 --- a/migrations/alembic.ini +++ /dev/null @@ -1,45 +0,0 @@ -# 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 deleted file mode 100644 index 8b3fb335..00000000 --- a/migrations/env.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import with_statement - -import logging -from logging.config import fileConfig - -from sqlalchemy import engine_from_config -from sqlalchemy import pool -from flask import current_app - -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 -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 deleted file mode 100644 index 2c015630..00000000 --- a/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${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/84140d7d4f88_adds_board_and_card_models.py b/migrations/versions/84140d7d4f88_adds_board_and_card_models.py deleted file mode 100644 index 6139182a..00000000 --- a/migrations/versions/84140d7d4f88_adds_board_and_card_models.py +++ /dev/null @@ -1,40 +0,0 @@ -"""adds Board and Card models - -Revision ID: 84140d7d4f88 -Revises: -Create Date: 2021-06-29 09:49:13.108519 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '84140d7d4f88' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('board', - sa.Column('board_id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('title', sa.String(), nullable=True), - sa.Column('owner', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('board_id') - ) - op.create_table('card', - sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('message', sa.String(), nullable=True), - sa.Column('likes_count', sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint('card_id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('card') - op.drop_table('board') - # ### end Alembic commands ### From 415df019832385ea1a3e5f718c428e76e50091de Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 14:29:20 -0700 Subject: [PATCH 09/16] updated card model with board id FK, added new boards//cards POST route --- app/models/board.py | 2 ++ app/models/card.py | 13 ++++++++- app/routes.py | 27 ++++++++++++++++++- ...0c65ce58412_adds_board_and_card_models.py} | 8 +++--- 4 files changed, 45 insertions(+), 5 deletions(-) rename migrations/versions/{84140d7d4f88_adds_board_and_card_models.py => e0c65ce58412_adds_board_and_card_models.py} (82%) diff --git a/app/models/board.py b/app/models/board.py index 6d92878b..8322a1e9 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -1,3 +1,5 @@ +from flask import current_app + from app import db class Board(db.Model): diff --git a/app/models/card.py b/app/models/card.py index 4063dd86..12a7354d 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1,6 +1,17 @@ +from flask import current_app + from app import db class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) - likes_count = db.Column(db.Integer) \ No newline at end of file + likes_count = db.Column(db.Integer) + board_id = db.Column(db.Integer, db.ForeignKey('board.board_id')) + + def to_dict(self): + return { + "id": self.card_id, + "message": self.message, + "likes_count": self.likes_count, + "board_id": self.board_id + } \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 9ebd3b61..c98b95c0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,8 +1,11 @@ from flask import Blueprint, request, jsonify, make_response from app import db from .models.board import Board +from .models.card import Card + board_bp = Blueprint("boards", __name__, url_prefix="/boards") +card_bp = Blueprint("cards", __name__, url_prefix="/cards") @board_bp.route("", methods=["POST"], strict_slashes=False) def create_board(): @@ -25,4 +28,26 @@ def create_board(): def get_boards(): boards = Board.query.all() boards_response = [board.to_dict() for board in boards] - return make_response(jsonify(boards_response), 200) \ No newline at end of file + return make_response(jsonify(boards_response), 200) + + +@board_bp.route("//cards", methods=["POST"], strict_slashes=False) +def add_card_to_board(board_id): + request_body=request.get_json() + + if "message" in request_body and "likes_count" in request_body: + new_card = Card(message= request_body["message"], + likes_count= request_body["likes_count"], + board_id= int(board_id)) + db.session.add(new_card) + db.session.commit() + + return make_response({"card": new_card.to_dict()},201) + + elif "message" not in request_body: + return make_response({"details": "Message data invalid"}, 400) + + elif "likes_count" not in request_body: + return make_response({"details": "Likes_count data invalid"}, 400) + # likes_count might need to be refactors to default to zero when new card is created!! + diff --git a/migrations/versions/84140d7d4f88_adds_board_and_card_models.py b/migrations/versions/e0c65ce58412_adds_board_and_card_models.py similarity index 82% rename from migrations/versions/84140d7d4f88_adds_board_and_card_models.py rename to migrations/versions/e0c65ce58412_adds_board_and_card_models.py index 6139182a..7f16a9b8 100644 --- a/migrations/versions/84140d7d4f88_adds_board_and_card_models.py +++ b/migrations/versions/e0c65ce58412_adds_board_and_card_models.py @@ -1,8 +1,8 @@ """adds Board and Card models -Revision ID: 84140d7d4f88 +Revision ID: e0c65ce58412 Revises: -Create Date: 2021-06-29 09:49:13.108519 +Create Date: 2021-06-29 14:06:19.739580 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '84140d7d4f88' +revision = 'e0c65ce58412' down_revision = None branch_labels = None depends_on = None @@ -28,6 +28,8 @@ def upgrade(): sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('message', sa.String(), nullable=True), sa.Column('likes_count', sa.Integer(), nullable=True), + sa.Column('board_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['board_id'], ['board.board_id'], ), sa.PrimaryKeyConstraint('card_id') ) # ### end Alembic commands ### From d440fc60f2c031e781ad7b3a859953e52fc0ff46 Mon Sep 17 00:00:00 2001 From: Courtney Date: Tue, 29 Jun 2021 15:00:48 -0700 Subject: [PATCH 10/16] adds GET route for cards of a board_id --- app/routes.py | 8 ++ migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ ...rd_and_card_models.py => 587505944eef_.py} | 8 +- 6 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako rename migrations/versions/{e0c65ce58412_adds_board_and_card_models.py => 587505944eef_.py} (89%) diff --git a/app/routes.py b/app/routes.py index c98b95c0..d5a6ee7c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -51,3 +51,11 @@ def add_card_to_board(board_id): return make_response({"details": "Likes_count data invalid"}, 400) # likes_count might need to be refactors to default to zero when new card is created!! +@board_bp.route("//cards", methods=["GET"], strict_slashes=False) +def get_cards_from_board(board_id): + cards = Card.query.filter_by(board_id=board_id) + if cards: + cards_response = [card.to_dict() for card in cards] + return make_response({"cards": cards_response}, 200) + return make_response("", 200) + 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..8b3fb335 --- /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 flask import current_app + +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 +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/e0c65ce58412_adds_board_and_card_models.py b/migrations/versions/587505944eef_.py similarity index 89% rename from migrations/versions/e0c65ce58412_adds_board_and_card_models.py rename to migrations/versions/587505944eef_.py index 7f16a9b8..88172c22 100644 --- a/migrations/versions/e0c65ce58412_adds_board_and_card_models.py +++ b/migrations/versions/587505944eef_.py @@ -1,8 +1,8 @@ -"""adds Board and Card models +"""empty message -Revision ID: e0c65ce58412 +Revision ID: 587505944eef Revises: -Create Date: 2021-06-29 14:06:19.739580 +Create Date: 2021-06-29 14:41:02.556951 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'e0c65ce58412' +revision = '587505944eef' down_revision = None branch_labels = None depends_on = None From 6b44218824d1a884dcbba34c15743b86e901c2e7 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 15:38:49 -0700 Subject: [PATCH 11/16] Added DELETE route for cards/ endpoint --- app/__init__.py | 4 ++++ app/routes.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index d7dbe9a1..0b17a01a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -29,5 +29,9 @@ def create_app(): from .routes import board_bp app.register_blueprint(board_bp) + from .routes import card_bp + app.register_blueprint(card_bp) + + CORS(app) return app diff --git a/app/routes.py b/app/routes.py index d5a6ee7c..3385d813 100644 --- a/app/routes.py +++ b/app/routes.py @@ -59,3 +59,13 @@ def get_cards_from_board(board_id): return make_response({"cards": cards_response}, 200) return make_response("", 200) +@card_bp.route("/", methods=["DELETE"], strict_slashes=False) +def remove_card(card_id): + card = Card.query.get(card_id) + if card: + db.session.delete(card) + db.session.commit() + return jsonify({ + "details": (f'Card {card.card_id} successfully deleted from Board {card.board_id}') + }), 200 + return "", 404 \ No newline at end of file From 6a94d1ffeb9c5e91c1735c21d944fdd6b2ba9825 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Tue, 29 Jun 2021 15:46:15 -0700 Subject: [PATCH 12/16] refactored POST card route to set default likes_count to 0 --- app/routes.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 3385d813..f1c59d23 100644 --- a/app/routes.py +++ b/app/routes.py @@ -35,9 +35,9 @@ def get_boards(): def add_card_to_board(board_id): request_body=request.get_json() - if "message" in request_body and "likes_count" in request_body: + if "message" in request_body: new_card = Card(message= request_body["message"], - likes_count= request_body["likes_count"], + likes_count= 0, board_id= int(board_id)) db.session.add(new_card) db.session.commit() @@ -47,9 +47,6 @@ def add_card_to_board(board_id): elif "message" not in request_body: return make_response({"details": "Message data invalid"}, 400) - elif "likes_count" not in request_body: - return make_response({"details": "Likes_count data invalid"}, 400) - # likes_count might need to be refactors to default to zero when new card is created!! @board_bp.route("//cards", methods=["GET"], strict_slashes=False) def get_cards_from_board(board_id): From 107c6bb5f38826a8915f451f535c1a20f0ce8666 Mon Sep 17 00:00:00 2001 From: Courtney Date: Tue, 29 Jun 2021 16:05:49 -0700 Subject: [PATCH 13/16] adds PUT route to cards//like endpoint --- app/routes.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index f1c59d23..f24091a6 100644 --- a/app/routes.py +++ b/app/routes.py @@ -65,4 +65,16 @@ def remove_card(card_id): return jsonify({ "details": (f'Card {card.card_id} successfully deleted from Board {card.board_id}') }), 200 - return "", 404 \ No newline at end of file + return "", 404 + +@card_bp.route("//like", methods=["PUT"], strict_slashes=False) +def add_like_to_card(card_id): + card = Card.query.get(card_id) + if card: + card.likes_count = card.likes_count + 1 + db.session.commit() + # what is the front end's response formatting preference??? + return jsonify({ + "details": (f'Card {card.card_id} now has {card.likes_count} likes') + }), 200 + return "", 404 From d74b527a7d39a7ebc177c3553828f709ae7ab5f2 Mon Sep 17 00:00:00 2001 From: Courtney Date: Wed, 30 Jun 2021 11:07:34 -0700 Subject: [PATCH 14/16] adds route to DELETE board and associated cards --- app/routes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/routes.py b/app/routes.py index f24091a6..c5de5602 100644 --- a/app/routes.py +++ b/app/routes.py @@ -31,6 +31,24 @@ def get_boards(): return make_response(jsonify(boards_response), 200) +@board_bp.route("/", methods=["DELETE"], strict_slashes=False) +def remove_board(board_id): + # delete all cards tied to board_id + cards = Card.query.filter_by(board_id=board_id) + if cards: + for card in cards: + db.session.delete(card) + + board = Board.query.get(board_id) + if board: + db.session.delete(board) + db.session.commit() + return jsonify({ + "details": (f'Board {board.board_id} successfully deleted from Inspiration Board') + }), 200 + return "", 404 + + @board_bp.route("//cards", methods=["POST"], strict_slashes=False) def add_card_to_board(board_id): request_body=request.get_json() From 5b2301fb1a1e46e8933220b48d0312b90d054b5e Mon Sep 17 00:00:00 2001 From: Courtney Date: Thu, 1 Jul 2021 14:31:28 -0700 Subject: [PATCH 15/16] adds color attribute to Card model --- app/models/card.py | 2 ++ app/routes.py | 2 +- migrations/versions/b60c02e0a8c7_.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/b60c02e0a8c7_.py diff --git a/app/models/card.py b/app/models/card.py index 12a7354d..479d7707 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -6,6 +6,7 @@ class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) likes_count = db.Column(db.Integer) + color = db.Column(db.String, default=None) board_id = db.Column(db.Integer, db.ForeignKey('board.board_id')) def to_dict(self): @@ -13,5 +14,6 @@ def to_dict(self): "id": self.card_id, "message": self.message, "likes_count": self.likes_count, + "color": self.color, "board_id": self.board_id } \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index c5de5602..415d186d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -30,7 +30,6 @@ def get_boards(): boards_response = [board.to_dict() for board in boards] return make_response(jsonify(boards_response), 200) - @board_bp.route("/", methods=["DELETE"], strict_slashes=False) def remove_board(board_id): # delete all cards tied to board_id @@ -55,6 +54,7 @@ def add_card_to_board(board_id): if "message" in request_body: new_card = Card(message= request_body["message"], + color= request_body["color"], likes_count= 0, board_id= int(board_id)) db.session.add(new_card) diff --git a/migrations/versions/b60c02e0a8c7_.py b/migrations/versions/b60c02e0a8c7_.py new file mode 100644 index 00000000..e7017865 --- /dev/null +++ b/migrations/versions/b60c02e0a8c7_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: b60c02e0a8c7 +Revises: 587505944eef +Create Date: 2021-07-01 14:27:06.398834 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b60c02e0a8c7' +down_revision = '587505944eef' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('card', sa.Column('color', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('card', 'color') + # ### end Alembic commands ### From 5bb6d1ac69cead03a520ec0d76a1064a2044c0b8 Mon Sep 17 00:00:00 2001 From: Daniela Sanchez Date: Thu, 1 Jul 2021 15:17:37 -0700 Subject: [PATCH 16/16] updated card model --- app/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/card.py b/app/models/card.py index 479d7707..e64b46d9 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -6,7 +6,7 @@ class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) likes_count = db.Column(db.Integer) - color = db.Column(db.String, default=None) + color = db.Column(db.String, default="yellow") board_id = db.Column(db.Integer, db.ForeignKey('board.board_id')) def to_dict(self):