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

Ocelots - Megan M & Xuan Hien Pham #11

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e5e4a0c
Set up envoirnment and DB for first wave
maple-megan333 Jan 3, 2023
15e74ab
Updated to seperate routes
maple-megan333 Jan 4, 2023
8c993d0
First models complete.
maple-megan333 Jan 4, 2023
cac0747
Made dictionary methods for models!
maple-megan333 Jan 4, 2023
3443973
CRUD for video
HienXuanPham Jan 4, 2023
1e54c81
working on customer
maple-megan333 Jan 4, 2023
b80a7c1
Merge branch 'main' of https://github.com/maple-megan333/retro-video-…
maple-megan333 Jan 4, 2023
4ffae47
Post and get complete
maple-megan333 Jan 4, 2023
30850f7
customer deleted completed
maple-megan333 Jan 4, 2023
d08fca4
Finished put method!
maple-megan333 Jan 4, 2023
3622132
Updated database and added Video Model.
maple-megan333 Jan 5, 2023
08ab095
modify video_routes to pass all tests in test_wave_01
HienXuanPham Jan 5, 2023
1d76b94
Tweaked rentals and started rental Routes.
maple-megan333 Jan 5, 2023
6462e46
passed first 3 rental tests!
maple-megan333 Jan 5, 2023
75476aa
bacref > back_populates
maple-megan333 Jan 5, 2023
109796c
videos back_populate upgrade.
maple-megan333 Jan 5, 2023
01a8a85
passed all checkout tests.
maple-megan333 Jan 5, 2023
12840e7
dynamic calculation of available inventory added.
maple-megan333 Jan 5, 2023
879db46
rentals passes all checkin methods!
maple-megan333 Jan 5, 2023
f9d491c
finished rentals by video
maple-megan333 Jan 5, 2023
2b45c78
Wave 2 complete
maple-megan333 Jan 5, 2023
fc894c5
working through wave 3
maple-megan333 Jan 6, 2023
73105b0
trying to do current tests.
maple-megan333 Jan 6, 2023
53fa5b6
continuing ot try and debug.
maple-megan333 Jan 6, 2023
b406f87
cleaned up
maple-megan333 Jan 6, 2023
b11a185
still tryiing to debug videos_routes.
maple-megan333 Jan 6, 2023
c4471a2
morning debugging.
maple-megan333 Jan 6, 2023
894e2f0
passes wave 3 minus history!
maple-megan333 Jan 6, 2023
25e2c5f
created refactor branch and added routes history for customer and video
HienXuanPham Jan 9, 2023
a88658c
gunicorn on my system
maple-megan333 Jan 9, 2023
b05a5bf
finsihed deploying on personal heroku
maple-megan333 Jan 9, 2023
ae1a7e7
finsiehd deploying personal heroku
maple-megan333 Jan 9, 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
1 change: 0 additions & 1 deletion ada-project-docs/wave_03.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ Things to note:
- Customers can be sorted by `name`, `registered_at` and `postal_code`
- Videos can be sorted by `title` and `release_date`
- If the client requests both sorting and pagination, pagination should be relative to the sorted order
- Check out the [paginate method](https://flask-sqlalchemy.palletsprojects.com/en/2.x/api/#flask_sqlalchemy.BaseQuery.paginate)
- The paginate method reteurns a [Pagination object](https://flask-sqlalchemy.palletsprojects.com/en/2.x/api/#flask_sqlalchemy.Pagination)
- To access records from a Pagination object, consider using the `items` attribute
- See [this article from DigitalOcean](https://www.digitalocean.com/community/tutorials/how-to-query-tables-and-paginate-data-in-flask-sqlalchemy) for a more detailed look at paginating data using SQLAlchemy
Expand Down
12 changes: 11 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ def create_app(test_config=None):
db.init_app(app)
migrate.init_app(app, db)

#Register Blueprints Here
#Register Blueprints Here

from app.routes.video_routes import videos_bp
app.register_blueprint(videos_bp)

from app.routes.customer_routes import customers_bp
app.register_blueprint(customers_bp)

from app.routes.rental_routes import rentals_bp
app.register_blueprint(rentals_bp)


return app
29 changes: 28 additions & 1 deletion app/models/customer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
from app import db
import datetime

class Customer(db.Model):
id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String)
postal_code = db.Column(db.String)
phone = db.Column(db.String)
registered_at = db.Column(db.DateTime, default=datetime.datetime.now())
videos_checked_out_count = db.Column(db.Integer, default=0)
videos = db.relationship("Video", secondary="rentals", back_populates="customers")

def to_dict(self):
customer_dict = {}
customer_dict["id"] = self.id
customer_dict["name"] = self.name
customer_dict["registered_at"]: self.registered_at
customer_dict["postal_code"]=self.postal_code
customer_dict["phone"] = self.phone

return customer_dict


@classmethod
def from_dict(cls,customer_data):
new_customer = Customer(
name=customer_data["name"],
postal_code=customer_data["postal_code"],
phone=customer_data["phone"],
)
return new_customer
38 changes: 37 additions & 1 deletion app/models/rental.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
from app import db
import datetime
import enum
from sqlalchemy import Enum


class Rental(db.Model):
id = db.Column(db.Integer, primary_key=True)

class RentalStatus(enum.Enum):
CHECKIN="checked_in"
CHECKOUT="checked_out"


__tablename__ = "rentals"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), primary_key=True,nullable=False)
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), primary_key=True,nullable=False)
status = db.Column(db.Enum(RentalStatus), default=RentalStatus.CHECKOUT)
checkout_date = db.Column(db.DateTime, default=datetime.datetime.now())
due_date = db.Column(db.DateTime, default=(datetime.date.today()+datetime.timedelta(days=7)))

