-
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
Cedar - Ana S. & Maria O. #37
base: master
Are you sure you want to change the base?
Changes from all commits
8d4e73a
9dbf424
d9ca38e
5828c0a
8d0c384
a865298
ea6e7a0
e89c873
1a7b99f
e42e144
8fe14eb
bcf8da8
fa6f4dc
d5a8ae8
51127ec
4a723b0
f1a3814
859b3a9
c900270
9bcb065
c6b1559
ad76349
ab64d0b
aa4a56b
02ebaa0
e51103b
8baa451
e4bacac
d39d20d
3fd276c
de7c00a
ae87331
25635d1
82597cd
7d088b7
b22e284
b897585
10aa325
7392940
63a3dc3
0ce429d
7423a6f
323c9d1
e4d82d4
c198f20
96fe624
5f29f40
70a72c5
b5570d6
67449a7
00605ea
ef9f5b7
2c9c287
0de537a
c41252c
507757e
5f0eabe
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,20 @@ | ||
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, nullable=False) | ||
postal_code = db.Column(db.String, nullable=False) | ||
phone = db.Column(db.String, nullable=False) | ||
registered_at = db.Column(db.DateTime(timezone=True), default=datetime.datetime.utcnow()) | ||
|
||
|
||
|
||
def to_dict(self): | ||
return { | ||
"id": self.id, | ||
"name": self.name, | ||
"postal_code": self.postal_code, | ||
"phone": self.phone, | ||
"registered_at": self.registered_at | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,24 @@ | ||
from app import db | ||
import datetime | ||
|
||
class Rental(db.Model): | ||
id = db.Column(db.Integer, primary_key=True) | ||
|
||
# relationship | ||
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), primary_key=True,nullable=False) | ||
video_id = db.Column(db.Integer, db.ForeignKey('video.id'), primary_key=True,nullable=False) | ||
# extra columns | ||
due_date = db.Column(db.DateTime(timezone=True),nullable=True) | ||
available_inventory = db.Column(db.Integer, nullable=False) | ||
videos_checked_out_count = db.Column(db.Integer, nullable=True) | ||
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. Similar to |
||
checked_in = db.Column(db.DateTime(timezone=True), nullable=True) | ||
checkout_date = db.Column(db.DateTime(timezone=True), nullable=True) | ||
|
||
|
||
def to_dict(self): | ||
return{ | ||
"customer_id": self.customer_id, | ||
"video_id": self.video_id, | ||
"due_date": self.due_date, | ||
"available_inventory": self.available_inventory, | ||
"videos_checked_out_count": self.videos_checked_out_count if self.videos_checked_out_count else False, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,19 @@ | ||
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(50), nullable=False) | ||
release_date = db.Column(db.Date(), nullable=True) | ||
total_inventory = db.Column(db.Integer(), nullable=False) | ||
# relationship | ||
customers = db.relationship("Customer", secondary="rental", backref="video",lazy=True) | ||
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. Nice work creating this secondary relationship column! |
||
|
||
|
||
def to_json(self): | ||
return { | ||
"id": self.id, | ||
"title": self.title, | ||
"release_date": self.release_date, | ||
"total_inventory" : self.total_inventory | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
from app import db | ||
from app.models.customer import Customer | ||
from flask import Blueprint, jsonify, request, make_response, abort | ||
from app.models.rental import Rental | ||
from app.models.video import Video | ||
from app.routes.helper_functions import * | ||
|
||
|
||
customers_bp = Blueprint("customers", __name__, url_prefix="/customers") | ||
|
||
|
||
@customers_bp.route("", methods=["GET"], strict_slashes=False) | ||
def get_all_customers(): | ||
customer_objects = Customer.query.all() | ||
response_list = [] | ||
for customer in customer_objects: | ||
response_list.append(customer.to_dict()) | ||
Comment on lines
+15
to
+17
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 could be a nice place to use a list comprehension |
||
return make_response(jsonify(response_list), 200) | ||
|
||
# Get one | ||
@customers_bp.route("/<customer_id>", methods=["GET"]) | ||
def get_customer(customer_id): | ||
selected_customer = get_customer_from_id(customer_id) | ||
return make_response(selected_customer.to_dict(), 200) | ||
|
||
# Add customer | ||
@customers_bp.route("", methods=["POST"], strict_slashes=False) | ||
def add_customer(): | ||
request_body = request.get_json() | ||
if "postal_code" not in request_body: | ||
response_body = {"details": "Request body must include postal_code."} | ||
response = abort(make_response(response_body, 400)) | ||
if "name" not in request_body: | ||
response_body = {"details": "Request body must include name."} | ||
response = abort(make_response(response_body, 400)) | ||
if "phone" not in request_body: | ||
response_body = {"details": "Request body must include phone."} | ||
response = abort(make_response(response_body, 400)) | ||
new_customer = Customer( | ||
name=request_body["name"], | ||
postal_code=request_body["postal_code"], | ||
phone=request_body["phone"] | ||
) | ||
db.session.add(new_customer) | ||
db.session.commit() | ||
response = make_response(new_customer.to_dict(), 201) | ||
return response | ||
|
||
# Delete customer | ||
@customers_bp.route("/<customer_id>", methods=["DELETE"], strict_slashes=False) | ||
def delete_customer(customer_id): | ||
selected_customer = get_customer_from_id(customer_id) | ||
db.session.delete(selected_customer) | ||
db.session.commit() | ||
return make_response({"id": int(customer_id)}, 200) | ||
|
||
# Update customer | ||
@customers_bp.route("/<customer_id>", methods=["PUT"], strict_slashes=False) | ||
def update_customer(customer_id): | ||
selected_customer = get_customer_from_id(customer_id) | ||
request_body = request.get_json() | ||
if "name" in request_body: | ||
selected_customer.name = request_body["name"] | ||
else: | ||
abort(make_response({"error": "Invalid input"},400)) | ||
if "phone" in request_body: | ||
selected_customer.phone = request_body["phone"] | ||
else: | ||
abort(make_response({"error": "Invalid input"},400)) | ||
if "postal_code" in request_body: | ||
selected_customer.postal_code = request_body["postal_code"] | ||
else: | ||
abort(make_response({"error": "Invalid input"},400)) | ||
Comment on lines
+62
to
+73
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. Note the similarities between this code and the code on lines 30-38. Consider how you could use a helper function to DFRY up your code. |
||
db.session.commit() | ||
response_body = jsonify(selected_customer.to_dict()) | ||
return make_response(response_body, 200) | ||
|
||
# Get rentals by customer id | ||
@customers_bp.route("/<id>/rentals", methods=["GET"], strict_slashes=False) | ||
def get_rentals_by_customer_id(id): | ||
get_customer_from_id(id) | ||
rentals = Rental.query.filter_by(customer_id=id).all() | ||
response_body = [] | ||
for rental in rentals: | ||
video = get_video_by_id(rental.video_id) | ||
response_body.append({"release_date": video.release_date, | ||
"title": video.title, | ||
"due_date": rental.due_date}) | ||
Comment on lines
+86
to
+88
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. Consider creating an instance method to build the dictionary for you response to enhance readability and changeability. |
||
return make_response(jsonify(response_body), 200) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from app import db | ||
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 work encapsulating this functionality in helper functions and breaking it out into it's own file for use throughout your program! |
||
from app.models.customer import Customer | ||
from flask import make_response, abort | ||
from app.models.video import Video | ||
|
||
|
||
|
||
def valid_int(number): | ||
try: | ||
id = int(number) | ||
return id | ||
except: | ||
response_body = 'Invalid Data' | ||
abort(make_response(response_body,400)) | ||
|
||
|
||
|
||
def get_customer_from_id(customer_id): | ||
id = valid_int(customer_id) | ||
customer = Customer.query.filter_by(id=id).one_or_none() | ||
if customer is None: | ||
response_body = {"message": f"Customer {id} was not found"} | ||
abort(make_response(response_body, 404)) | ||
return customer | ||
|
||
def get_video_by_id(video_id): | ||
id = valid_int(video_id) | ||
video = Video.query.filter_by(id=id).one_or_none() | ||
if video is None: | ||
response_body = {"message": f"Video {id} was not found"} | ||
abort(make_response(response_body, 404)) | ||
return video | ||
|
||
def get_customer_and_video_id(request_body): | ||
customer = get_customer_from_id(request_body['customer_id']) | ||
video = get_video_by_id(request_body['video_id']) | ||
return customer,video | ||
|
||
|
||
def validate_request(action, request_body): | ||
if "customer_id" not in request_body or "video_id" not in request_body: | ||
return abort(make_response({"message": f"Could not perform {action}"}, 400)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
from app import db | ||
from app.models.rental import Rental | ||
from app.models.video import Video | ||
from flask import Blueprint, request, make_response, jsonify | ||
from datetime import datetime, timedelta, timezone | ||
from app.routes.helper_functions import * | ||
rentals_bp = Blueprint('rentals', __name__, url_prefix='/rentals') | ||
|
||
|
||
|
||
''' | ||
handling request, | ||
is it valid?, | ||
getting filtered data, | ||
checking out, | ||
returning data | ||
''' | ||
@rentals_bp.route("/check-out", methods=["POST"], strict_slashes=False) | ||
def check_out_video(): | ||
request_body = request.get_json() | ||
validate_request("checkout", request_body) | ||
customer, video = get_customer_and_video_id(request_body) | ||
|
||
videos_checked_out_by_customer_count = Rental.query.filter_by(customer_id=customer.id).count() | ||
videos_id_checked_out_count = Rental.query.filter_by(video_id=video.id).count() | ||
available_inventory = video.total_inventory - videos_id_checked_out_count | ||
|
||
if not available_inventory: | ||
return make_response({"message": f"Could not perform checkout"},400) | ||
|
||
videos_checked_out_by_customer_count += 1 | ||
available_inventory -= 1 | ||
checked_out_date =datetime.utcnow() | ||
due_date = checked_out_date + timedelta(days=7) | ||
|
||
new_rental = Rental(video_id=video.id, | ||
customer_id=customer.id, | ||
due_date=due_date, | ||
available_inventory=available_inventory, | ||
videos_checked_out_count=videos_checked_out_by_customer_count, | ||
checkout_date= checked_out_date) | ||
Comment on lines
+24
to
+41
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. Consider moving this logic into a class method on the Rental model. In general, we prefer to encapsulate logic that manipulates data in a method attached to the model, leaving the route function to process the request body, and create the response body. |
||
|
||
db.session.add(new_rental) | ||
db.session.commit() | ||
response_body = new_rental.to_dict() | ||
return make_response(response_body, 200) | ||
|
||
|
||
# Post check-in | ||
''' | ||
* handling request, | ||
* is it valid?, | ||
* getting filtered data, | ||
* handling test_checkin_video_not_checked_out, | ||
* checking in rental, returning data | ||
''' | ||
@rentals_bp.route("/check-in", methods=["POST"], strict_slashes=False) | ||
def check_in_video(): | ||
|
||
request_body = request.get_json() | ||
|
||
validate_request("checkin", request_body) | ||
customer, video = get_customer_and_video_id(request_body) | ||
rentals = Rental.query.filter_by(video_id=video.id, customer_id=customer.id).one_or_none() | ||
|
||
if rentals == None: | ||
return make_response({"message": f"No outstanding rentals for customer {customer.id} and video {video.id}"}, 400) | ||
|
||
if not rentals.checked_in: | ||
rentals.due_date = None, | ||
rentals.available_inventory = rentals.available_inventory + 1 | ||
rentals.videos_checked_out_count= rentals.videos_checked_out_count - 1 | ||
rentals.checked_in =datetime.utcnow() | ||
|
||
db.session.commit() | ||
|
||
response_body = rentals.to_dict() | ||
return make_response(response_body, 200) | ||
|
||
@rentals_bp.route("/overdue", methods=["GET"], strict_slashes=False) | ||
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 work implementing this route! |
||
def get_overdue_videos(): | ||
now = datetime.now(timezone.utc) | ||
rentals_overdue = Rental.query.filter(Rental.due_date < now).all() | ||
response_body = [] | ||
|
||
for rental in rentals_overdue: | ||
customer = get_customer_from_id(rental.customer_id) | ||
video = get_video_by_id(rental.video_id) | ||
|
||
response_body.append( | ||
{"customer_id": rental.customer_id, | ||
"video_id": rental.video_id, | ||
"Video Title": video.title, | ||
"Costumer name": customer.name, | ||
"postal_code": customer.postal_code, | ||
"due_date":rental.due_date, | ||
"checkout_date":rental.checkout_date | ||
} | ||
) | ||
|
||
return make_response(jsonify(response_body), 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.
Consider calculating available inventory based of the number of rentals a video currently has. For instance something like, for the
video
model:available_inventory = len([rental for rental in self.rentals if not rental.check_in_date])
This method requires that with have a
rentals
relationships column on our video:rentals = db.relationship('Rental', back_populates='customer', lazy=True)
This method of dynamically computing
available_inventory
rather than storing it in an instance variable makes it so there isn’t information stored in multiple places that could potentially conflict. Also something to note is thatavailable_inventory
is more of an attribute of avideo
rather thanrental