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

Zoisite Z2- Muniba, Jackie, Johanna, Katherine #19

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cbe2172
Adds Board and Card models
bashmu33 Jun 26, 2023
e25f4a3
Adds relationship between Board and Cards
bashmu33 Jun 26, 2023
61e249b
adds foreign key to establish relationship
Kguarnizo Jun 26, 2023
2407bd9
complete GET endpoint for board model in get-board branch
Kguarnizo Jun 26, 2023
d155ec6
Adds validate model helper, validate message helper and board routes …
bashmu33 Jun 27, 2023
7f8e516
Adds post board endpoint and from dict helper
johanna-j-c Jun 27, 2023
aa17a55
Merge GET and POST boards endpoints
johanna-j-c Jun 27, 2023
91ed486
Merge branch 'muniba'
bashmu33 Jun 27, 2023
b63a7a3
Merge POST cards route to main branch
bashmu33 Jun 27, 2023
83f2aae
made updates to board.py and board_routes.py
Kguarnizo Jun 27, 2023
650d617
refactored post card route
bashmu33 Jun 27, 2023
71e7173
Adds card routes file and delete card endpoint
johanna-j-c Jun 27, 2023
7362b49
Add PATCH endpoint for card
Kguarnizo Jun 27, 2023
b221c8a
wave 1
japonte08 Jun 28, 2023
eff3be2
Wave 1
japonte08 Jun 28, 2023
da6112d
added GET endpoint for cards in boards
japonte08 Jun 28, 2023
b0f9f23
fixed syntax
japonte08 Jun 28, 2023
70a3f15
Added pytest fixtures to conftest.py
bashmu33 Jun 28, 2023
0cae6e2
add test config in __init__ file
Kguarnizo Jun 28, 2023
cdbab06
update conftest file
Kguarnizo Jun 28, 2023
4f7f23b
add blinker dependencies
Kguarnizo Jun 28, 2023
3c52def
Create first test in tests
Kguarnizo Jun 28, 2023
e619518
add test for deleting a card
Kguarnizo Jun 28, 2023
4255a67
Adds test get card not found
johanna-j-c Jun 29, 2023
6361848
Adds test update like on card
johanna-j-c Jun 29, 2023
2f84bf0
Adds test for get one saved card
bashmu33 Jun 29, 2023
6d39a69
added create one card test
japonte08 Jun 29, 2023
1c5fafd
Add delete card test to main after merging
Kguarnizo Jun 29, 2023
11b2651
Merge test get card not found
johanna-j-c Jun 29, 2023
bd8a6eb
Merge branch 'test_update_like_on_card'
johanna-j-c Jun 29, 2023
5ed46d2
post one card wip
japonte08 Jun 29, 2023
8c19f8f
updated post one card response and test. updated card and board model…
japonte08 Jun 29, 2023
b44d323
merged tests
japonte08 Jun 29, 2023
9edfca6
update tests to all pass
Kguarnizo Jun 29, 2023
0076d66
update validate_message helper function for error handling in the fro…
Kguarnizo Nov 6, 2023
7a7e89e
fix test_update_on_card based on updating original PATCH increase car…
Kguarnizo Dec 14, 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
31 changes: 20 additions & 11 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,31 @@
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")

# Import models here for Alembic setup
# from app.models.ExampleModel import ExampleModel

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

db.init_app(app)
migrate.init_app(app, db)

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

from app.models.board import Board
from app.models.card import Card

from .board_routes import boards_bp
app.register_blueprint(boards_bp)

from .card_routes import cards_bp
app.register_blueprint(cards_bp)

CORS(app)
return app
83 changes: 83 additions & 0 deletions app/board_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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
from app.helper import validate_model, validate_message

boards_bp = Blueprint('boards', __name__, url_prefix='/boards')

@boards_bp.route('', methods=['GET'])
def get_all_boards():
boards = Board.query.all()

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

return jsonify(boards_response)

