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

Sea Turtles - Adriana Gutierrez, Esther Annorzie, Joanna Dudley, & Lili Parra #25

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
227c09d
Added Board and Card models, updated __init__ to reflect changes
jfdudley Jun 27, 2022
340954b
Added helper_functions.py and instance/class methods from task list A…
jfdudley Jun 28, 2022
39b629a
Created relationship between board and card models
jfdudley Jun 28, 2022
d796793
Created routes folder, created and registered blueprints
jfdudley Jun 28, 2022
57318de
Completed reading all boards and one board routes
estherannorzie Jun 28, 2022
c8ec7a6
Fixed helper_functions import
lili4x4 Jun 28, 2022
43624ca
untested create_new_board route created
adrianajg Jun 28, 2022
0d4fa40
Updated board and card models with instance methods
jfdudley Jun 28, 2022
712766f
Pulled read routes for boards
adrianajg Jun 28, 2022
909a41b
Merge branch 'main' of https://github.com/jfdudley/back-end-inspirati…
adrianajg Jun 28, 2022
db72352
Updated get_one_board route so paramaters in endpoint and function match
jfdudley Jun 28, 2022
aaac7e8
created board GET endpoint to read all cards by board id
adrianajg Jun 28, 2022
f8ba08a
Merge branch 'main' of https://github.com/jfdudley/back-end-inspirati…
adrianajg Jun 28, 2022
a13effd
Created endpoint to DELETE card
lili4x4 Jun 28, 2022
a019a27
Merge branch 'main' of https://github.com/jfdudley/back-end-inspirati…
adrianajg Jun 28, 2022
62e8195
added update card endroute, updated helper functions file
jfdudley Jun 29, 2022
411c710
Fixed merge conflict in card_routes
jfdudley Jun 29, 2022
a81af8a
Began post route for creating a card
estherannorzie Jun 29, 2022
692b882
Started post route for card
estherannorzie Jun 29, 2022
73dfd45
Finishedcards post route and tested read route
estherannorzie Jun 29, 2022
4f2b365
Fixed migrations issue
estherannorzie Jun 29, 2022
a1bbbd7
removed migrations folder
jfdudley Jun 29, 2022
8cdfa20
Revert "removed migrations folder"
jfdudley Jun 29, 2022
6fdaa86
Added delete board route
jfdudley Jun 30, 2022
d4ba54f
fixed spelling of delete
jfdudley Jun 30, 2022
4e15002
Fixed delete board by id route, renamed delete one card route to refl…
jfdudley Jun 30, 2022
51e5d6d
Set up testing suite and added test to get one saved board with no cards
lili4x4 Jun 30, 2022
17bdb0a
Added test for creating a card, fixed HTTP response for creating a ca…
lili4x4 Jun 30, 2022
24be81b
Added a few more tests
lili4x4 Jun 30, 2022
a909b89
added test_post_one_card_to_board test
adrianajg Jul 1, 2022
5083264
undid doing exactly what Lili had already figured out how to do...who…
adrianajg Jul 1, 2022
b14421a
Realized Board model self_to_dict function was returning id instead o…
jfdudley Jul 1, 2022
a8d2624
added get one board with cards test
adrianajg Jul 1, 2022
1057184
Merge branch 'main' of https://github.com/jfdudley/back-end-inspirati…
adrianajg Jul 1, 2022
0a55ffb
removed unecessary fixture
adrianajg Jul 1, 2022
8e8b731
Added test for getting multiple boards with no cards
adrianajg Jul 1, 2022
9ab3802
expanded tests for getting two boards with no cards
adrianajg Jul 1, 2022
e23c31a
fixed test for get one board with cards
adrianajg Jul 1, 2022
831711d
test for geting cards from board with no cards returns empty list
adrianajg Jul 1, 2022
3b5a753
renamed test for clarity
adrianajg Jul 1, 2022
c7b1660
added test to get cards for a board with two cards
adrianajg Jul 1, 2022
f0ba784
Added several tests. Delete card test in progress.
adrianajg Jul 1, 2022
9750006
Small change to test_routes.py
adrianajg Jul 1, 2022
f8e229b
Added a test fixture for one board with two cards; added delete card …
lili4x4 Jul 1, 2022
db90d10
updated board model so card lists will be sorted based on card ids
jfdudley Jul 18, 2022
812b805
Updated create card route to return full board dict
jfdudley Jul 18, 2022
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
20 changes: 12 additions & 8 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,26 @@
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")
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 .routes.board_routes import board_bp
app.register_blueprint(board_bp)
from .routes.card_routes import card_bp
app.register_blueprint(card_bp)

CORS(app)
return app
47 changes: 47 additions & 0 deletions app/helper_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

from flask import jsonify, abort, make_response
import os
import requests


def error_message(message, status_code):
abort(make_response(jsonify(dict(details=message)), status_code))

def success_message_info_as_list(message, status_code=200):
return make_response(jsonify(message), status_code)

def return_database_info_list(return_value):
return make_response(jsonify(return_value))

def return_database_info_dict(category, return_value):
return_dict = {}
return_dict[category] = return_value
return make_response(jsonify(return_dict))

def get_record_by_id(cls, id):
try:
id = int(id)
except ValueError:
error_message(f"Invalid id: {id}", 400)
record = cls.query.get(id)
if record:
return record
else:
error_message(f"{cls.return_class_name()} id: {id} not found", 404)

def create_record_safely(cls, data_dict):
try:
return cls.create_from_dict(data_dict)
except ValueError as err:
error_message(f"Invalid key(s): {err}. {cls.return_class_name()} not added to {cls.return_class_name()} List.", 400)

def update_record_safely(cls, record, data_dict):
try:
record.update_self(data_dict)
except ValueError as err:
error_message(f"Invalid key(s): {err}. {cls.return_class_name()} not updated.", 400)





Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love all the helper functions!

