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 Anna Du #20

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
a7f93f7
create customer model
anjing0921 Jan 8, 2023
970b865
create video model
anjing0921 Jan 8, 2023
f3c0a44
create video to dict
anjing0921 Jan 8, 2023
92ba167
create video from_dict
anjing0921 Jan 8, 2023
21c1412
create routes folders
anjing0921 Jan 8, 2023
6cbed0b
create customer_bp
anjing0921 Jan 8, 2023
5ef2b92
create videos_bp
anjing0921 Jan 8, 2023
de7c76b
register blueprint
anjing0921 Jan 8, 2023
fe44a9c
add validate_model
anjing0921 Jan 8, 2023
2675462
add model to db
anjing0921 Jan 8, 2023
3ac152d
delete registered_at from_dict because generate auto
anjing0921 Jan 8, 2023
3d2bc1d
add create customer test pass
anjing0921 Jan 8, 2023
e218a73
add get customers route test pass
anjing0921 Jan 8, 2023
2b86314
add get customer by id
anjing0921 Jan 8, 2023
eafc8c2
add delete custom by id
anjing0921 Jan 8, 2023
f3edbcf
add put customer by id
anjing0921 Jan 8, 2023
ef22036
modify
anjing0921 Jan 8, 2023
1c45fe8
video_dict["title"] = self.title
anjing0921 Jan 8, 2023
b46fe90
add video_dict["id"] = self.id
anjing0921 Jan 8, 2023
27e7097
modify
anjing0921 Jan 8, 2023
f76d903
add get videos,post videos, get video by id
anjing0921 Jan 8, 2023
503c5cb
add delete video by id test pass
anjing0921 Jan 8, 2023
4d3a93e
add put video by id wave 1 done
anjing0921 Jan 8, 2023
1d596ad
register rentals_bp
anjing0921 Jan 8, 2023
be161ba
setup rental model
anjing0921 Jan 8, 2023
9809f3e
relationship with rental
anjing0921 Jan 8, 2023
f06397c
rental model
anjing0921 Jan 8, 2023
23c9a3c
relationship with rental
anjing0921 Jan 8, 2023
ec8615b
add videos_checked_out_count
anjing0921 Jan 8, 2023
77a328c
modify
anjing0921 Jan 8, 2023
5cdaa45
add post rental check-out test pass
anjing0921 Jan 8, 2023
352ab04
import rental model
anjing0921 Jan 8, 2023
b0b45a8
add GET /videos/<id>/rentals test pass
anjing0921 Jan 9, 2023
20dae48
GET /videos/<id>/rentals` test done
anjing0921 Jan 9, 2023
a4f462d
wave 2 not finish
anjing0921 Jan 9, 2023
fbdfff3
get sort args
anjing0921 Jan 9, 2023
f275f01
add sort
anjing0921 Jan 9, 2023
9596ea2
modify model
anjing0921 Jan 9, 2023
60881e0
finish customer
anjing0921 Jan 9, 2023
b420483
wave 3 done
anjing0921 Jan 9, 2023
ad7cecb
add rental status
anjing0921 Jan 10, 2023
6a75a34
add validate for check out missing deta
anjing0921 Jan 10, 2023
f7ad568
add rental status
anjing0921 Jan 10, 2023
29c2641
change foreignkey to nullable true
anjing0921 Jan 10, 2023
4d247cf
modify
anjing0921 Jan 10, 2023
3815b0d
add gunicorn
anjing0921 Jan 10, 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
9 changes: 9 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,14 @@ def create_app(test_config=None):
migrate.init_app(app, db)

#Register Blueprints Here
from app.routes.customer_route import customers_bp
app.register_blueprint(customers_bp)

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

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


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

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,nullable = False)
registered_at = db.Column(db.DateTime,default=(datetime.date.today()))
postal_code = db.Column(db.String)
phone = db.Column(db.String,nullable = False)
videos_checked_out_count = db.Column(db.Integer, default=0)
rentals = db.relationship("Rental", back_populates="customer")


def to_dict(self):

Choose a reason for hiding this comment

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

looks good

customer_dict = {}
customer_dict["id"] = self.id
customer_dict["name"] = self.name
customer_dict["registered_at"] = self.registered_at
customer_dict["phone"] = self.phone
customer_dict["postal_code"] = self.postal_code
return customer_dict

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

class Rental(db.Model):
id = db.Column(db.Integer, primary_key=True)
id = db.Column(db.Integer, primary_key=True)
customer_id = db.Column(db.Integer, db.ForeignKey("customer.id"),nullable = True)
video_id = db.Column(db.Integer, db.ForeignKey("video.id"),nullable = True)
due_date = db.Column(db.DateTime, default=(datetime.date.today()+datetime.timedelta(days=7)))
check_out_status = db.Column(db.Boolean,default = True)
available_inventory = db.Column(db.Integer)
customer = db.relationship("Customer", back_populates="rentals")

Choose a reason for hiding this comment

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

Nice job setting up the rental relationships!

video = db.relationship("Video", back_populates="rentals")

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["due_date"] = self.due_date
rental_dict["check_out_status"] = self.check_out_status

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
25 changes: 24 additions & 1 deletion app/models/video.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
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,nullable = False)
release_date = db.Column(db.String)
total_inventory = db.Column(db.Integer)
rentals = db.relationship("Rental", back_populates="video")


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
File renamed without changes.
159 changes: 159 additions & 0 deletions app/routes/customer_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from app import db
from app.models.customer import Customer
from app.models.video import Video
from app.models.rental import Rental
from flask import Blueprint, jsonify, abort, make_response, request
from datetime import datetime


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

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 validate_num_queries(query_param):
try:
query_int = int(query_param)
except:
return False
return True

#============================== customers_bp.route =============================
#============================================================================
#GET /customers
@customers_bp.route("", methods=["GET"])
def get_customers():
sort_query = request.args.get("sort")
if sort_query == "name":
customer_query = Customer.query.order_by(Customer.name)
elif sort_query == "registered_at":
customer_query = Customer.query.order_by(Customer.registered_at)
elif sort_query == "postal_code":
customer_query = Customer.query.order_by(Customer.postal_code)
else:
customer_query = Customer.query.order_by(Customer.id)

count_query = request.args.get("count")
page_num_query = request.args.get("page_num")
if validate_num_queries(count_query) and validate_num_queries(page_num_query):
page = customer_query.paginate(page=int(page_num_query), per_page=int(count_query), error_out=False)
customers_response = []

for items in page.items:
customers_response.append(items.to_dict())
return jsonify(customers_response), 200

if validate_num_queries(count_query) and not validate_num_queries(page_num_query):
page = customer_query.paginate(per_page=int(count_query), error_out=False)
customers = customer_query.all()
customers_response = []

for items in page.items:
customers_response.append(items.to_dict())
return jsonify(customers_response), 200


response_body = []

for customer in customer_query:
response_body.append(customer.to_dict())
return jsonify(response_body)

# POST /customers
@customers_bp.route("", methods=["POST"])
def create_customer():
try:
request_body = request.get_json()
new_customer = Customer.from_dict(request_body)
except:
abort(make_response({"details":"Request body must include name.,Request body must include phone.,Request body must include postal_code."}, 400))

db.session.add(new_customer)
db.session.commit()

response_body = new_customer.to_dict()
return make_response(response_body, 201)
# GET /customers/<id>
@customers_bp.route("/<id>", methods=["GET"])
def get_customers_by_id(id):
customer = validate_model(Customer, id)

return jsonify(customer.to_dict())



# DELETE /customers/<id>
@customers_bp.route("/<id>", methods=["DELETE"])
def put_customers_by_id(id):
customer = validate_model(Customer, id)

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

return jsonify(customer.to_dict()),200

# PUT /customers/<id>
@customers_bp.route("/<id>", methods=["PUT"])
def delete_customers_by_id(id):
customer = validate_model(Customer, id)
try:
request_body = request.get_json()
customer.name = request_body["name"]
customer.postal_code = request_body["postal_code"]
customer.phone = request_body["phone"]
except:
abort(make_response(jsonify("Bad Request"), 400))

db.session.commit()

return jsonify(customer.to_dict()),200


# `GET /customers/<id>/rentals`
@customers_bp.route("/<id>/rentals", methods=["GET"])
def get_rentals_by_customer_id(id):
customer = validate_model(Customer, id)

video_query = Rental.query.filter_by(customer_id=customer.id).join(Video)
sort_query = request.args.get("sort")

Choose a reason for hiding this comment

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

Good use of joining tables!

I will add that thanks to the relationships you've set up, you could use the OOP-style SQLAlchemy to get some of this information. Such as customer.rentals :)

if sort_query == "title":
rental_query = video_query.order_by(Video.title)
else:
rental_query = video_query.order_by(Video.id)
count_query = request.args.get("count")
page_num_query = request.args.get("page_num")

if validate_num_queries(count_query) and validate_num_queries(page_num_query):

page = video_query.paginate(page=int(page_num_query), per_page=int(count_query), error_out=False)
video_result = []

for items in page.items:
video_result.append(items.video.to_dict())
return jsonify(video_result), 200
if validate_num_queries(count_query) and not validate_num_queries(page_num_query):
page = video_query.paginate(per_page=int(count_query), error_out=False)
video_result = []

for items in page.items:
video_result.append(items.video.to_dict())
return jsonify(video_result), 200

response_body = []
for rental in rental_query:
response_body.append({"title":rental.video.title,
"id":rental.video.id,
"total_inventory":rental.video.total_inventory})


return jsonify(response_body)
85 changes: 85 additions & 0 deletions app/routes/rental_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from app import db
from app.models.rental import Rental
from app.models.customer import Customer
from app.models.video import Video
from app.routes.customer_route import validate_model
from flask import Blueprint, jsonify, abort, make_response, request
from datetime import datetime

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

#============================== rentals_bp.route =============================
#============================================================================

## `POST /rentals/check-out`
@rentals_bp.route("/check-out", methods=["POST"])
def create_rental():
request_body = request.get_json()
try:
new_rental = Rental.from_dict(request_body)
except:
abort(make_response({"message":f"Missing data"}, 400))

customer = validate_model(Customer, request_body["customer_id"])
video = validate_model(Video, request_body["video_id"])

if video.total_inventory == 0:
abort(make_response({"message":f"Could not perform checkout"}, 400))

videos_checked_out = Rental.query.filter_by(video_id=video.id).count() # look into count method

available_inventory = video.total_inventory - videos_checked_out
if available_inventory == 0:
abort(make_response({"message":f"Could not perform checkout"}, 400))

Choose a reason for hiding this comment

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

When I see repeated code, I think about added cost of maintenance. In this case, I would wonder if there's a way to extract repeated code. Maybe at least defining the error string as a variable, so you only have to make changes in one location. :)


# new_rental = Rental.from_dict(request_body)

customer.videos_checked_out_count += 1
db.session.add(new_rental)
db.session.add(customer)
db.session.commit()

rental_response = new_rental.to_dict()
rental_response["videos_checked_out_count"] = customer.videos_checked_out_count
available_inventory -= 1
rental_response["available_inventory"] = available_inventory

return jsonify(rental_response), 200




## `POST /rentals/check-in`
@rentals_bp.route("/check-in", methods=["POST"])
def check_in_rental():
request_body = request.get_json()
try:
rental = Rental.from_dict(request_body)
except:
abort(make_response({"message":f"Missing data"}, 400))
customer = validate_model(Customer, request_body["customer_id"])
video = validate_model(Video, request_body["video_id"])

rental = db.session.query(Rental).filter_by(video_id=video.id, customer_id=customer.id).first()

Choose a reason for hiding this comment

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

This is another example where you might be able to make use of OOP-style SQLAlchemy interfaces, here :)


if not rental:
abort(make_response({"message": f"No outstanding rentals for customer {customer.id} and video {video.id}"}, 400))

if rental.check_out_status == False:
abort(make_response({"message":f"Could not perform checkout bc already checked out"}, 400))
videos_checked_out = db.session.query(Rental).filter_by(video_id=video.id).all() # look into count method, refactor duplicate code
available_inventory = video.total_inventory - len(videos_checked_out)

customer.videos_checked_out_count -= 1
available_inventory += 1
rental.check_out_status = False


db.session.add(rental)
db.session.commit()

rental_response = rental.to_dict()
rental_response["videos_checked_out_count"] = customer.videos_checked_out_count
rental_response["available_inventory"] = available_inventory

return jsonify(rental_response), 200
Loading