@boards_bp.route('/<board_id>', methods=['GET'])
def get_one_board(board_id):
board = validate_model(Board, board_id)

return jsonify(board.to_dict())

@boards_bp.route('', methods=['POST'])
def create_board():
request_body = request.get_json()

try:
new_board = Board.from_dict(request_body)

errors = []

if not new_board.title:
errors.append({"field": "title", "message": "Title is required"})

if not new_board.owner:
errors.append({"field": "owner", "message": "Owner is required"})

if errors:
return make_response(jsonify(errors=errors), 400)

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

return make_response({'board': new_board.to_dict()}, 201)
except:
return make_response({'details': 'Invalid data'}, 400)

@boards_bp.route('/<board_id>', methods=['DELETE'])
def delete_board(board_id):
board_to_delete = validate_model(Board, board_id)

db.session.delete(board_to_delete)
db.session.commit()

message = f'Board {board_id} successfully deleted'
return make_response({"details": message}, 200)

@boards_bp.route('/<board_id>/cards', methods=['POST'])
def create_card(board_id):
board = validate_model(Board, board_id)
request_body = request.get_json()
message = request_body.get("message")

validated_message = validate_message(message)
if validated_message is not None:
return validated_message

card = Card(message=message, likes_count=0, board=board)

db.session.add(card)
db.session.commit()

return make_response(card.to_dict()), 201

@boards_bp.route('/<board_id>/cards', methods=['GET'])
def get_all_cards(board_id):
board = validate_model(Board, board_id)

cards = Card.query.filter_by(board=board)

cards_response = [card.to_dict() for card in cards]

return jsonify(cards_response)
53 changes: 53 additions & 0 deletions app/card_routes.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
from app.helper import validate_model, validate_message

cards_bp = Blueprint('cards', __name__, url_prefix='/cards')

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

db.session.delete(card_to_delete)
db.session.commit()

message = f'Card {card_id} successfully deleted'
return make_response({"details": message}, 200)

@cards_bp.route("/<card_id>/increase", methods=["PATCH"])
def increase_likes(card_id):
card = validate_model(Card, card_id)

card.likes_count += 1

db.session.commit()
return jsonify(card.to_dict()), 200

@cards_bp.route("/<card_id>/decrease", methods=["PATCH"])
def decrease_likes(card_id):
card = validate_model(Card, card_id)

if card.likes_count > 0:
card.likes_count -= 1

db.session.commit()
return jsonify(card.to_dict())

@cards_bp.route("/<card_id>", methods=["PATCH"])
def update_card_message(card_id):
card = validate_model(Card, card_id)

new_message = request.json.get("message")

validated_new_message = validate_message(new_message)
if validated_new_message is not None:
return validated_new_message

card.message = new_message

db.session.commit()
return jsonify(card.to_dict()), 200



21 changes: 21 additions & 0 deletions app/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from flask import abort, make_response, jsonify

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

def validate_message(message):
if not message:
return make_response(jsonify(errors=[{"field": "cardMessage", "message": "Message is required"}]), 400)

if len(message) > 40:
return make_response(jsonify(errors=[{"field": "cardMessage", "message": "Message should not exceed 40 characters"}]), 400)
20 changes: 20 additions & 0 deletions app/models/board.py
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
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)
cards = db.relationship('Card', back_populates='board', lazy=True)

def to_dict(self):
return {
'board_id' : self.board_id,
'title' : self.title,
'owner' : self.owner
}

@classmethod
def from_dict(cls, data_dict):
return cls(
title = data_dict['title'],
owner = data_dict['owner'],
)
15 changes: 15 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
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)
board_id = db.Column(db.Integer, db.ForeignKey('board.board_id'))
board = db.relationship('Board', back_populates='cards')

def to_dict(self):
return {
'card_id' : self.card_id,
'message' : self.message,
'likes_count' : self.likes_count
}

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

This file was deleted.

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()
Loading