def to_dict(self):
rental_dict = {}
rental_dict["id"] = self.id
rental_dict["customer_id"] = self.customer_id
rental_dict["video_id"] = self.video_id
rental_dict["checkout_date"] = self.checkout_date
rental_dict["status"] = self.status
rental_dict["due_date"] = self.due_date

return rental_dict

@classmethod
def from_dict(cls, rental_data):
new_rental = Rental(
customer_id=rental_data["customer_id"],
video_id=rental_data["video_id"]
)

return new_rental
26 changes: 25 additions & 1 deletion app/models/video.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
from app import db

class Video(db.Model):
id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title=db.Column(db.String)
release_date = db.Column(db.Date)
total_inventory = db.Column(db.Integer)
customers = db.relationship("Customer", secondary="rentals", back_populates="videos")


def to_dict(self):
video_dict = {}
video_dict["id"] = self.id
video_dict["title"] = self.title
video_dict["release_date"] = self.release_date
video_dict["total_inventory"] = self.total_inventory

return video_dict

@classmethod
def from_dict(cls, video_data):
new_video = Video(
title=video_data["title"],
release_date=video_data["release_date"],
total_inventory=video_data["total_inventory"]
)

return new_video
Empty file removed app/routes.py
Empty file.
95 changes: 95 additions & 0 deletions app/routes/customer_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from app import db
from app.models.customer import Customer
from app.models.video import Video
from app.models.rental import Rental
from app.routes.rental_routes import query_rentals
from flask import Blueprint, jsonify, abort, make_response, request
from app.routes.helper_functions import validate_model, custom_query

customers_bp = Blueprint("customers_bp", __name__, url_prefix="/customers")

@customers_bp.route("", methods=["POST"])
def create_customer():
request_body = request.get_json()
try:

new_customer = Customer.from_dict(request_body)
except KeyError as key_error:
abort(make_response({"details":f"Request body must include {key_error.args[0]}."}, 400))

db.session.add(new_customer)
db.session.commit()
return make_response(new_customer.to_dict(), 201)

#queries customers appends dictionaries, jysonifys list.

@customers_bp.route('', methods=["GET"])
def get_all_customer():
customers=custom_query(Customer,['id','name','registered_at','postal_code'])
customer_response = []
for customer in customers:
customer_response.append(customer.to_dict())
return jsonify(customer_response)

#get customer by id
@customers_bp.route("/<customer_id>", methods=["GET"])
def get_one_customer(customer_id):
customer = validate_model(Customer, customer_id)
return customer.to_dict()

#route to delete
@customers_bp.route("/<customer_id>", methods=["DELETE"])
def delete_customer(customer_id):
customer = validate_model(Customer, customer_id)

db.session.delete(customer)
db.session.commit()

return make_response(jsonify(customer.to_dict()), 200)

#update route
@customers_bp.route("/<customer_id>", methods=["PUT"])
def update_customer(customer_id):
customer=validate_model(Customer, customer_id)

request_body = request.get_json()
try:

customer.name=request_body["name"]
customer.phone=request_body["phone"]
customer.postal_code=request_body["postal_code"]
except KeyError as key_error:
abort(make_response({"details":f"Request body must include {key_error.args[0]}."}, 400))

db.session.add(customer)
db.session.commit()

return make_response(customer.to_dict(), 200)

@customers_bp.route("/<customer_id>/rentals", methods=["GET"])
def get_rentals_by_customer(customer_id):
customer = validate_model(Customer, customer_id)
query = custom_query(Rental,['id','title','release_date'],{"customer_id":customer.id, "status": Rental.RentalStatus.CHECKOUT})

response=[]
for rental in query:
video = validate_model(Video, rental.video_id)
rental_info = video.to_dict()
rental_info["due_date"] = rental.due_date
response.append(rental_info)
return jsonify(response)