57 changes: 57 additions & 0 deletions app/models/board.py
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
from app import db

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


required_attributes = {
"title" : True,
"owner" : True
}

# Instance Methods

def self_to_dict(self):
instance_dict = dict(
board_id=self.board_id,
title=self.title,
owner=self.owner
)

card_list = [card.self_to_dict() for card in self.cards] if self.cards else []
# sort card list by card_ids to prevent cards shifting when like numbers change
card_list.sort(key= lambda x: x["card_id"])
instance_dict["cards"] = card_list

return instance_dict


def update_self(self, data_dict):
dict_key_errors = []
for key in data_dict.keys():
if hasattr(self, key):
setattr(self, key, data_dict[key])
else:
dict_key_errors.append(key)
if dict_key_errors:
raise ValueError(dict_key_errors)



# Class Methods

@classmethod
def create_from_dict(cls, data_dict):

if data_dict.keys() == cls.required_attributes.keys():
return cls(title=data_dict["title"], owner=data_dict["owner"])
else:
remaining_keys= set(data_dict.keys())-set("title", "owner")
response=list(remaining_keys)
raise ValueError(response)
Comment on lines +49 to +54

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on your project, if a user sends an extra key along with the required data for an operation, you may want to still create an object with that valid info and maybe share a message that non-required keys were not be used. There's no right or wrong in this case, just something to think about when designing endpoints.


@classmethod
def return_class_name(cls):
return cls.__name__
56 changes: 56 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
from app import db

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


required_attributes = {
"message" : True,
"board_id" : True,
}

# Instance Methods
def self_to_dict(self):
instance_dict = dict(
card_id=self.card_id,
message=self.message,
board_id=self.board_id,
likes_count=self.likes_count
)

return instance_dict


def update_self(self, data_dict):
dict_key_errors = []
for key in data_dict.keys():
if hasattr(self, key):
setattr(self, key, data_dict[key])
else:
dict_key_errors.append(key)
if dict_key_errors:
raise ValueError(dict_key_errors)
Comment on lines +28 to +36

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could cut down on duplication of the identical update_self instance functions each of the classes implements. Looking at where self is used, I believe this could be moved to the helper functions file shared amongst the classes if the first parameter was the instance variable to be updated rather than self.



# Class Methods


@classmethod
def create_from_dict(cls, data_dict):
if data_dict.keys() == cls.required_attributes.keys():
return cls(message=data_dict["message"],
board_id = data_dict["board_id"],
likes_count = 0
)

else:
remaining_keys= set(data_dict.keys())-set(cls.required_attributes.keys())
response=list(remaining_keys)
raise ValueError(response)

@classmethod
def return_class_name(cls):
return cls.__name__
4 changes: 0 additions & 4 deletions app/routes.py

This file was deleted.

67 changes: 67 additions & 0 deletions app/routes/board_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from flask import Blueprint, request, jsonify, make_response
from app import db
from app.models.board import Board
from app.helper_functions import success_message_info_as_list, get_record_by_id, return_database_info_dict, error_message, create_record_safely
from app.models.card import Card

board_bp = Blueprint('Boards', __name__, url_prefix='/boards')

# create one board
@board_bp.route("", methods=["POST"])
def create_new_board():
request_body = request.get_json()
new_board = create_record_safely(Board, request_body)

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

return success_message_info_as_list(dict(board=new_board.self_to_dict()), 201)

# read all boards
@board_bp.route("", methods=["GET"])
def get_boards():
boards = Board.query.all()
boards_response = [board.self_to_dict() for board in boards]
return success_message_info_as_list(boards_response, status_code=200)

# reading one board
@board_bp.route("/<board_id>", methods=["GET"])
def get_one_board(board_id):
board = get_record_by_id(Board, board_id)
return return_database_info_dict("board", board.self_to_dict())

# read all cards by board id
@board_bp.route("/<board_id>/cards", methods=["GET"])
def get_cards_by_board_id(board_id):
board = get_record_by_id(Board, board_id)

return success_message_info_as_list(board.self_to_dict())

# creating one card
@board_bp.route("/<board_id>/cards", methods=["POST"])
def create_card(board_id):
request_body = request.get_json()
if "message" not in request_body:
error_message("Message not found", 400)

request_body["board_id"] = board_id
card = Card.create_from_dict(request_body)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also use the helper create_record_safely here?

board = get_record_by_id(Board, board_id)

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

return success_message_info_as_list(dict(board=board.self_to_dict()), 201)

# Delete one board
@board_bp.route("/<board_id>", methods=["DELETE"])
def delete_one_board(board_id):
board = get_record_by_id(Board, board_id)

for card in board.cards:
db.session.delete(card)

db.session.delete(board)
db.session.commit()

return success_message_info_as_list(dict(details=f'Board {board.board_id} "{board.title}" successfully deleted'))
29 changes: 29 additions & 0 deletions app/routes/card_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from flask import Blueprint, request, jsonify, make_response
from app import db
from app.models.card import Card
from app.helper_functions import *

card_bp = Blueprint('Cards', __name__, url_prefix='/cards')


@card_bp.route("/<card_id>", methods=["PATCH"])
def update_card(card_id):
card = get_record_by_id(Card, card_id)

request_body = request.get_json()

update_record_safely(Card, card, request_body)

db.session.commit()

return return_database_info_dict("card", card.self_to_dict())

#deleting one card
@card_bp.route("/<card_id>", methods=["DELETE"])
def delete_one_card(card_id):
card = get_record_by_id(Card, card_id)
db.session.delete(card)
db.session.commit()

return success_message_info_as_list(dict(details=f'Card {card.card_id} \"{card.message}\" successfully 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
Loading