-
Notifications
You must be signed in to change notification settings - Fork 55
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
Sierra and Melinda - Pine #60
base: master
Are you sure you want to change the base?
Changes from all commits
c55c759
ad5962e
1178840
3c33a1c
3e5a09f
8831ee5
0b343ba
aa12ca2
2621848
4ca570c
7d6433f
e036c38
f729e95
0ad1fde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,24 @@ | ||
from app import db | ||
from sqlalchemy.orm import backref | ||
|
||
|
||
class Customer(db.Model): | ||
id = db.Column(db.Integer, primary_key=True) | ||
id = db.Column(db.Integer, autoincrement = True, primary_key=True) | ||
name = db.Column(db.String) | ||
postal_code = db.Column(db.String) | ||
phone = db.Column(db.String) | ||
register_at = db.Column(db.DateTime) | ||
# videos = db.relationship("Video",secondary="customer_videos", backref="customers") | ||
# rentals = db.relationship('Rental', backref="customer_rentals", lazy=True) | ||
|
||
|
||
def to_dict(self): | ||
response={ | ||
"id": self.id, | ||
"name": self.name, | ||
"postal_code": self.postal_code, | ||
"phone": self.phone, | ||
"register_at": self.register_at, | ||
} | ||
return response | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,47 @@ | ||
from app import db | ||
from .video import Video | ||
from .customer import Customer | ||
|
||
|
||
class Rental(db.Model): | ||
id = db.Column(db.Integer, primary_key=True) | ||
__tablename__ = "rentals" | ||
id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
customer_id = db.Column(db.Integer, db.ForeignKey("customer.id")) | ||
video_id = db.Column(db.Integer, db.ForeignKey("video.id")) | ||
due_date = db.Column(db.String) | ||
customer = db.relationship("Customer", backref="rentals") | ||
video = db.relationship("Video", backref="rentals") | ||
checked_out = db.Column(db.Boolean, default=False) | ||
|
||
|
||
def to_dict(self): | ||
# rentals = Rental.query.filter(Rental.video_id == self.video_id) | ||
|
||
return { | ||
"id": self.id, | ||
"customer_id": self.customer_id, | ||
"video_id": self.video_id, | ||
"due_date": self.due_date, | ||
"videos_checked_out_count": len(self.customer.rentals), | ||
"available_inventory": self.video.total_inventory - len(self.video.rentals), | ||
} | ||
|
||
def rental_dict(self, id): | ||
video = Video.query.get(id) | ||
|
||
return { | ||
"release_date": video.release_date, | ||
"title": video.title, | ||
"due_date": self.due_date | ||
} | ||
|
||
def get_rental_by_video(self, id): | ||
customer = Customer.query.get(id) | ||
|
||
return { | ||
"due_date": self.due_date, | ||
"name": customer.name, | ||
"phone": customer.phone, | ||
"postal_code": customer.postal_code | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,20 @@ | ||
from app import db | ||
from datetime import datetime, timedelta | ||
|
||
class Video(db.Model): | ||
id = db.Column(db.Integer, primary_key=True) | ||
id = db.Column(db.Integer, autoincrement = True, primary_key=True) | ||
title = db.Column(db.String) | ||
release_date = db.Column(db.DateTime) | ||
total_inventory = db.Column(db.Integer) | ||
available_inventory = db.Column(db.Integer) | ||
# rentals = db.relationship("Rental", backref = "video", lazy = True) | ||
|
||
def to_dict(self): | ||
response={ | ||
"id": self.id, | ||
"title": self.title, | ||
"release_date": self.release_date, | ||
"total_inventory": self.total_inventory, | ||
} | ||
return response | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,286 @@ | ||||||||||||||||||||
from flask import abort,Blueprint,jsonify,request,make_response | ||||||||||||||||||||
from app.models.customer import Customer | ||||||||||||||||||||
from app.models.video import Video | ||||||||||||||||||||
from app.models.rental import Rental | ||||||||||||||||||||
from app import db | ||||||||||||||||||||
import requests, os | ||||||||||||||||||||
from dotenv import load_dotenv | ||||||||||||||||||||
from datetime import datetime, timezone, timedelta, date | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
customers_bp = Blueprint("customers", __name__, url_prefix="/customers") | ||||||||||||||||||||
videos_bp = Blueprint("videos", __name__, url_prefix="/videos") | ||||||||||||||||||||
rentals_bp = Blueprint("rentals", __name__, url_prefix="/rentals") | ||||||||||||||||||||
load_dotenv() | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@customers_bp.route("", methods=["GET", "POST"]) | ||||||||||||||||||||
def handle_customers(): | ||||||||||||||||||||
|
||||||||||||||||||||
if request.method == "GET": | ||||||||||||||||||||
customers = Customer.query.all() | ||||||||||||||||||||
|
||||||||||||||||||||
customers_response = [] | ||||||||||||||||||||
|
||||||||||||||||||||
for customer in customers: | ||||||||||||||||||||
customers_response.append(customer.to_dict()) | ||||||||||||||||||||
Comment on lines
+23
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works well and is very readable, but if you'd like to start incorporating list comprehensions into your code, a loop like this that builds a list is a great place to start:
Suggested change
|
||||||||||||||||||||
return jsonify(customers_response), 200 | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
elif request.method == "POST": | ||||||||||||||||||||
|
||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
|
||||||||||||||||||||
if "name" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include name."}, 400) | ||||||||||||||||||||
|
||||||||||||||||||||
elif "postal_code" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include postal_code."}, 400) | ||||||||||||||||||||
|
||||||||||||||||||||
elif "phone" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include phone."}, 400) | ||||||||||||||||||||
else: | ||||||||||||||||||||
new_customer = Customer(name=request_body["name"], phone=request_body["phone"], postal_code=request_body["postal_code"]) | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.add(new_customer) | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
return make_response({"id": new_customer.id}, 201) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@customers_bp.route("/<customer_id>", methods=["GET", "DELETE", "PUT"]) | ||||||||||||||||||||
def handle_customer(customer_id): | ||||||||||||||||||||
if customer_id.isnumeric() != True: | ||||||||||||||||||||
return {"details" : "Invalid request"}, 400 | ||||||||||||||||||||
customer = Customer.query.get(customer_id) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
if request.method == "GET": | ||||||||||||||||||||
if customer is None: | ||||||||||||||||||||
return make_response({"message": f"Customer {customer_id} was not found"}, 404) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
return make_response(customer.to_dict(), 200) | ||||||||||||||||||||
|
||||||||||||||||||||
elif request.method == "DELETE": | ||||||||||||||||||||
if customer is None: | ||||||||||||||||||||
return make_response({"message": f"Customer {customer_id} was not found"}, 404) | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.delete(customer) | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
|
||||||||||||||||||||
return make_response({"id": int(customer_id)}, 200) | ||||||||||||||||||||
|
||||||||||||||||||||
if request.method == "PUT": | ||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
if customer is None: | ||||||||||||||||||||
return make_response({"message": f"Customer {customer_id} was not found"}, 404) | ||||||||||||||||||||
elif "name" not in request_body or "postal_code" not in request_body or "phone" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Invalid data"}, 400) | ||||||||||||||||||||
Comment on lines
+78
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section is very similar to the validation in the POST route in the handle_customers() function. I recommend thinking about ways to pull these sections out into helper functions to DRY the code. |
||||||||||||||||||||
else: | ||||||||||||||||||||
response_body = request.get_json() | ||||||||||||||||||||
Comment on lines
+76
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great error checking! |
||||||||||||||||||||
|
||||||||||||||||||||
customer.name = response_body["name"] | ||||||||||||||||||||
customer.phone = response_body["phone"] | ||||||||||||||||||||
customer.postal_code = response_body["postal_code"] | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
return make_response(customer.to_dict(), 200) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@videos_bp.route("", methods=["GET", "POST"]) | ||||||||||||||||||||
def handle_videos(): | ||||||||||||||||||||
|
||||||||||||||||||||
# Wave 1 GET/videos and no saved videos | ||||||||||||||||||||
if request.method == "GET": | ||||||||||||||||||||
videos = Video.query.all() | ||||||||||||||||||||
|
||||||||||||||||||||
videos_response = [] | ||||||||||||||||||||
|
||||||||||||||||||||
for video in videos: | ||||||||||||||||||||
videos_response.append(video.to_dict()) | ||||||||||||||||||||
return jsonify(videos_response), 200 | ||||||||||||||||||||
|
||||||||||||||||||||
# Wave 1 POST/videos and video must contain title, video must contain release_date, and video must contain total_inventory | ||||||||||||||||||||
elif request.method == "POST": | ||||||||||||||||||||
|
||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
|
||||||||||||||||||||
if "title" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include title."}, 400) | ||||||||||||||||||||
|
||||||||||||||||||||
elif "release_date" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include release_date."}, 400) | ||||||||||||||||||||
|
||||||||||||||||||||
elif "total_inventory" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Request body must include total_inventory."}, 400) | ||||||||||||||||||||
else: | ||||||||||||||||||||
new_video = Video(title=request_body["title"], release_date=request_body["release_date"], total_inventory=request_body["total_inventory"]) | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.add(new_video) | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
return make_response({"id": new_video.id, "title": new_video.title, "total_inventory": new_video.total_inventory}, 201) | ||||||||||||||||||||
|
||||||||||||||||||||
# Wave 1 GET/DELETE/PUT | ||||||||||||||||||||
@videos_bp.route("", methods=["GET"]) | ||||||||||||||||||||
def get_all_videos(): | ||||||||||||||||||||
videos = Video.query.all() | ||||||||||||||||||||
response_body = [video.to_dict() for video in videos] | ||||||||||||||||||||
return jsonify(response_body), 200 | ||||||||||||||||||||
Comment on lines
+126
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't getting called. This is the same route as line 92 and I suspect what is happening here is that the first route is the one that is called and this one is ignored. |
||||||||||||||||||||
|
||||||||||||||||||||
@videos_bp.route("/<video_id>", methods=["GET", "DELETE", "PUT"]) | ||||||||||||||||||||
def handle_video(video_id): | ||||||||||||||||||||
if video_id.isnumeric() != True: | ||||||||||||||||||||
return {"details" : "Invalid request"}, 400 | ||||||||||||||||||||
video = Video.query.get(video_id) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
if request.method == "GET": | ||||||||||||||||||||
if video is None: | ||||||||||||||||||||
return make_response({"message": f"Video {video_id} was not found"}, 404) | ||||||||||||||||||||
Comment on lines
+140
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block is the same for all methods, I recommend bumping this up out of the if/elif structure to right after line 136 to DRY the code slightly. |
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
return make_response(video.to_dict(), 200) | ||||||||||||||||||||
|
||||||||||||||||||||
elif request.method == "DELETE": | ||||||||||||||||||||
if video is None: | ||||||||||||||||||||
return make_response({"message": f"Video {video_id} was not found"}, 404) | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.delete(video) | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
|
||||||||||||||||||||
return make_response({"id": int(video_id)}, 200) | ||||||||||||||||||||
|
||||||||||||||||||||
if request.method == "PUT": | ||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
if video is None: | ||||||||||||||||||||
return make_response({"message": f"Video {video_id} was not found"}, 404) | ||||||||||||||||||||
elif "title" not in request_body or "release_date" not in request_body or "total_inventory" not in request_body: | ||||||||||||||||||||
return make_response({"details": "Invalid data"}, 400) | ||||||||||||||||||||
else: | ||||||||||||||||||||
response_body = request.get_json() | ||||||||||||||||||||
|
||||||||||||||||||||
video.title = response_body["title"] | ||||||||||||||||||||
video.release_date = response_body["release_date"] | ||||||||||||||||||||
video.total_inventory = response_body["total_inventory"] | ||||||||||||||||||||
|
||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
return make_response(video.to_dict(), 200) | ||||||||||||||||||||
|
||||||||||||||||||||
# WAVE 2 | ||||||||||||||||||||
|
||||||||||||||||||||
@rentals_bp.route("/check-out", methods=["POST"]) | ||||||||||||||||||||
def create_rental(): | ||||||||||||||||||||
|
||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
|
||||||||||||||||||||
if "customer_id" not in request_body or "video_id" not in request_body: | ||||||||||||||||||||
abort(400, "Request body must include a customer_id and video_id") | ||||||||||||||||||||
|
||||||||||||||||||||
try: | ||||||||||||||||||||
customer_id = int(request_body["customer_id"]) | ||||||||||||||||||||
video_id = int(request_body["video_id"]) | ||||||||||||||||||||
except ValueError: | ||||||||||||||||||||
return jsonify({"Error": "Customer ID and Video ID must be integers."}), 400 | ||||||||||||||||||||
|
||||||||||||||||||||
customer = Customer.query.get(customer_id) | ||||||||||||||||||||
video = Video.query.get(video_id) | ||||||||||||||||||||
|
||||||||||||||||||||
if customer is None: | ||||||||||||||||||||
return jsonify({"message": f"Customer {customer_id} was not found"}), 404 | ||||||||||||||||||||
elif video is None: | ||||||||||||||||||||
return jsonify({"message": f"Video {video_id} was not found"}), 404 | ||||||||||||||||||||
|
||||||||||||||||||||
if video.total_inventory == 0: | ||||||||||||||||||||
return jsonify({"message": f"Video {video_id} is out of stock"}), 404 | ||||||||||||||||||||
|
||||||||||||||||||||
if video.total_inventory - len(video.rentals) == 0: | ||||||||||||||||||||
abort(make_response({"message": "Could not perform checkout"}, 400)) | ||||||||||||||||||||
Comment on lines
+198
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a small logical bug here. The list of rentals associated with a video will include all of the rentals where that have a foreign key
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
new_rental = Rental( | ||||||||||||||||||||
customer_id=request_body["customer_id"], | ||||||||||||||||||||
video_id=request_body["video_id"] | ||||||||||||||||||||
) | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
db.session.add(new_rental) | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
return jsonify(new_rental.to_dict()), 200 | ||||||||||||||||||||
|
||||||||||||||||||||
@rentals_bp.route("/check-in", methods=["POST"]) | ||||||||||||||||||||
def handle_rentals(): | ||||||||||||||||||||
request_body = request.get_json() | ||||||||||||||||||||
|
||||||||||||||||||||
if "customer_id" not in request_body or "video_id" not in request_body: | ||||||||||||||||||||
return jsonify({"details": "Request body must include customer_id and video_id."}), 400 | ||||||||||||||||||||
|
||||||||||||||||||||
try: | ||||||||||||||||||||
customer_id = int(request_body["customer_id"]) | ||||||||||||||||||||
video_id = int(request_body["video_id"]) | ||||||||||||||||||||
except ValueError: | ||||||||||||||||||||
return jsonify({"Error": "Customer ID and Video ID must be integers."}), 400 | ||||||||||||||||||||
|
||||||||||||||||||||
customer = Customer.query.get(customer_id) | ||||||||||||||||||||
video = Video.query.get(video_id) | ||||||||||||||||||||
|
||||||||||||||||||||
if customer is None: | ||||||||||||||||||||
return jsonify({"message": f"Customer {customer_id} was not found"}), 404 | ||||||||||||||||||||
elif video is None: | ||||||||||||||||||||
return jsonify({"message": f"Video {video_id} was not found"}), 404 | ||||||||||||||||||||
|
||||||||||||||||||||
if video.total_inventory == 0: | ||||||||||||||||||||
return jsonify({"message": f"Video {video_id} is out of stock"}), 404 | ||||||||||||||||||||
|
||||||||||||||||||||
rental = Rental.query.filter_by(customer_id=customer.id, video_id=video.id).first() | ||||||||||||||||||||
if rental is None: | ||||||||||||||||||||
return jsonify(message=f"No outstanding rentals for customer {customer.id} and video {video.id}"), 400 | ||||||||||||||||||||
|
||||||||||||||||||||
rental.checked_out = False | ||||||||||||||||||||
db.session.commit() | ||||||||||||||||||||
|
||||||||||||||||||||
num_currently_checked_out = Rental.query.filter_by(video_id=video.id, checked_out=True).count() | ||||||||||||||||||||
available_inventory = video.total_inventory - num_currently_checked_out | ||||||||||||||||||||
videos_customer_checked_out = Rental.query.filter_by(customer_id=customer.id, checked_out=True).count() | ||||||||||||||||||||
Comment on lines
+244
to
+246
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 The query on line 244 could also be used to fix the logical bug in the check out method. |
||||||||||||||||||||
|
||||||||||||||||||||
return jsonify({ | ||||||||||||||||||||
"customer_id": customer.id, | ||||||||||||||||||||
"video_id": video.id, | ||||||||||||||||||||
"videos_checked_out_count": videos_customer_checked_out, | ||||||||||||||||||||
"available_inventory": available_inventory, | ||||||||||||||||||||
}), 200 | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@customers_bp.route("/<customer_id>/rentals") | ||||||||||||||||||||
def read_customer_rentals(customer_id): | ||||||||||||||||||||
customer = Customer.query.get(customer_id) | ||||||||||||||||||||
customer_rentals = Rental.query.filter(Rental.customer_id == customer_id).all() | ||||||||||||||||||||
|
||||||||||||||||||||
response = [] | ||||||||||||||||||||
|
||||||||||||||||||||
if not customer: | ||||||||||||||||||||
return {"message": f"Customer {customer_id} was not found"}, 404 | ||||||||||||||||||||
|
||||||||||||||||||||
for item in customer_rentals: | ||||||||||||||||||||
response.append(item.rental_dict(item.video_id)) | ||||||||||||||||||||
|
||||||||||||||||||||
return jsonify(response), 200 | ||||||||||||||||||||
|
||||||||||||||||||||
@videos_bp.route("/<video_id>/rentals") | ||||||||||||||||||||
def read_rentals_by_video(video_id): | ||||||||||||||||||||
video = Video.query.get(video_id) | ||||||||||||||||||||
video_rentals = Rental.query.filter(Rental.video_id == video_id).all() | ||||||||||||||||||||
|
||||||||||||||||||||
rentals_response = [] | ||||||||||||||||||||
|
||||||||||||||||||||
if not video: | ||||||||||||||||||||
return {"message": f"Video {video_id} was not found"}, 404 | ||||||||||||||||||||
|
||||||||||||||||||||
for item in video_rentals: | ||||||||||||||||||||
id = item.customer_id | ||||||||||||||||||||
rentals_response.append(item.get_rental_by_video(id)) | ||||||||||||||||||||
|
||||||||||||||||||||
return jsonify(rentals_response), 200 | ||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The db.relationships is already doing this work for you. It looks like the relationships are set up correctly so self.video should be a Video model where the Video.id == self.video_id.