Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snow Leopards BE- Annie, Cristal, Aria, Geiselle #23

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2ce93c8
created models
cbcodee Jan 3, 2023
f5d5caf
adds routes folder and registers blueprints in app
anniegallagher Jan 3, 2023
b5ebf46
adds create route for board and to_dict & from_dict to board model
anniegallagher Jan 3, 2023
5f19b03
adds read route for all boards
anniegallagher Jan 3, 2023
2ad0a25
fixed an error in bp naming in card model and added a single board GE…
anniegallagher Jan 3, 2023
9701ce7
added create endpoint for a card and updated model to include a to_di…
anniegallagher Jan 3, 2023
0063205
updated models
cbcodee Jan 4, 2023
16a9c9f
updates read_one_board to include cards in return
anniegallagher Jan 4, 2023
cebe277
updates to_dict to include logic for handling cards in the board
anniegallagher Jan 4, 2023
138abb4
updates to and from dict func to include board_id info
anniegallagher Jan 4, 2023
71df57a
updated likes count to int in card model. dropped migrations and recr…
anniegallagher Jan 4, 2023
9a48255
reverted change to migrate in app
anniegallagher Jan 4, 2023
e6394bd
added delete card
cbcodee Jan 4, 2023
c822212
adds PUT method to update like_count in card
anniegallagher Jan 4, 2023
110b088
created helper function to handle board inputs with duplicate names
anniegallagher Jan 4, 2023
3624a72
working on CRUD tests as additional feature
cbcodee Jan 5, 2023
1526f8c
added test_config to create_app and updated test_routes typo to pass …
anniegallagher Jan 6, 2023
7fa518d
adds test for duplicate board
anniegallagher Jan 6, 2023
44e7312
added two crud tests
cbcodee Jan 6, 2023
45cd247
added read three boards test and duplicate board test
cbcodee Jan 6, 2023
6e1ae94
updates one_board pytest fixture to add board and then separately car…
anniegallagher Jan 6, 2023
da835e0
tests read for invalid title
anniegallagher Jan 6, 2023
3968115
removed unnecessary lines
anniegallagher Jan 6, 2023
0911b69
updates urls to be plural
anniegallagher Jan 17, 2023
d54b54a
sorted cards alphabetically and by likes
cbcodee Jan 17, 2023
d39a73f
added route to boards to retrieve all cards with a specific board. re…
anniegallagher Jan 17, 2023
ac66e2e
fixes id sort so it's newest to oldest
anniegallagher Jan 17, 2023
42c227b
Working on tests
cbcodee Jan 18, 2023
e91277f
Merge branch 'main' of https://github.com/cbcodee/back-end-inspiratio…
cbcodee Jan 18, 2023
4d1b747
adds 's' to the test endpoints to fix broken tests
anniegallagher Jan 18, 2023
275bd4d
Merge branch 'main' of https://github.com/cbcodee/back-end-inspiratio…
anniegallagher Jan 18, 2023
9d0d72b
updated test to return correct data format and deleted redundant test
anniegallagher Jan 18, 2023
f5f6063
added tests to check for sorting by alphabetical message and by likes
anniegallagher Jan 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,33 @@
load_dotenv()


def create_app():
def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI"
)
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_TEST_DATABASE_URI"
)

# 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)

# Register Blueprints here
# from .routes import example_bp
# app.register_blueprint(example_bp)
from .routes import board
from .routes import card

app.register_blueprint(board.bp)
app.register_blueprint(card.bp)

CORS(app)
return app
18 changes: 18 additions & 0 deletions app/models/board.py
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
from app import db


class Board(db.Model):
board_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
owner = db.Column(db.String)
cards = db.relationship("Card", back_populates="board")

def to_dict(self):
board_as_dict = {"id": self.board_id, "title": self.title, "owner": self.owner}

return board_as_dict

@classmethod
def from_dict(cls, board_data):
new_board = cls(title=board_data["title"], owner=board_data["owner"])

return new_board
26 changes: 26 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
from app import db


class Card(db.Model):
card_id = db.Column(db.Integer, primary_key=True)
message = db.Column(db.String(40))
likes_count = db.Column(db.Integer)
board_id = db.Column(db.Integer, db.ForeignKey("board.board_id"))
board = db.relationship("Board", back_populates="cards")

def to_dict(self):
return dict(
id=self.card_id,
message=self.message,
likes_count=self.likes_count,
board_id=self.board_id,
)

@classmethod
def from_dict(cls, card_data):
new_card = cls(
message=card_data["message"],
likes_count=card_data["likes_count"],
board_id=card_data["board_id"],
)

return new_card
4 changes: 0 additions & 4 deletions app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
61 changes: 61 additions & 0 deletions app/routes/board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from flask import Blueprint, request, jsonify, make_response, abort
from app import db
from app.models.board import Board
from app.models.card import Card

bp = Blueprint("board_bp", __name__, url_prefix="/boards")


@bp.route("", methods=["POST"])
def create_board():
request_body = request.get_json()
check_duplicates(request_body["title"])

new_board = Board.from_dict(request_body)

db.session.add(new_board)
db.session.commit()

board_dict = new_board.to_dict()

return make_response(jsonify({"board": board_dict}), 201)


def check_duplicates(board_title):
"""
check whether or not a board with a particular title already exists
"""
test_board = Board.query.filter(Board.title == board_title).first()
if test_board is not None:
abort(
make_response(
{
"details": f"Board {board_title} already exists, please enter a unique title"
},
400,
)
)


@bp.route("", methods=["GET"])
def read_all_boards():
boards = Board.query.all()

boards_response = [board.to_dict() for board in boards]

return jsonify(boards_response), 200


@bp.route("<board_id>/cards", methods=["GET"])
def read_all_cards(board_id):
sort_query = request.args.get("sort")
card_query = Card.query.filter(Card.board_id == board_id)
if sort_query == "asc":
card_query = card_query.order_by(Card.message.asc())
elif sort_query == "likes":
card_query = card_query.order_by(Card.likes_count.desc())
else:
card_query = card_query.order_by(Card.card_id.desc())
card_response = [card.to_dict() for card in card_query]

return jsonify(card_response), 200
53 changes: 53 additions & 0 deletions app/routes/card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from flask import Blueprint, request, jsonify, make_response, abort
from app import db
from app.models.card import Card

bp = Blueprint("card_bp", __name__, url_prefix="/cards")


def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message": f"{cls.__name__} {model_id} invalid"}, 400))

model = cls.query.get(model_id)

if not model:
abort(make_response({"message": f"{cls.__name__} {model_id} not found"}, 404))

return model


@bp.route("", methods=["POST"])
def create_card():
request_body = request.get_json()
new_card = Card.from_dict(request_body)

db.session.add(new_card)
db.session.commit()

card_dict = new_card.to_dict()

return make_response(jsonify({"card": card_dict}), 201)


@bp.route("<card_id>", methods=["DELETE"])
def delete_card(card_id):
card = validate_model(Card, card_id)

db.session.delete(card)
db.session.commit()

return {"details": f"Card {card.card_id} successfully deleted"}


@bp.route("<card_id>/like", methods=["PUT"])
def increase_likes(card_id):
card = validate_model(Card, card_id)

request_body = request.get_json()
card.likes_count = request_body["likes_count"]
db.session.commit()

return make_response(jsonify({"card": card.to_dict()}), 200)
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -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
96 changes: 96 additions & 0 deletions migrations/env.py
Original file line number Diff line number Diff line change
@@ -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()
24 changes: 24 additions & 0 deletions migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -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"}
Loading