@customers_bp.route("/<customer_id>/history", methods=["GET"])

Choose a reason for hiding this comment

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

really well done, adding these optional enhancements!

def get_video_had_been_checked_out(customer_id):
customer = validate_model(Customer, customer_id)
videos = custom_query(Rental,['id','title','release_date'],{"customer_id":customer.id, "status": Rental.RentalStatus.CHECKIN})
response = []
rental_info = {}
for rental in videos:
video = validate_model(Video, rental.video_id)
rental_info["title"] = video.title
rental_info["checkout_date"] = rental.checkout_date
rental_info["due_date"] = rental.due_date
response.append(rental_info)
return jsonify(response)

63 changes: 63 additions & 0 deletions app/routes/helper_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from flask import Blueprint, jsonify, abort, make_response, request
from app.models.rental import Rental
from app.models.customer import Customer
from app.models.video import Video

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} was not found"}, 404))

return model

def custom_query(cls, approvedsortinig, filters={}):
#list of accepted sort paramas
valid_sort=(approvedsortinig)
custom_querys=None

#getting sort and pagnation args, with defults and types
sort=request.args.get('sort', 'id')
page = None
count=None

if request.args.get('page_num'):
page=request.args.get('page_num', 1, type=int)
else: page=1

if request.args.get("count"):
count=request.args.get("count", 100, type=int)
else: count=100

#making id if not valid.
if sort not in valid_sort: sort= 'id'
#checking to see if class is the orderby attricute
order_cls=cls

if cls is Rental:
join_class=None
if filters.get("customer_id"):
join_class=Video
else:
join_class=Customer

if not hasattr(cls,sort):
find_att=[Customer,Video,Rental]
for object in find_att:
if hasattr(object,sort):
order_cls=object
break

custom_querys=cls.query.filter_by(**filters).join(join_class).order_by(
getattr(order_cls,sort)).paginate(page=page,per_page=count,error_out=False)
else:
custom_querys=cls.query.order_by(getattr(
order_cls,sort)).paginate(page,count,False)

query=custom_querys.items
return query
92 changes: 92 additions & 0 deletions app/routes/rental_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from app import db
from app.models.rental import Rental
from app.models.video import Video
from app.models.customer import Customer
from flask import Blueprint, jsonify, abort, make_response, request

rentals_bp = Blueprint("rentals_bp", __name__, url_prefix="/rentals")

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} was not found"}, 404))

return model

#helper query function:
def query_rentals(filters):
query_results = db.session.query(Rental).filter_by(**filters).all()
return query_results

#extract results
def extract_query(query_results):
response = []
for object in query_results:
response.append(object.to_dict())
return response

#calculate avail inventory
def availabl_inventory(video):
vids_out = query_rentals({"video_id":video.id, "status":Rental.RentalStatus.CHECKOUT})
return video.total_inventory-len(vids_out)

#response helper function
def rental_response(rental,customer,video):
rental_response={}
rental_response["video_id"]=video.id
rental_response["customer_id"]=customer.id
rental_response["videos_checked_out_count"]=customer.videos_checked_out_count
rental_response["available_inventory"] = availabl_inventory(video) #here we need to subtract all rentals associated with video
return rental_response

#should make a query method that can take in video_id,customer_id and status

@rentals_bp.route("/check-out", methods=["POST"])
def video_checkout():
request_body=request.get_json()
#put into rental model as to_dictionary
try:
customer = validate_model(Customer, request_body["customer_id"])
video = validate_model(Video, request_body["video_id"])
if availabl_inventory(video)==0:
abort(make_response({"message":"Could not perform checkout"}, 400))
new_rental=Rental.from_dict(request_body)
customer.videos_checked_out_count +=1

except KeyError as key_error:
abort(make_response({"details":f"Request body must include {key_error.args[0]}."}, 400))

db.session.add_all([new_rental, customer])

db.session.commit()

return make_response(rental_response(new_rental,customer,video), 200)

@rentals_bp.route("/check-in", methods=["POST"])
def checkin_video():
request_body = request.get_json()

try:
customer = validate_model(Customer, request_body["customer_id"])
video = validate_model(Video, request_body["video_id"])
rental_query = query_rentals({"video_id": video.id, "customer_id":customer.id, "status": Rental.RentalStatus.CHECKOUT})
if not rental_query:
abort(make_response({"message":"No outstanding rentals for customer 1 and video 1"}, 400))
rental=rental_query[0]
rental.status=Rental.RentalStatus.CHECKIN
customer.videos_checked_out_count -=1
except KeyError as key_error:
abort(make_response({"details":f"Request body must include {key_error.args[0]}."}, 400))

db.session.add_all([rental, customer])

db.session.commit()

return make_response(rental_response(rental,customer,video), 200)

Loading