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

SL-07 BE #25

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2a41cf6
backend setup
biancadlc Jan 3, 2023
8b67963
Adds the from_dict_to_object and to_dict functions to Card model and …
aliaathman Jan 3, 2023
2169b3b
created class method and to dict function for Board object, and finis…
biancadlc Jan 3, 2023
5ff4a3f
Adds the read_all_boards route to board_routes
aliaathman Jan 3, 2023
291fa5a
added read all card in the board routes
biancadlc Jan 3, 2023
cdf40a6
Adds add_card_to_board route, still testing
aliaathman Jan 3, 2023
cf1bad3
creates the add_card_to_board function in board_routes to link the ca…
aliaathman Jan 4, 2023
a24667c
Adds the delete_card and delete_board functions to card_routes and bo…
aliaathman Jan 4, 2023
2ba4f99
Adds the read_specific_board route to board_routes, working succesful…
aliaathman Jan 5, 2023
da4217b
added character limit to our message post requests
biancadlc Jan 5, 2023
3feb8f6
created a one_card fixture
biancadlc Jan 5, 2023
11cd9a7
Adds the test routes and test fixtures for necessary card and board r…
aliaathman Jan 5, 2023
098ee89
modified add_card_to_board to post and link a card under one post req…
biancadlc Jan 16, 2023
e718e3d
modified models to delete on cascade
biancadlc Jan 17, 2023
4c1491a
deleted commented code and moved delete card to card routes
biancadlc Jan 17, 2023
38da985
Clair's suggestions after we spoke about ssome logic that could be ho…
JaimeMitchell Jan 18, 2023
7d4ab43
Adds the like_card_with_id function to the PATCH route for liking cards
aliaathman Jan 18, 2023
4608857
added error message for when either owner or title is left blank
biancadlc Jan 18, 2023
d2611be
Refactored our tests to pass with our new changes in routes
aliaathman Jan 19, 2023
b278200
adding card_as_dict boardId
JaimeMitchell Jan 21, 2023
8f4bccd
Merge branch 'main' of https://github.com/arettab/back-end-inspiratio…
JaimeMitchell Jan 21, 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
26 changes: 22 additions & 4 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,40 @@
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(

if test_config is None:
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
else:
# used for running testing routes
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.card import Card
from app.models.board import Board



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

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

from .board_routes import board_bp
app.register_blueprint(board_bp)


app.config['CORS_HEADERS'] = 'Content-Type'
CORS(app)
return app
78 changes: 78 additions & 0 deletions app/board_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from flask import Blueprint, request, jsonify, make_response
from .card_routes import validate_model
from app.models.card import Card
from app.models.board import Board
from app import db

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


@board_bp.route("", methods=["POST"])
def create_board():
request_body = request.get_json()
title = request_body['title']
owner = request_body['owner']
if len(request_body) != 2:
return {"details": "Invalid Data"}, 400

if not owner or not title:
return {"details": "Title and/or Owner was left blank"}
new_board = Board.from_dict_to_object(request_body)

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

return make_response(jsonify({"board": new_board.to_dict()}), 201)


@board_bp.route("/<id>", methods=["GET"])
def read_specific_board(id):
board = validate_model(Board, id)
response_body = board.to_dict()
return make_response(jsonify(response_body), 200)


@board_bp.route("", methods=["GET"])
def read_all_boards():
boards = Board.query.all()
board_response = [board.to_dict() for board in boards]

return make_response(jsonify(board_response), 200)


@board_bp.route("/<board_id>/cards", methods=["GET"])
def read_all_cards(board_id):
board = validate_model(Board, board_id)
boards_response = [card.to_dict() for card in board.cards]
return(jsonify(boards_response))


@board_bp.route("/<board_id>/cards", methods=["POST"])
def add_card_to_board(board_id):
request_body = request.get_json()
message = request_body["message"]
if not message:
return {"details": "Invalid Data"}, 400
if len(message) > 40:
return {"details": "You have gone over the 40 character message limit."}
new_card = Card.from_dict_to_object(request_body)

db.session.add(new_card)


board = validate_model(Board, board_id)
board.cards.append(new_card)

db.session.commit()

return make_response(jsonify({'board_id': board.board_id, 'cards': [card.to_dict() for card in board.cards]}), 200)


@board_bp.route("/<id>", methods=["DELETE"])
def delete_board(id):
board = validate_model(Board, id)

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

return make_response(jsonify({"details": f"Board {id} '{board.title}' successfully deleted"}), 200)
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.models.card import Card
from app import db

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


def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"details": "Invalid Data"}, 400))
model = cls.query.get(model_id)

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


@cards_bp.route("", methods=["POST"])
def create_card():
request_body = request.get_json()
message = request_body["message"]
if not message:
return {"details": "Invalid Data"}, 400
if len(message) > 40:
return {"details": "You have gone over the 40 character message limit."}
new_card = Card.from_dict_to_object(request_body)

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

return make_response(jsonify({"card": new_card.to_dict()}), 201)


@cards_bp.route("/<id>", methods=["DELETE"])
def delete_card(id):

card = validate_model(Card, id)

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

return make_response(jsonify({"details": f"Card {id} '{card.message}' successfully deleted"}), 200)

@cards_bp.route("/<id>", methods=["PATCH"])
def like_card_with_id(id):
card = validate_model(Card, id)
card.likes_count += 1

db.session.commit()
return jsonify(card.to_dict())
3 changes: 3 additions & 0 deletions app/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@



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

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", lazy=True, passive_deletes=True)


def to_dict(self, cards=False):
if not self.cards and cards==False:
return {
'board_id': self.board_id,
'title': self.title,
'owner': self.owner
}
else:
return {
'board_id': self.board_id,
'title': self.title,
'cards': [card.to_dict() for card in self.cards],
'owner': self.owner
}
@classmethod
def from_dict_to_object(cls,data_dict):
return cls(title=data_dict["title"], owner=data_dict['owner'])
25 changes: 25 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
from app import db

class Card(db.Model):
card_id = db.Column(db.Integer, primary_key=True)
message = db.Column(db.String)
board = db.relationship("Board", back_populates="cards", lazy=True)
# an object from the card class can accept a relationship from an object
# that's from the Board class. backpopulates signals that the specific board
# object will now display this specific card in its board.cards attribute
board_id = db.Column(db.Integer, db.ForeignKey('board.board_id', ondelete='CASCADE'), nullable=True)
likes_count = db.Column(db.Integer, default=0)

def to_dict(self):
card_as_dict = {}
card_as_dict["card_id"] = self.card_id
card_as_dict["message"] = self.message
card_as_dict["likes_count"] = self.likes_count
card_as_dict["board_id"] = self.board_id
return card_as_dict
@classmethod
def from_dict_to_object(cls,data_dict):
return cls(message=data_dict["message"])




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