From 6665a5697e88621d4237fbc5a3bd8f50e44820b7 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Tue, 3 Jan 2023 13:53:18 -0800 Subject: [PATCH 01/56] Wave 0:Setup added migrations folder --- migrations/README | 1 + migrations/alembic.ini | 45 ++++++++++++++++++ migrations/env.py | 96 +++++++++++++++++++++++++++++++++++++++ migrations/script.py.mako | 24 ++++++++++ 4 files changed, 166 insertions(+) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..f8ed4801f --- /dev/null +++ b/migrations/alembic.ini @@ -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 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -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() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} From 75fb009da27c1f07310d5a86e78d11ee2f4b661e Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 13:04:46 -0800 Subject: [PATCH 02/56] Registered /customers blueprint --- app/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 4ab3975b8..9df10a2fe 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -31,6 +31,8 @@ def create_app(test_config=None): db.init_app(app) migrate.init_app(app, db) - #Register Blueprints Here + #Register Blueprints + from .routes import customers_bp + app.register_blueprint(customers_bp) return app \ No newline at end of file From 9f10b69fb4396d53e8a899d38f0bb7d22fadcffa Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 13:05:13 -0800 Subject: [PATCH 03/56] Added customer class attributes and datatypes --- app/models/customer.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/models/customer.py b/app/models/customer.py index 54d10b49a..2c2f1697d 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -2,3 +2,20 @@ class Customer(db.Model): id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False) + postal_code = db.Column(db.String, nullable=False) + phone_number = db.Column(db.String, nullable=False) + registered_at = db.Column(db.DateTime, nullable=False) + # videos_checked_out = db.Column(db.Integer) + + def to_dict(self): + """ + Returns dictionary of customer data. + """ + return { + "id": self.id, + "name": self.name, + "postal code": self.postal_code, + "phone number": self.phone_number, + "registered at": self.registered_at, + } \ No newline at end of file From 4f2d3d43ebc035a214dc9afb2bcbbd3429b0fd89 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 13:05:51 -0800 Subject: [PATCH 04/56] Updated migrations with new customer class columns and datatypes --- ...added_customer_attributes_and_datatypes.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py diff --git a/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py b/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py new file mode 100644 index 000000000..99342c91a --- /dev/null +++ b/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py @@ -0,0 +1,45 @@ +"""Added customer attributes and datatypes + +Revision ID: d6e919e969dd +Revises: +Create Date: 2023-01-04 13:00:37.921761 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd6e919e969dd' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('customer', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('postal_code', sa.String(), nullable=False), + sa.Column('phone_number', sa.String(), nullable=False), + sa.Column('registered_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('rental', + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('video', + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('video') + op.drop_table('rental') + op.drop_table('customer') + # ### end Alembic commands ### From ee5ccc7d7f31ebcb06415060c2f2b708a8b38c27 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 13:06:27 -0800 Subject: [PATCH 05/56] Added routes for customers blueprint --- app/routes.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/routes.py b/app/routes.py index e69de29bb..1262ac45d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -0,0 +1,32 @@ +from flask import Blueprint, jsonify +from .models.customer import Customer + +# initialize customers blueprint +customers_bp = Blueprint("customers", __name__, url_prefix="/customers") + +# ~~~~~~ customers endpoints ~~~~~~ +@customers_bp.route("", methods=["GET"]) +def display_all_customers(): + # query all customers + customers = Customer.query.all() + # initialize response list + response = [] + for customer in customers: + response.append(customer.to_dict) + return jsonify(response) + +@customers_bp.route("/", methods=["GET"]) +def display_one_customer(): + pass + +@customers_bp.route("", methods=["POST"]) +def create_a_customer(): + pass + +@customers_bp.route("/", methods=["POST"]) +def modify_a_customer(): + pass + +@customers_bp.route("/", methods=["POST"]) +def delete_a_customer(): + pass \ No newline at end of file From 7a0dfb33a2adbec06ed101d09fb99b03d0e3f4ed Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 15:41:03 -0800 Subject: [PATCH 06/56] Updated customer attributes and added class methods --- app/models/customer.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index 2c2f1697d..20443bf65 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -4,8 +4,8 @@ class Customer(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) postal_code = db.Column(db.String, nullable=False) - phone_number = db.Column(db.String, nullable=False) - registered_at = db.Column(db.DateTime, nullable=False) + phone = db.Column(db.String, nullable=False) + registered_at = db.Column(db.DateTime) # videos_checked_out = db.Column(db.Integer) def to_dict(self): @@ -15,7 +15,23 @@ def to_dict(self): return { "id": self.id, "name": self.name, - "postal code": self.postal_code, - "phone number": self.phone_number, - "registered at": self.registered_at, - } \ No newline at end of file + "postal_code": self.postal_code, + "phone": self.phone, + "registered_at": self.registered_at, + } + + def retrieve_by_id(customer_id): + """ + Returns customer instance given customer id. + """ + return Customer.query.get(customer_id) + + def create_from_dict(dict): + """ + Creates customer instance from dict values. + """ + return Customer( + name=dict["name"], + postal_code=dict["postal_code"], + phone=dict["phone"] + ) \ No newline at end of file From c56bda98b69a02c3d9d6fc807fd31a117c8fa8f7 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 15:41:36 -0800 Subject: [PATCH 07/56] Added routes for displaying all customers, one customer by id, and posting a new customer --- app/routes.py | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/app/routes.py b/app/routes.py index 1262ac45d..857ded973 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,5 @@ -from flask import Blueprint, jsonify +from app import db +from flask import Blueprint, jsonify, make_response, abort, request from .models.customer import Customer # initialize customers blueprint @@ -12,16 +13,46 @@ def display_all_customers(): # initialize response list response = [] for customer in customers: - response.append(customer.to_dict) + response.append(customer.to_dict()) return jsonify(response) @customers_bp.route("/", methods=["GET"]) -def display_one_customer(): - pass +def display_one_customer(customer_id): + try: + # check if customer id is int + customer_id = int(customer_id) + except: + abort(make_response({ + "message": f"{customer_id} should be an integer dtype" + }, 400)) + # retrieve customer + customer = Customer.retrieve_by_id(customer_id) + # check if customer exists + if not customer: + abort(make_response({ + "message": f"{customer_id} not found" + }, 404)) + return customer @customers_bp.route("", methods=["POST"]) def create_a_customer(): - pass + # request requirements + reqs = ["name", "postal_code", "phone"] + # collect request + request_body = request.get_json() + # check if all requirements not in request + if not all(x in request_body for x in reqs): + abort(make_response({ + "message": "name, postal_code and phone required" + }, 400)) + # create new customer + new_customer = Customer.create_from_dict(request_body) + # commit to database + db.session.add(new_customer) + db.session.commit() + return make_response({ + "message": "customer has been successfully created" + }, 201) @customers_bp.route("/", methods=["POST"]) def modify_a_customer(): From 2c78b554ee3ec1f246a899820ee35b4b4e427ef6 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 15:41:58 -0800 Subject: [PATCH 08/56] Upgraded migrations to reflect changes in customer attributes --- ...changed_phone_number_attr_to_phone_for_.py | 30 +++++++++++++++++ ...changed_registered_at_attr_in_customer_.py | 32 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py create mode 100644 migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py diff --git a/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py b/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py new file mode 100644 index 000000000..32d3b0829 --- /dev/null +++ b/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py @@ -0,0 +1,30 @@ +"""Changed phone_number attr to phone for Customer class + +Revision ID: 3f7ddb648abb +Revises: d6e919e969dd +Create Date: 2023-01-04 15:33:30.154196 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3f7ddb648abb' +down_revision = 'd6e919e969dd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('customer', sa.Column('phone', sa.String(), nullable=False)) + op.drop_column('customer', 'phone_number') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('customer', sa.Column('phone_number', sa.VARCHAR(), autoincrement=False, nullable=False)) + op.drop_column('customer', 'phone') + # ### end Alembic commands ### diff --git a/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py b/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py new file mode 100644 index 000000000..37933c740 --- /dev/null +++ b/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py @@ -0,0 +1,32 @@ +"""Changed registered_at attr in customer class as nullable + +Revision ID: 48b860c77074 +Revises: 3f7ddb648abb +Create Date: 2023-01-04 15:36:39.984641 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '48b860c77074' +down_revision = '3f7ddb648abb' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('customer', 'registered_at', + existing_type=postgresql.TIMESTAMP(), + nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('customer', 'registered_at', + existing_type=postgresql.TIMESTAMP(), + nullable=False) + # ### end Alembic commands ### From d9b1bca363821ba9cfba0d192845d681616afd6b Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 17:18:26 -0800 Subject: [PATCH 09/56] Added update_attr() method which allows a value of a customer instance attr to be changed --- app/models/customer.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/customer.py b/app/models/customer.py index 20443bf65..db180e471 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -19,6 +19,20 @@ def to_dict(self): "phone": self.phone, "registered_at": self.registered_at, } + + def update_attr(self, attr, val): + """ + Updates an attr given a *modifiable attr and value. + * modifiable attrs: name, postal_code, phone + """ + if attr == "name": + self.name = val + elif attr == "postal_code": + self.postal_code = val + elif attr == "phone": + self.phone = val + else: + return False def retrieve_by_id(customer_id): """ @@ -34,4 +48,4 @@ def create_from_dict(dict): name=dict["name"], postal_code=dict["postal_code"], phone=dict["phone"] - ) \ No newline at end of file + ) From 15505c1fc68cb6f58098d99d8daf84dd69a60887 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 17:19:16 -0800 Subject: [PATCH 10/56] Added delete_a_customer route and modify_a_customer route --- app/routes.py | 100 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/app/routes.py b/app/routes.py index 857ded973..06d35441c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,6 +5,47 @@ # initialize customers blueprint customers_bp = Blueprint("customers", __name__, url_prefix="/customers") +# ~~~~~~ validation checkers ~~~~~~ +def validate_customer_id(customer_id): + """ + Checks if customer id is correct dtype (int) and if exists in db. + :params: + - customer_id + :returns: + - response_msg (dict), status_code (int) + """ + # check if customer id is integer dtype + try: + customer_id = int(customer_id) + except: + abort(make_response({"message": f"{customer_id} should be an integer dtype"}, 400)) + # fetch customer by id + customer = Customer.retrieve_by_id(customer_id) + if not customer: + return abort(make_response({"message": f"{customer_id} not found"}, 404)) + else: + return customer + +def validate_post_request(request): + """ + Validates that http requests satisfy all requirements for PUT/POST methods + :params: + - request (from client) + :returns: + - request_body (if requirements met) + """ + # request requirements for put/post + reqs = ["name", "postal_code", "phone"] + # collect request + request_body = request.get_json() + # check if all requirements in request + if all(x in request_body for x in reqs): + return request_body + else: + abort(make_response({ + "message": "name, postal_code and phone required" + }, 400)) + # ~~~~~~ customers endpoints ~~~~~~ @customers_bp.route("", methods=["GET"]) def display_all_customers(): @@ -18,46 +59,43 @@ def display_all_customers(): @customers_bp.route("/", methods=["GET"]) def display_one_customer(customer_id): - try: - # check if customer id is int - customer_id = int(customer_id) - except: - abort(make_response({ - "message": f"{customer_id} should be an integer dtype" - }, 400)) - # retrieve customer - customer = Customer.retrieve_by_id(customer_id) - # check if customer exists - if not customer: - abort(make_response({ - "message": f"{customer_id} not found" - }, 404)) - return customer + customer = validate_customer_id(customer_id) + return customer.to_dict() @customers_bp.route("", methods=["POST"]) def create_a_customer(): - # request requirements - reqs = ["name", "postal_code", "phone"] - # collect request - request_body = request.get_json() - # check if all requirements not in request - if not all(x in request_body for x in reqs): - abort(make_response({ - "message": "name, postal_code and phone required" - }, 400)) + request_body = validate_post_request(request) # create new customer new_customer = Customer.create_from_dict(request_body) - # commit to database db.session.add(new_customer) db.session.commit() return make_response({ "message": "customer has been successfully created" }, 201) -@customers_bp.route("/", methods=["POST"]) -def modify_a_customer(): - pass +@customers_bp.route("/", methods=["PUT"]) +def modify_a_customer(customer_id): + request_body = request.get_json() + customer = validate_customer_id(customer_id) + # unpack request body items + for key, val in request_body.items(): + # update customer data + result = customer.update_attr(key, val) + # check if attr not valid + if not result: + return abort(make_response({ + "message": f"{key} is not a modifiable data type for customers" + }, 400)) + db.session.commit() + return make_response( + {"message": f"Customer #{customer_id} successfully updated"}, 200 + ) -@customers_bp.route("/", methods=["POST"]) -def delete_a_customer(): - pass \ No newline at end of file +@customers_bp.route("/", methods=["DELETE"]) +def delete_a_customer(customer_id): + customer = validate_customer_id(customer_id) + db.session.delete(customer) + db.session.commit() + return make_response( + {"message": f"Customer #{customer_id} successfully deleted"}, 200 + ) \ No newline at end of file From 7f6d6be2d8956e38ce99af2ddc0130ae62191527 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 17:51:37 -0800 Subject: [PATCH 11/56] added compare_type=True to Migrations --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 9df10a2fe..b156a2ce2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -5,7 +5,7 @@ from dotenv import load_dotenv db = SQLAlchemy() -migrate = Migrate() +migrate = Migrate(compare_type=True) load_dotenv() def create_app(test_config=None): From 86c9e700b4f04ac17fa304e8b8e259fa70eae8c1 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 17:52:14 -0800 Subject: [PATCH 12/56] Got default datetime working for registered_at customer data --- app/models/customer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index db180e471..fd4a98297 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -1,3 +1,4 @@ +from datetime import datetime from app import db class Customer(db.Model): @@ -5,8 +6,7 @@ class Customer(db.Model): 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) - # videos_checked_out = db.Column(db.Integer) + registered_at = db.Column(db.DateTime, default=datetime.now()) def to_dict(self): """ From 7b92a0f22d79634167297537ff38b4cd4648bb64 Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 17:52:36 -0800 Subject: [PATCH 13/56] Refactored routes to make use of customer methods --- app/routes.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/routes.py b/app/routes.py index 06d35441c..7f1ba1f54 100644 --- a/app/routes.py +++ b/app/routes.py @@ -2,8 +2,6 @@ from flask import Blueprint, jsonify, make_response, abort, request from .models.customer import Customer -# initialize customers blueprint -customers_bp = Blueprint("customers", __name__, url_prefix="/customers") # ~~~~~~ validation checkers ~~~~~~ def validate_customer_id(customer_id): @@ -46,6 +44,9 @@ def validate_post_request(request): "message": "name, postal_code and phone required" }, 400)) +# ~~~~~~ initialize customers blueprint ~~~~~~ +customers_bp = Blueprint("customers", __name__, url_prefix="/customers") + # ~~~~~~ customers endpoints ~~~~~~ @customers_bp.route("", methods=["GET"]) def display_all_customers(): @@ -69,14 +70,12 @@ def create_a_customer(): new_customer = Customer.create_from_dict(request_body) db.session.add(new_customer) db.session.commit() - return make_response({ - "message": "customer has been successfully created" - }, 201) + return make_response({"id": new_customer.id}, 201) @customers_bp.route("/", methods=["PUT"]) def modify_a_customer(customer_id): - request_body = request.get_json() customer = validate_customer_id(customer_id) + request_body = request.get_json() # unpack request body items for key, val in request_body.items(): # update customer data @@ -87,9 +86,7 @@ def modify_a_customer(customer_id): "message": f"{key} is not a modifiable data type for customers" }, 400)) db.session.commit() - return make_response( - {"message": f"Customer #{customer_id} successfully updated"}, 200 - ) + return make_response(customer.to_dict(), 200) @customers_bp.route("/", methods=["DELETE"]) def delete_a_customer(customer_id): From 037e36f4537e39d667ef73f1a5dd39d01730f97d Mon Sep 17 00:00:00 2001 From: larissa Date: Wed, 4 Jan 2023 18:54:46 -0800 Subject: [PATCH 14/56] Added return True boolean to customer.update_attr() if update successful --- app/models/customer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/customer.py b/app/models/customer.py index fd4a98297..613d14ef6 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -33,6 +33,7 @@ def update_attr(self, attr, val): self.phone = val else: return False + return True def retrieve_by_id(customer_id): """ From c278adb367d14196127a525bd5ab65197ec46c5e Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:47:00 -0800 Subject: [PATCH 15/56] added video model and to_dict() and from_dict() methods. --- app/models/video.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/models/video.py b/app/models/video.py index db3bf3aeb..d5e8bd3a4 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -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()) + release_date = db.Column(db.Date()) + total_inventory = db.Column(db.Integer()) + #customer = db.relationship("Customer", back_populates="videos") + + def to_dict(self): + video_as_dict = {} + video_as_dict["id"] = self.id + video_as_dict["title"] = self.title + video_as_dict["release_date"] = self.release_date + video_as_dict["total_inventory"] = self.total_inventory + + return video_as_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 From 35d93a4dfa8b870bf5d13a8657b4717b1bf86638 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:52:17 -0800 Subject: [PATCH 16/56] added video blueprint and registered it with the app. --- app/__init__.py | 2 ++ app/video_routes.py | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 app/video_routes.py diff --git a/app/__init__.py b/app/__init__.py index 4ab3975b8..b67448fc1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -32,5 +32,7 @@ def create_app(test_config=None): migrate.init_app(app, db) #Register Blueprints Here + from .video_routes import video_bp + app.register_blueprint(video_bp) return app \ No newline at end of file diff --git a/app/video_routes.py b/app/video_routes.py new file mode 100644 index 000000000..fa35529cb --- /dev/null +++ b/app/video_routes.py @@ -0,0 +1,5 @@ +from app import db +from app.models.video import Video +from flask import Blueprint, jsonify, abort, make_response, request + +video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") From 7da4e5bce2ec71b6d0ee89d08754d98684639d3e Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 13:08:21 -0800 Subject: [PATCH 17/56] added routes to get and create videos. --- app/video_routes.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/video_routes.py b/app/video_routes.py index fa35529cb..405a54304 100644 --- a/app/video_routes.py +++ b/app/video_routes.py @@ -3,3 +3,31 @@ from flask import Blueprint, jsonify, abort, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") + +@video_bp.route("", methods=["GET"]) +def get_all_videos(): + + videos = Video.query.all() + + videos_response = [] + for video in videos: + videos_response.append(video.to_dict()) + + return jsonify(videos_response) + +@video_bp.route("", methods=["GET"]) +def get_video_by_id(video_id): + + video = validate_model(Video, video_id) + + return video.to_dict() + +@video_bp.route("", methods=["POST"]) +def create_a_new_video(): + request_body = request.get_json() + new_video = Video.from_dict(request_body) + + db.session.add(new_video) + db.session.commit() + + return make_response(jsonify(f"Video {new_.video} successfully created"), 201) \ No newline at end of file From 5041c2930afe816fcfb388efc75d55d63ed47d41 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:24:20 -0800 Subject: [PATCH 18/56] moved routes to separate routes folder. Refactored validate_customer_id to validate_model. Some more refactoring to make the tests pass. --- app/__init__.py | 4 +- app/models/customer.py | 17 ++-- app/{routes.py => routes/customer_routes.py} | 84 +++++++++++++------- app/{ => routes}/video_routes.py | 12 ++- 4 files changed, 72 insertions(+), 45 deletions(-) rename app/{routes.py => routes/customer_routes.py} (52%) rename app/{ => routes}/video_routes.py (56%) diff --git a/app/__init__.py b/app/__init__.py index 35f934da8..b36e190fd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -32,9 +32,9 @@ def create_app(test_config=None): migrate.init_app(app, db) #Register Blueprints - from .routes import customers_bp + from app.routes.customer_routes import customers_bp app.register_blueprint(customers_bp) - from .video_routes import video_bp + from app.routes.video_routes import video_bp app.register_blueprint(video_bp) return app \ No newline at end of file diff --git a/app/models/customer.py b/app/models/customer.py index 613d14ef6..c5ca81f9e 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -25,21 +25,14 @@ def update_attr(self, attr, val): Updates an attr given a *modifiable attr and value. * modifiable attrs: name, postal_code, phone """ - if attr == "name": + if attr == "name" and val.isalpha(): self.name = val - elif attr == "postal_code": + elif attr == "postal_code" and not val.isalpha(): self.postal_code = val - elif attr == "phone": + elif attr == "phone" and not val.isalpha(): self.phone = val - else: - return False - return True - - def retrieve_by_id(customer_id): - """ - Returns customer instance given customer id. - """ - return Customer.query.get(customer_id) + + return False def create_from_dict(dict): """ diff --git a/app/routes.py b/app/routes/customer_routes.py similarity index 52% rename from app/routes.py rename to app/routes/customer_routes.py index 7f1ba1f54..0ba06566e 100644 --- a/app/routes.py +++ b/app/routes/customer_routes.py @@ -1,10 +1,10 @@ from app import db from flask import Blueprint, jsonify, make_response, abort, request -from .models.customer import Customer +from ..models.customer import Customer # ~~~~~~ validation checkers ~~~~~~ -def validate_customer_id(customer_id): +def validate_model(cls, model_id): """ Checks if customer id is correct dtype (int) and if exists in db. :params: @@ -14,35 +14,58 @@ def validate_customer_id(customer_id): """ # check if customer id is integer dtype try: - customer_id = int(customer_id) + model_id = int(model_id) except: - abort(make_response({"message": f"{customer_id} should be an integer dtype"}, 400)) + abort(make_response({"message": f"{model_id} should be an integer dtype"}, 400)) # fetch customer by id - customer = Customer.retrieve_by_id(customer_id) - if not customer: - return abort(make_response({"message": f"{customer_id} not found"}, 404)) + model = cls.query.get(model_id) + if not model: + return abort(make_response({"message": f"{cls.__name__} {model_id} was not found"}, 404)) + #Video 1 was not found'} else: - return customer + return model -def validate_post_request(request): +def validate_post_request(request, reqs): """ - Validates that http requests satisfy all requirements for PUT/POST methods + Validates that http requests satisfy all requirements for POST methods :params: - request (from client) :returns: - request_body (if requirements met) """ - # request requirements for put/post - reqs = ["name", "postal_code", "phone"] + # collect request request_body = request.get_json() # check if all requirements in request - if all(x in request_body for x in reqs): - return request_body - else: - abort(make_response({ - "message": "name, postal_code and phone required" - }, 400)) + for req in reqs: + if req not in request_body: + #"Request body must include name." in response_body["details"] + abort(make_response({ + "details" : f"Request body must include {req}." + + }, 400)) + return request_body + +def validate_put_request(request, reqs): + """ + Validates that http requests satisfy all requirements for PUT methods + :params: + - request (from client) + :returns: + - request_body (if requirements met) + """ + + # collect request + request_body = request.get_json() + # check if all requirements in request + for req in reqs: + if req not in request_body: + #"Request body must include name." in response_body["details"] + abort(make_response({ + "details" : f"Request body must include {req}." + + }, 400)) + return request_body # ~~~~~~ initialize customers blueprint ~~~~~~ customers_bp = Blueprint("customers", __name__, url_prefix="/customers") @@ -60,12 +83,13 @@ def display_all_customers(): @customers_bp.route("/", methods=["GET"]) def display_one_customer(customer_id): - customer = validate_customer_id(customer_id) + customer = validate_model(Customer, customer_id) return customer.to_dict() @customers_bp.route("", methods=["POST"]) def create_a_customer(): - request_body = validate_post_request(request) + reqs = ["name", "postal_code", "phone"] + request_body = validate_post_request(request,reqs) # create new customer new_customer = Customer.create_from_dict(request_body) db.session.add(new_customer) @@ -74,25 +98,29 @@ def create_a_customer(): @customers_bp.route("/", methods=["PUT"]) def modify_a_customer(customer_id): - customer = validate_customer_id(customer_id) + + reqs = {"name", "postal_code", "phone"} request_body = request.get_json() + set_request_keys = set(request_body.keys()) + if not set_request_keys.issubset(reqs): + return abort(make_response({ + "message": f"modifiable must include {reqs}" + }, 400)) + customer = validate_model(Customer, customer_id) # unpack request body items for key, val in request_body.items(): # update customer data result = customer.update_attr(key, val) - # check if attr not valid if not result: - return abort(make_response({ - "message": f"{key} is not a modifiable data type for customers" - }, 400)) + return abort(make_response({"message": f"{val} is invalid"}, 400)) + db.session.commit() return make_response(customer.to_dict(), 200) @customers_bp.route("/", methods=["DELETE"]) def delete_a_customer(customer_id): - customer = validate_customer_id(customer_id) + customer = validate_model(Customer, customer_id) db.session.delete(customer) db.session.commit() return make_response( - {"message": f"Customer #{customer_id} successfully deleted"}, 200 - ) \ No newline at end of file + {"id" : customer.id,"message": f"Customer #{customer_id} successfully deleted"}, 200) \ No newline at end of file diff --git a/app/video_routes.py b/app/routes/video_routes.py similarity index 56% rename from app/video_routes.py rename to app/routes/video_routes.py index 405a54304..ad82704e6 100644 --- a/app/video_routes.py +++ b/app/routes/video_routes.py @@ -1,12 +1,13 @@ from app import db from app.models.video import Video +from app.routes.customer_routes import validate_model, validate_post_request, validate_put_response from flask import Blueprint, jsonify, abort, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") @video_bp.route("", methods=["GET"]) def get_all_videos(): - + #The API should return an empty array and a status 200 if there are no videos. videos = Video.query.all() videos_response = [] @@ -17,17 +18,22 @@ def get_all_videos(): @video_bp.route("", methods=["GET"]) def get_video_by_id(video_id): - + # The API should return back detailed errors and a status 404: Not Found + # if this video does not exist. video = validate_model(Video, video_id) return video.to_dict() @video_bp.route("", methods=["POST"]) def create_a_new_video(): + # The API should return back detailed errors and a status 400: Bad Request + # if the video does not have any of the required fields to be valid. request_body = request.get_json() + reqs = ["title", "release_date", "total_inventory"] + #request_body = validate_post_request(request,reqs) new_video = Video.from_dict(request_body) db.session.add(new_video) db.session.commit() - return make_response(jsonify(f"Video {new_.video} successfully created"), 201) \ No newline at end of file + return make_response(new_video.to_dict(), 201) \ No newline at end of file From 020dece882af55061a9f62990812a42ac1c6c493 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:34:58 -0800 Subject: [PATCH 19/56] fix an issue that was making some tests fail. --- app/routes/video_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index ad82704e6..281112dba 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,6 +1,6 @@ from app import db from app.models.video import Video -from app.routes.customer_routes import validate_model, validate_post_request, validate_put_response +from app.routes.customer_routes import validate_model from flask import Blueprint, jsonify, abort, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") From 5e365e62daea350b89fada5695066b20e4004c17 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 16:24:45 -0800 Subject: [PATCH 20/56] Some more refactoring to create separate validate_request_attributes helper. --- app/routes/customer_routes.py | 49 +++++++++++++++-------------------- app/routes/video_routes.py | 39 +++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 0ba06566e..916d2b94e 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -21,7 +21,6 @@ def validate_model(cls, model_id): model = cls.query.get(model_id) if not model: return abort(make_response({"message": f"{cls.__name__} {model_id} was not found"}, 404)) - #Video 1 was not found'} else: return model @@ -39,11 +38,8 @@ def validate_post_request(request, reqs): # check if all requirements in request for req in reqs: if req not in request_body: - #"Request body must include name." in response_body["details"] - abort(make_response({ - "details" : f"Request body must include {req}." - - }, 400)) + response = f"Request body must include {req}." + abort(make_response({"details" : response}, 400)) return request_body def validate_put_request(request, reqs): @@ -58,15 +54,22 @@ def validate_put_request(request, reqs): # collect request request_body = request.get_json() # check if all requirements in request - for req in reqs: - if req not in request_body: - #"Request body must include name." in response_body["details"] - abort(make_response({ - "details" : f"Request body must include {req}." - - }, 400)) + set_request_keys = set(request_body.keys()) + if not set_request_keys.issubset(reqs): + return abort(make_response({ + "message": f"modifiable must include {reqs}" + }, 400)) return request_body +def validate_request_attributes(cls,request_body): + # unpack request body items + for key, val in request_body.items(): + # update customer data + result = cls.update_attr(key, val) + if not result: + return abort(make_response({"message": f"{val} is invalid"}, 400)) + return result + # ~~~~~~ initialize customers blueprint ~~~~~~ customers_bp = Blueprint("customers", __name__, url_prefix="/customers") @@ -100,21 +103,11 @@ def create_a_customer(): def modify_a_customer(customer_id): reqs = {"name", "postal_code", "phone"} - request_body = request.get_json() - set_request_keys = set(request_body.keys()) - if not set_request_keys.issubset(reqs): - return abort(make_response({ - "message": f"modifiable must include {reqs}" - }, 400)) - customer = validate_model(Customer, customer_id) - # unpack request body items - for key, val in request_body.items(): - # update customer data - result = customer.update_attr(key, val) - if not result: - return abort(make_response({"message": f"{val} is invalid"}, 400)) - - db.session.commit() + request_body = validate_put_request(request,reqs) + result = validate_request_attributes(Customer, request_body) + if result: + customer = validate_model(Customer, customer_id) + db.session.commit() return make_response(customer.to_dict(), 200) @customers_bp.route("/", methods=["DELETE"]) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 281112dba..dcda90ef6 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,6 +1,9 @@ from app import db from app.models.video import Video from app.routes.customer_routes import validate_model +from app.routes.customer_routes import validate_post_request +from app.routes.customer_routes import validate_put_request +from app.routes.customer_routes import validate_request_attributes from flask import Blueprint, jsonify, abort, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") @@ -28,12 +31,42 @@ def get_video_by_id(video_id): def create_a_new_video(): # The API should return back detailed errors and a status 400: Bad Request # if the video does not have any of the required fields to be valid. - request_body = request.get_json() + #request_body = request.get_json() reqs = ["title", "release_date", "total_inventory"] - #request_body = validate_post_request(request,reqs) + request_body = validate_post_request(request,reqs) new_video = Video.from_dict(request_body) db.session.add(new_video) db.session.commit() - return make_response(new_video.to_dict(), 201) \ No newline at end of file + return make_response(new_video.to_dict(), 201) + +@video_bp.route("", methods=["PUT"]) +def update_a_video(video_id): + # The API should return back detailed errors and a status 404: Not Found + # if this video does not exist. + # The API should return back a 400 Bad Request response + # for missing or invalid fields in the request body. + # For example, if total_inventory is missing or is not a number + reqs = ["title", "release_date", "total_inventory"] + request_body = validate_put_request(request,reqs) + new_video = validate_model(Video, video_id) + result = validate_request_attributes(request_body) + if result: + db.session.commit() + + return make_response(new_video.to_dict(), 200) + +@video_bp.route("/", methods=["DELETE"]) +def delete_video_by_id(video_id): + # The API should return back detailed errors and a status 404: Not Found + # if this video does not exist. + video = validate_model(Video,video_id) + + db.session.delete(video) + db.session.commit() + + return make_response({ + "id" : video.id, + "message": f"Video #{video_id} successfully deleted" + }, 200) \ No newline at end of file From 9af0ef6e1a66ce2b1ba1b9ca1b6df11c775cf295 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Thu, 5 Jan 2023 22:27:29 -0800 Subject: [PATCH 21/56] Wave 1 tests passing. Further refactored to combine validate_post_request and validate_put_request into validate_request. --- app/routes/customer_routes.py | 62 +++++++++++------------------------ app/routes/video_routes.py | 48 +++++++++++++-------------- 2 files changed, 42 insertions(+), 68 deletions(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 916d2b94e..7caa2a2d9 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -6,43 +6,25 @@ # ~~~~~~ validation checkers ~~~~~~ def validate_model(cls, model_id): """ - Checks if customer id is correct dtype (int) and if exists in db. + Checks if model id is correct dtype (int) and if exists in db. :params: - - customer_id + - model_id :returns: - response_msg (dict), status_code (int) """ - # check if customer id is integer dtype + # check if model id is integer dtype try: model_id = int(model_id) except: abort(make_response({"message": f"{model_id} should be an integer dtype"}, 400)) - # fetch customer by id + # fetch model by id model = cls.query.get(model_id) if not model: return abort(make_response({"message": f"{cls.__name__} {model_id} was not found"}, 404)) else: return model -def validate_post_request(request, reqs): - """ - Validates that http requests satisfy all requirements for POST methods - :params: - - request (from client) - :returns: - - request_body (if requirements met) - """ - - # collect request - request_body = request.get_json() - # check if all requirements in request - for req in reqs: - if req not in request_body: - response = f"Request body must include {req}." - abort(make_response({"details" : response}, 400)) - return request_body - -def validate_put_request(request, reqs): +def validate_request(request, reqs): """ Validates that http requests satisfy all requirements for PUT methods :params: @@ -55,21 +37,14 @@ def validate_put_request(request, reqs): request_body = request.get_json() # check if all requirements in request set_request_keys = set(request_body.keys()) - if not set_request_keys.issubset(reqs): + set_reqs= set(reqs) + if not set_request_keys == set_reqs: + missing_key = "".join(set_reqs-set_request_keys) return abort(make_response({ - "message": f"modifiable must include {reqs}" + "details": f"Request body must include {missing_key}." }, 400)) return request_body -def validate_request_attributes(cls,request_body): - # unpack request body items - for key, val in request_body.items(): - # update customer data - result = cls.update_attr(key, val) - if not result: - return abort(make_response({"message": f"{val} is invalid"}, 400)) - return result - # ~~~~~~ initialize customers blueprint ~~~~~~ customers_bp = Blueprint("customers", __name__, url_prefix="/customers") @@ -91,8 +66,8 @@ def display_one_customer(customer_id): @customers_bp.route("", methods=["POST"]) def create_a_customer(): - reqs = ["name", "postal_code", "phone"] - request_body = validate_post_request(request,reqs) + reqs = {"name", "postal_code", "phone"} + request_body = validate_request(request,reqs) # create new customer new_customer = Customer.create_from_dict(request_body) db.session.add(new_customer) @@ -101,13 +76,16 @@ def create_a_customer(): @customers_bp.route("/", methods=["PUT"]) def modify_a_customer(customer_id): - + customer = validate_model(Customer, customer_id) + reqs = {"name", "postal_code", "phone"} - request_body = validate_put_request(request,reqs) - result = validate_request_attributes(Customer, request_body) - if result: - customer = validate_model(Customer, customer_id) - db.session.commit() + request_body = validate_request(request,reqs) + + customer.name = request_body["name"] + customer.postal_code = request_body["postal_code"] + customer.phone = request_body["phone"] + + db.session.commit() return make_response(customer.to_dict(), 200) @customers_bp.route("/", methods=["DELETE"]) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index dcda90ef6..7b8934ce7 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,19 +1,19 @@ from app import db from app.models.video import Video from app.routes.customer_routes import validate_model -from app.routes.customer_routes import validate_post_request -from app.routes.customer_routes import validate_put_request -from app.routes.customer_routes import validate_request_attributes +from app.routes.customer_routes import validate_request from flask import Blueprint, jsonify, abort, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") @video_bp.route("", methods=["GET"]) def get_all_videos(): - #The API should return an empty array and a status 200 if there are no videos. - videos = Video.query.all() - + videos_response = [] + videos = Video.query.all() + if not videos: + make_response(jsonify(videos_response), 200) + for video in videos: videos_response.append(video.to_dict()) @@ -21,19 +21,16 @@ def get_all_videos(): @video_bp.route("", methods=["GET"]) def get_video_by_id(video_id): - # The API should return back detailed errors and a status 404: Not Found - # if this video does not exist. + video = validate_model(Video, video_id) return video.to_dict() @video_bp.route("", methods=["POST"]) def create_a_new_video(): - # The API should return back detailed errors and a status 400: Bad Request - # if the video does not have any of the required fields to be valid. - #request_body = request.get_json() + reqs = ["title", "release_date", "total_inventory"] - request_body = validate_post_request(request,reqs) + request_body = validate_request(request,reqs) new_video = Video.from_dict(request_body) db.session.add(new_video) @@ -43,24 +40,23 @@ def create_a_new_video(): @video_bp.route("", methods=["PUT"]) def update_a_video(video_id): - # The API should return back detailed errors and a status 404: Not Found - # if this video does not exist. - # The API should return back a 400 Bad Request response - # for missing or invalid fields in the request body. - # For example, if total_inventory is missing or is not a number - reqs = ["title", "release_date", "total_inventory"] - request_body = validate_put_request(request,reqs) - new_video = validate_model(Video, video_id) - result = validate_request_attributes(request_body) - if result: - db.session.commit() + + video = validate_model(Video, video_id) + + reqs = {"title", "release_date", "total_inventory"} + request_body = validate_request(request,reqs) + + video.title = request_body["title"] + video.release_date = request_body["release_date"] + video.total_inventory = request_body["total_inventory"] + + db.session.commit() - return make_response(new_video.to_dict(), 200) + return make_response(video.to_dict(), 200) @video_bp.route("/", methods=["DELETE"]) def delete_video_by_id(video_id): - # The API should return back detailed errors and a status 404: Not Found - # if this video does not exist. + video = validate_model(Video,video_id) db.session.delete(video) From f04affb03a137ab9567550571ac1416603f5ecd1 Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 19:33:48 -0800 Subject: [PATCH 22/56] imported classes and blueprint for rentals endpoint --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index b36e190fd..06cb73d08 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -36,5 +36,7 @@ def create_app(test_config=None): app.register_blueprint(customers_bp) from app.routes.video_routes import video_bp app.register_blueprint(video_bp) + from app.routes.rentals_routes import rentals_bp + app.register_blueprint(rentals_bp) return app \ No newline at end of file From 6fa6a57d5954d5a5ae0d7b56235255cab343600f Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 19:34:43 -0800 Subject: [PATCH 23/56] added customers/customer_id/rentals endpoint --- app/routes/customer_routes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 7caa2a2d9..6524d458a 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -94,4 +94,12 @@ def delete_a_customer(customer_id): db.session.delete(customer) db.session.commit() return make_response( - {"id" : customer.id,"message": f"Customer #{customer_id} successfully deleted"}, 200) \ No newline at end of file + {"id" : customer.id,"message": f"Customer #{customer_id} successfully deleted"}, 200) + +@customers_bp.route("//rentals", methods=["GET"]) +def display_customer_rentals(customer_id): + customer = validate_model(Customer, customer_id) + rentals_response = [] + for rental in customer.rentals: + rentals_response.append(rental.to_dict()) + return make_response(jsonify(rentals_response), 200) From 774e7145e283247769b5095dcc9515123cccaa76 Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 19:35:06 -0800 Subject: [PATCH 24/56] Reinitialized migrations --- ...changed_phone_number_attr_to_phone_for_.py | 30 ---------- ...changed_registered_at_attr_in_customer_.py | 32 ----------- ..._initialized_all_attributes_for_rental_.py | 55 +++++++++++++++++++ ...added_customer_attributes_and_datatypes.py | 45 --------------- 4 files changed, 55 insertions(+), 107 deletions(-) delete mode 100644 migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py delete mode 100644 migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py create mode 100644 migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py delete mode 100644 migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py diff --git a/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py b/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py deleted file mode 100644 index 32d3b0829..000000000 --- a/migrations/versions/3f7ddb648abb_changed_phone_number_attr_to_phone_for_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Changed phone_number attr to phone for Customer class - -Revision ID: 3f7ddb648abb -Revises: d6e919e969dd -Create Date: 2023-01-04 15:33:30.154196 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '3f7ddb648abb' -down_revision = 'd6e919e969dd' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('customer', sa.Column('phone', sa.String(), nullable=False)) - op.drop_column('customer', 'phone_number') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('customer', sa.Column('phone_number', sa.VARCHAR(), autoincrement=False, nullable=False)) - op.drop_column('customer', 'phone') - # ### end Alembic commands ### diff --git a/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py b/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py deleted file mode 100644 index 37933c740..000000000 --- a/migrations/versions/48b860c77074_changed_registered_at_attr_in_customer_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Changed registered_at attr in customer class as nullable - -Revision ID: 48b860c77074 -Revises: 3f7ddb648abb -Create Date: 2023-01-04 15:36:39.984641 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import postgresql - -# revision identifiers, used by Alembic. -revision = '48b860c77074' -down_revision = '3f7ddb648abb' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('customer', 'registered_at', - existing_type=postgresql.TIMESTAMP(), - nullable=True) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('customer', 'registered_at', - existing_type=postgresql.TIMESTAMP(), - nullable=False) - # ### end Alembic commands ### diff --git a/migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py b/migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py new file mode 100644 index 000000000..0ec59d288 --- /dev/null +++ b/migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py @@ -0,0 +1,55 @@ +"""Initialized all attributes for Rental, Customer and Video + +Revision ID: 8b1b64498a3f +Revises: +Create Date: 2023-01-06 19:32:08.988351 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8b1b64498a3f' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('customer', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('postal_code', sa.String(), nullable=False), + sa.Column('phone', sa.String(), nullable=False), + sa.Column('registered_at', sa.DateTime(), nullable=True), + sa.Column('videos_checked_out_count', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('video', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('release_date', sa.Date(), nullable=True), + sa.Column('total_inventory', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('rentals', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('customer_id', sa.Integer(), nullable=True), + sa.Column('video_id', sa.Integer(), nullable=True), + sa.Column('due_date', sa.DateTime(), nullable=True), + sa.Column('n_video_copies', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['customer_id'], ['customer.id'], ), + sa.ForeignKeyConstraint(['video_id'], ['video.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('rentals') + op.drop_table('video') + op.drop_table('customer') + # ### end Alembic commands ### diff --git a/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py b/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py deleted file mode 100644 index 99342c91a..000000000 --- a/migrations/versions/d6e919e969dd_added_customer_attributes_and_datatypes.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Added customer attributes and datatypes - -Revision ID: d6e919e969dd -Revises: -Create Date: 2023-01-04 13:00:37.921761 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'd6e919e969dd' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('customer', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=False), - sa.Column('postal_code', sa.String(), nullable=False), - sa.Column('phone_number', sa.String(), nullable=False), - sa.Column('registered_at', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('rental', - sa.Column('id', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('video', - sa.Column('id', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('video') - op.drop_table('rental') - op.drop_table('customer') - # ### end Alembic commands ### From 50214398e0194051344316e08f139adf0c190e2a Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 19:35:41 -0800 Subject: [PATCH 25/56] New module for rentals routes --- app/routes/rentals_routes.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 app/routes/rentals_routes.py diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py new file mode 100644 index 000000000..ec3a40dd2 --- /dev/null +++ b/app/routes/rentals_routes.py @@ -0,0 +1,6 @@ +from app import db +from flask import Blueprint, jsonify, make_response, abort, request +from ..models.rental import Rental + +# ~~~~~~ initialize rentals blueprint ~~~~~~ +rentals_bp = Blueprint("rentals", __name__, url_prefix="/rentals") \ No newline at end of file From 26e19a80fd1576021048d133f7ddfb5d445c5727 Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 19:36:08 -0800 Subject: [PATCH 26/56] Set up all attributes for models --- app/models/customer.py | 32 ++++++++++++++--------- app/models/rental.py | 59 +++++++++++++++++++++++++++++++++++++++++- app/models/video.py | 9 +++++-- 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index c5ca81f9e..a8e0f15f3 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -7,6 +7,8 @@ class Customer(db.Model): postal_code = db.Column(db.String, nullable=False) phone = db.Column(db.String, nullable=False) registered_at = db.Column(db.DateTime, default=datetime.now()) + rentals = db.relationship("Rental", back_populates="customer") + videos_checked_out_count = db.Column(db.Integer) def to_dict(self): """ @@ -19,20 +21,26 @@ def to_dict(self): "phone": self.phone, "registered_at": self.registered_at, } + + def check_out_videos(self, n): + self.n_rented_videos += n - def update_attr(self, attr, val): - """ - Updates an attr given a *modifiable attr and value. - * modifiable attrs: name, postal_code, phone - """ - if attr == "name" and val.isalpha(): - self.name = val - elif attr == "postal_code" and not val.isalpha(): - self.postal_code = val - elif attr == "phone" and not val.isalpha(): - self.phone = val + def check_in_videos(self, n): + self.n_rented_videos -= n + + # def update_attr(self, attr, val): + # """ + # Updates an attr given a *modifiable attr and value. + # * modifiable attrs: name, postal_code, phone + # """ + # if attr == "name" and val.isalpha(): + # self.name = val + # elif attr == "postal_code" and not val.isalpha(): + # self.postal_code = val + # elif attr == "phone" and not val.isalpha(): + # self.phone = val - return False + # return False def create_from_dict(dict): """ diff --git a/app/models/rental.py b/app/models/rental.py index 11009e593..a0a855400 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -1,4 +1,61 @@ from app import db +import datetime as dt class Rental(db.Model): - id = db.Column(db.Integer, primary_key=True) \ No newline at end of file + __tablename__ = "rentals" + id = db.Column(db.Integer, primary_key=True) + customer = db.relationship("Customer", back_populates="rentals") + customer_id = db.Column(db.Integer, db.ForeignKey("customer.id")) + video = db.relationship("Video", back_populates="rentals") + video_id = db.Column(db.Integer, db.ForeignKey("video.id")) + due_date = db.Column(db.DateTime, default=dt.datetime.now()+dt.timedelta(days=7)) + n_video_copies = db.Column(db.Integer, default=1) + + def to_dict(self): + """ + Returns dictionary of customer data. + """ + return { + "id": self.id, + "customer_id": self.customer_id, + "video_id": self.video_id, + "due_date": self.due_date, + "videos_checked_out_count": self.videos_checked_out_count, + } + + # def check_out_video(self, n): + # """ + # Updates video available inventory when customer checks out video(s) + # """ + # # decrement the video inventory + # self.video.calculate_available_inventory() + # # increment the customer's number of rented videos + # self.customer.check_out_videos(n) + + # def check_in_video(self): + # """ + # Updates video available inventory when customer checks out video(s) + # """ + # # decrement the video inventory + # self.video.calculate_available_inventory() + # # increment the customer's number of rented videos + # self.customer.check_out_videos() + + + + def check_in_video(self, n): + """ + Updates videos checked out count and video available inventory + :params: + - n: number of videos to check in + """ + self.num_duplicates_video -= n + self.video.calculate_available_inventory() + + @classmethod + def from_dict(cls, rental_data): + new_rental = Rental( + due_date=rental_data["due_date"], + videos_checked_out=rental_data["videos_checked_out"], + ) + return new_rental \ No newline at end of file diff --git a/app/models/video.py b/app/models/video.py index d5e8bd3a4..83c1e9ae4 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -5,7 +5,7 @@ class Video(db.Model): title = db.Column(db.String()) release_date = db.Column(db.Date()) total_inventory = db.Column(db.Integer()) - #customer = db.relationship("Customer", back_populates="videos") + rentals = db.relationship("Rental", back_populates="video") def to_dict(self): video_as_dict = {} @@ -16,12 +16,17 @@ def to_dict(self): return video_as_dict + def calculate_available_inventory(self): + """ + Calculate number of available videos to rent + """ + self.available_inventory = self.total_inventory - self.rentals.n_video_copies + @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 From 67f05dfa2a72ebaa75bec8911538d9aa531bd374 Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 21:30:16 -0800 Subject: [PATCH 27/56] refactor --- app/models/customer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index a8e0f15f3..2a4fca102 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -14,13 +14,14 @@ def to_dict(self): """ Returns dictionary of customer data. """ - return { - "id": self.id, - "name": self.name, - "postal_code": self.postal_code, - "phone": self.phone, - "registered_at": self.registered_at, + customer_dict = { + "id": self.id, + "name": self.name, + "postal_code": self.postal_code, + "phone": self.phone, + "registered_at": self.registered_at, } + return customer_dict def check_out_videos(self, n): self.n_rented_videos += n From 46d9040cc5371acb9f45f10847d9ceae29d808ca Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 21:31:10 -0800 Subject: [PATCH 28/56] added clauses to_dict method to return more info about customer and video --- app/models/rental.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/rental.py b/app/models/rental.py index a0a855400..1b8b47c75 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -13,15 +13,17 @@ class Rental(db.Model): def to_dict(self): """ - Returns dictionary of customer data. + Returns dictionary of rental data and customer data if customer """ - return { - "id": self.id, - "customer_id": self.customer_id, - "video_id": self.video_id, - "due_date": self.due_date, - "videos_checked_out_count": self.videos_checked_out_count, + rental_dict = { + "due_date": self.due_date, + "n_video_copies": self.n_video_copies, } + if self.customer: + rental_dict.update(self.customer.to_dict()) + if self.video: + rental_dict.update(self.video.to_dict()) + return rental_dict # def check_out_video(self, n): # """ From f8ddbc0010dac9d920463c3342cef914e4f08166 Mon Sep 17 00:00:00 2001 From: larissa Date: Fri, 6 Jan 2023 21:31:47 -0800 Subject: [PATCH 29/56] added display_rentals_by_customer_id endpoint --- app/routes/video_routes.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 7b8934ce7..8840bdbe9 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,5 +1,6 @@ from app import db from app.models.video import Video +from app.models.rental import Rental from app.routes.customer_routes import validate_model from app.routes.customer_routes import validate_request from flask import Blueprint, jsonify, abort, make_response, request @@ -40,7 +41,7 @@ def create_a_new_video(): @video_bp.route("", methods=["PUT"]) def update_a_video(video_id): - + video = validate_model(Video, video_id) reqs = {"title", "release_date", "total_inventory"} @@ -56,7 +57,7 @@ def update_a_video(video_id): @video_bp.route("/", methods=["DELETE"]) def delete_video_by_id(video_id): - + video = validate_model(Video,video_id) db.session.delete(video) @@ -65,4 +66,11 @@ def delete_video_by_id(video_id): return make_response({ "id" : video.id, "message": f"Video #{video_id} successfully deleted" - }, 200) \ No newline at end of file + }, 200) + +@video_bp.route("/rentals", methods=["GET"]) +def display_rentals_by_customer_id(video_id): + video = validate_model(Video, video_id) + rentals = Rental.query.get(video_id) + return make_response( + jsonify(rentals.to_dict()), 200) \ No newline at end of file From 74f2bd97c09382c142a7e29010cfcee608911392 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:31:14 -0800 Subject: [PATCH 30/56] added available inventory to video model. --- app/models/video.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/video.py b/app/models/video.py index 83c1e9ae4..107297809 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -5,6 +5,7 @@ class Video(db.Model): title = db.Column(db.String()) release_date = db.Column(db.Date()) total_inventory = db.Column(db.Integer()) + available_inventory = db.Column(db.Integer()) rentals = db.relationship("Rental", back_populates="video") def to_dict(self): From 29f62dc2edc9fcbfd255a54e413953be60f3e019 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Fri, 6 Jan 2023 23:47:24 -0800 Subject: [PATCH 31/56] added route to checkout video using POST /rentals/checkout --- app/routes/rentals_routes.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py index ec3a40dd2..43b039156 100644 --- a/app/routes/rentals_routes.py +++ b/app/routes/rentals_routes.py @@ -1,6 +1,28 @@ from app import db from flask import Blueprint, jsonify, make_response, abort, request from ..models.rental import Rental +from ..models.customer import Customer +from ..models.video import Video +from ..routes.customer_routes import validate_model +from ..routes.customer_routes import validate_request # ~~~~~~ initialize rentals blueprint ~~~~~~ -rentals_bp = Blueprint("rentals", __name__, url_prefix="/rentals") \ No newline at end of file +rentals_bp = Blueprint("rentals", __name__, url_prefix="/rentals") + +@rentals_bp.route("/check-out", methods=["POST"]) +def checkout_video(): + reqs = {"customer_id", "video_id"} + request_body = validate_request(request, reqs) + video_id = request_body["video_id"] + video = validate_model(Video, video_id) + customer_id = request_body["customer_id"] + customer = validate_model(Customer, customer_id) + if video and customer: + new_rental = Rental(video_id=request_body["video_id"],customer_id=request_body["customer_id"]) + + db.session.add(new_rental) + db.session.commit() + + return make_response(jsonify(new_rental.to_dict()), 200) + else: + return make_response(jsonify({"message": f"Video {video_id} or Customer {customer_id} are invalid"}), 404) \ No newline at end of file From 5d282695974f332ac64415582f82ea32d101c822 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sat, 7 Jan 2023 10:31:56 -0800 Subject: [PATCH 32/56] added route for video check-in. Also added updates to video_check_out_count and available_inventory. --- app/routes/rentals_routes.py | 37 ++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py index 43b039156..b33a0dd8f 100644 --- a/app/routes/rentals_routes.py +++ b/app/routes/rentals_routes.py @@ -13,16 +13,49 @@ def checkout_video(): reqs = {"customer_id", "video_id"} request_body = validate_request(request, reqs) + video_id = request_body["video_id"] video = validate_model(Video, video_id) + customer_id = request_body["customer_id"] customer = validate_model(Customer, customer_id) + if video and customer: new_rental = Rental(video_id=request_body["video_id"],customer_id=request_body["customer_id"]) - + # update customer.videos_checked_out_count + new_rental.customer.video_checked_out_count += 1 + # update video.available_inventory + new_rental.video.available_inventory -= 1 db.session.add(new_rental) db.session.commit() return make_response(jsonify(new_rental.to_dict()), 200) else: - return make_response(jsonify({"message": f"Video {video_id} or Customer {customer_id} are invalid"}), 404) \ No newline at end of file + return make_response(jsonify({"message": f"Video {video_id} or Customer {customer_id} are invalid"}), 404) + +@rentals_bp.route("/check-in", methods=["POST"]) +def checkin_video(): + reqs = {"customer_id", "video_id"} + request_body = validate_request(request, reqs) + + video_id = request_body["video_id"] + video = validate_model(Video, video_id) + + customer_id = request_body["customer_id"] + customer = validate_model(Customer, customer_id) + + if video and customer: + # QUERY for rental by video_id and customer _id + rental = Rental.query.filter(video_id=video_id, customer_id=customer_id) + # if found check it in -> + # change status to checked_in + rental.status = "checked_in" + # update customer.videos_checked_out_count + rental.customer.videos_checked_out_count -= 1 + # update video.available_inventory + rental.video.available_inventory += 1 + rental.video.update + return make_response(jsonify(rental.to_dict()), 200) + else: + # if not found error out + return make_response(jsonify({"message": f"Video {video_id} or Customer {customer_id} are not found"}), 400) \ No newline at end of file From e1a734c42d6a32bc36e44c27a93de46a1fb7f81f Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sat, 7 Jan 2023 16:26:54 -0800 Subject: [PATCH 33/56] added status to rental model. --- ...c_added_status_to_rental_and_available_.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py diff --git a/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py b/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py new file mode 100644 index 000000000..6685a3d70 --- /dev/null +++ b/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py @@ -0,0 +1,30 @@ +"""added status to rental and available_inventory to video models. + +Revision ID: 7f3a3cc43a5c +Revises: 8b1b64498a3f +Create Date: 2023-01-07 16:24:54.863815 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '7f3a3cc43a5c' +down_revision = '8b1b64498a3f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('rentals', sa.Column('status', sa.String(), nullable=True)) + op.add_column('video', sa.Column('available_inventory', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('video', 'available_inventory') + op.drop_column('rentals', 'status') + # ### end Alembic commands ### From 9f9f2d4fb1705c1847e81c8974283a08bb4f2958 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sat, 7 Jan 2023 20:08:51 -0800 Subject: [PATCH 34/56] Wave 1 tests are passing with these fixes. --- app/models/rental.py | 39 ++++++++++++++++++++------------------- app/models/video.py | 11 ++++++++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/app/models/rental.py b/app/models/rental.py index a0a855400..3c485714e 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -9,6 +9,7 @@ class Rental(db.Model): video = db.relationship("Video", back_populates="rentals") video_id = db.Column(db.Integer, db.ForeignKey("video.id")) due_date = db.Column(db.DateTime, default=dt.datetime.now()+dt.timedelta(days=7)) + status = db.Column(db.String(), default="checked_out") n_video_copies = db.Column(db.Integer, default=1) def to_dict(self): @@ -20,9 +21,17 @@ def to_dict(self): "customer_id": self.customer_id, "video_id": self.video_id, "due_date": self.due_date, - "videos_checked_out_count": self.videos_checked_out_count, + "videos_checked_out_count": self.customer.videos_checked_out_count, + "available_inventory" : self.video.available_inventory } - + @classmethod + def from_dict(cls, rental_data): + new_rental = Rental( + due_date=rental_data["due_date"], + status=rental_data["status"] + ) + return new_rental + # def check_out_video(self, n): # """ # Updates video available inventory when customer checks out video(s) @@ -41,21 +50,13 @@ def to_dict(self): # # increment the customer's number of rented videos # self.customer.check_out_videos() - - - def check_in_video(self, n): - """ - Updates videos checked out count and video available inventory - :params: - - n: number of videos to check in - """ - self.num_duplicates_video -= n - self.video.calculate_available_inventory() + # def check_in_video(self, n): + # """ + # Updates videos checked out count and video available inventory + # :params: + # - n: number of videos to check in + # """ + # self.num_duplicates_video -= n + # self.video.calculate_available_inventory() - @classmethod - def from_dict(cls, rental_data): - new_rental = Rental( - due_date=rental_data["due_date"], - videos_checked_out=rental_data["videos_checked_out"], - ) - return new_rental \ No newline at end of file + \ No newline at end of file diff --git a/app/models/video.py b/app/models/video.py index 107297809..78a29b328 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -1,11 +1,14 @@ from app import db +def mydefault(context): + return context.get_current_parameters()['total_inventory'] + class Video(db.Model): 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()) - available_inventory = db.Column(db.Integer()) + available_inventory = db.Column(db.Integer(),default=mydefault) rentals = db.relationship("Rental", back_populates="video") def to_dict(self): @@ -14,7 +17,7 @@ def to_dict(self): video_as_dict["title"] = self.title video_as_dict["release_date"] = self.release_date video_as_dict["total_inventory"] = self.total_inventory - + video_as_dict["available_inventory"] = self.available_inventory return video_as_dict def calculate_available_inventory(self): @@ -28,6 +31,8 @@ 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"], + total_inventory=video_data["total_inventory"] ) return new_video + + From 4259c63581d1896982651357fbb7f0ac6cf72de2 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sat, 7 Jan 2023 21:18:32 -0800 Subject: [PATCH 35/56] Wave 2 video checkout tests are passing with these changes. --- app/models/customer.py | 2 +- app/models/rental.py | 4 ++-- app/routes/rentals_routes.py | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index a8e0f15f3..01e5796a7 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -8,7 +8,7 @@ class Customer(db.Model): phone = db.Column(db.String, nullable=False) registered_at = db.Column(db.DateTime, default=datetime.now()) rentals = db.relationship("Rental", back_populates="customer") - videos_checked_out_count = db.Column(db.Integer) + videos_checked_out_count = db.Column(db.Integer, default=0) def to_dict(self): """ diff --git a/app/models/rental.py b/app/models/rental.py index 3c485714e..4cdeb1ce0 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -27,8 +27,8 @@ def to_dict(self): @classmethod def from_dict(cls, rental_data): new_rental = Rental( - due_date=rental_data["due_date"], - status=rental_data["status"] + customer_id=rental_data["customer_id"], + video_id=rental_data["video_id"] ) return new_rental diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py index b33a0dd8f..a09f14ac4 100644 --- a/app/routes/rentals_routes.py +++ b/app/routes/rentals_routes.py @@ -21,10 +21,14 @@ def checkout_video(): customer = validate_model(Customer, customer_id) if video and customer: - new_rental = Rental(video_id=request_body["video_id"],customer_id=request_body["customer_id"]) + if video.available_inventory<=0: + return make_response(jsonify({"message": f"Could not perform checkout"}), 400) + new_rental = Rental.from_dict(request_body) # update customer.videos_checked_out_count - new_rental.customer.video_checked_out_count += 1 + new_rental.customer = customer + new_rental.customer.videos_checked_out_count += 1 # update video.available_inventory + new_rental.video = video new_rental.video.available_inventory -= 1 db.session.add(new_rental) db.session.commit() From 4f60d9989f730bc8e5dbcc9f160d3cfa4c02cbac Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sat, 7 Jan 2023 23:58:44 -0800 Subject: [PATCH 36/56] updated rental check_in to add more checks. --- app/routes/rentals_routes.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py index a09f14ac4..4f8baf46a 100644 --- a/app/routes/rentals_routes.py +++ b/app/routes/rentals_routes.py @@ -23,13 +23,15 @@ def checkout_video(): if video and customer: if video.available_inventory<=0: return make_response(jsonify({"message": f"Could not perform checkout"}), 400) + new_rental = Rental.from_dict(request_body) - # update customer.videos_checked_out_count + new_rental.customer = customer new_rental.customer.videos_checked_out_count += 1 - # update video.available_inventory + new_rental.video = video new_rental.video.available_inventory -= 1 + db.session.add(new_rental) db.session.commit() @@ -49,16 +51,18 @@ def checkin_video(): customer = validate_model(Customer, customer_id) if video and customer: - # QUERY for rental by video_id and customer _id - rental = Rental.query.filter(video_id=video_id, customer_id=customer_id) - # if found check it in -> - # change status to checked_in + rental = Rental.query.filter_by(video_id=video_id, customer_id=customer_id).first() + if not rental: + return make_response(jsonify({"message": f"No outstanding rentals for customer {customer_id} and video {video_id}"}), 400) + + if rental.status == "checked_in": + return make_response(jsonify({"message": f"Cannot check_in video already checked_in"}), 400) + rental.status = "checked_in" - # update customer.videos_checked_out_count rental.customer.videos_checked_out_count -= 1 - # update video.available_inventory rental.video.available_inventory += 1 - rental.video.update + + db.session.commit() return make_response(jsonify(rental.to_dict()), 200) else: # if not found error out From df45839dc1be19367e7661a06e57d8e26920c4f7 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sun, 8 Jan 2023 00:28:47 -0800 Subject: [PATCH 37/56] added getting rentals by video_id. --- app/routes/video_routes.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 7b8934ce7..5bc033a06 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -65,4 +65,19 @@ def delete_video_by_id(video_id): return make_response({ "id" : video.id, "message": f"Video #{video_id} successfully deleted" - }, 200) \ No newline at end of file + }, 200) + +@video_bp.route("/rentals", methods=["GET"]) +def get_rentals_by_video_id(video_id): + + video = validate_model(Video, video_id) + rentals_response = [] + for rental in video.rentals: + rentals_response.append({ + "due_date": rental.due_date, + "name": rental.customer.name, + "phone": rental.customer.phone, + "postal_code": rental.customer.postal_code + }) + + return jsonify(rentals_response) \ No newline at end of file From 5664ef5cbea204f9be857c74eee8f8237f7e0da7 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sun, 8 Jan 2023 00:29:13 -0800 Subject: [PATCH 38/56] Made some fixes so Wave 2 all tests are passing. --- app/routes/customer_routes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 6524d458a..68894d615 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -101,5 +101,9 @@ def display_customer_rentals(customer_id): customer = validate_model(Customer, customer_id) rentals_response = [] for rental in customer.rentals: - rentals_response.append(rental.to_dict()) - return make_response(jsonify(rentals_response), 200) + rentals_response.append({ + "release_date": rental.video.release_date, + "title": rental.video.title, + "due_date": rental.due_date + }) + return make_response(jsonify(rentals_response), 200) \ No newline at end of file From b76799b436124b7af248a0290f5eb3b994e4b8a6 Mon Sep 17 00:00:00 2001 From: larissa Date: Sun, 8 Jan 2023 01:54:42 -0800 Subject: [PATCH 39/56] modified id to customer_id in response and included videos_checked_out_count in response dict to pass tests --- app/models/customer.py | 5 +++-- app/models/rental.py | 2 +- app/models/video.py | 2 +- app/routes/video_routes.py | 13 +++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index d27bb0375..e6e2178a7 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -15,14 +15,15 @@ def to_dict(self): Returns dictionary of customer data. """ customer_dict = { - "id": self.id, + "customer_id": self.id, "name": self.name, "postal_code": self.postal_code, "phone": self.phone, "registered_at": self.registered_at, + "videos_checked_out_count": self.videos_checked_out_count, } return customer_dict - + def check_out_videos(self, n): self.n_rented_videos += n diff --git a/app/models/rental.py b/app/models/rental.py index f2449cdb0..fae894c2f 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -31,7 +31,7 @@ def from_dict(cls, rental_data): new_rental = Rental( customer_id=rental_data["customer_id"], video_id=rental_data["video_id"] - ) + ) return new_rental # def check_out_video(self, n): diff --git a/app/models/video.py b/app/models/video.py index 78a29b328..76b6de03b 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -13,7 +13,7 @@ class Video(db.Model): def to_dict(self): video_as_dict = {} - video_as_dict["id"] = self.id + video_as_dict["video_id"] = self.id video_as_dict["title"] = self.title video_as_dict["release_date"] = self.release_date video_as_dict["total_inventory"] = self.total_inventory diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index baf403e82..f6d8d2673 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -68,13 +68,14 @@ def delete_video_by_id(video_id): "message": f"Video #{video_id} successfully deleted" }, 200) -@video_bp.route("/rentals", methods=["GET"]) -def display_rentals_by_video_id(video_id): - video = validate_model(Video, video_id) - rentals = Rental.query.get(video_id) - return make_response( - jsonify(rentals.to_dict()), 200) +#@video_bp.route("/rentals", methods=["GET"]) +# def display_rentals_by_video_id(video_id): +# video = validate_model(Video, video_id) +# rentals = Rental.query.get(video_id) +# return make_response( +# jsonify(rentals.to_dict()), 200) +@video_bp.route("/rentals", methods=["GET"]) def get_rentals_by_video_id(video_id): video = validate_model(Video, video_id) From 0cf86e69a17a4cce75da0ce07c4ccf87280e190b Mon Sep 17 00:00:00 2001 From: larissa Date: Sun, 8 Jan 2023 13:15:46 -0800 Subject: [PATCH 40/56] corrected key error problem in wave 1 and wave 2 tests' --- app/models/customer.py | 2 +- app/models/rental.py | 8 ++++++-- app/models/video.py | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/models/customer.py b/app/models/customer.py index e6e2178a7..5eafe7934 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -15,7 +15,7 @@ def to_dict(self): Returns dictionary of customer data. """ customer_dict = { - "customer_id": self.id, + "id": self.id, "name": self.name, "postal_code": self.postal_code, "phone": self.phone, diff --git a/app/models/rental.py b/app/models/rental.py index fae894c2f..e065e4c84 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -21,9 +21,13 @@ def to_dict(self): "n_video_copies": self.n_video_copies, } if self.customer: - rental_dict.update(self.customer.to_dict()) + customer_dict = self.customer.to_dict() + customer_dict["customer_id"] = customer_dict.pop("id") + rental_dict.update(customer_dict) if self.video: - rental_dict.update(self.video.to_dict()) + video_dict = self.video.to_dict() + video_dict["video_id"] = video_dict.pop("id") + rental_dict.update(video_dict) return rental_dict @classmethod diff --git a/app/models/video.py b/app/models/video.py index 76b6de03b..a49c49883 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -12,12 +12,18 @@ class Video(db.Model): rentals = db.relationship("Rental", back_populates="video") def to_dict(self): - video_as_dict = {} - video_as_dict["video_id"] = self.id - video_as_dict["title"] = self.title - video_as_dict["release_date"] = self.release_date - video_as_dict["total_inventory"] = self.total_inventory - video_as_dict["available_inventory"] = self.available_inventory + video_as_dict = { + "id": self.id, + "title": self.title, + "release_date": self.release_date, + "total_inventory": self.total_inventory, + "available_inventory": self.available_inventory, + } + # video_as_dict["video_id"] = self.id + # video_as_dict["title"] = self.title + # video_as_dict["release_date"] = self.release_date + # video_as_dict["total_inventory"] = self.total_inventory + # video_as_dict["available_inventory"] = self.available_inventory return video_as_dict def calculate_available_inventory(self): From 5733651f3e8aa8e154dddcc988e83245d4d18e27 Mon Sep 17 00:00:00 2001 From: larissa Date: Sun, 8 Jan 2023 19:52:45 -0800 Subject: [PATCH 41/56] LA-wave-03-partial --- app/models/customer.py | 24 ++--- app/models/rental.py | 43 +++----- app/models/validation.py | 102 ++++++++++++++++++ app/models/video.py | 32 +++--- app/routes/customer_routes.py | 88 ++++++--------- app/routes/rentals_routes.py | 5 +- app/routes/video_routes.py | 51 ++++----- ...initialized_migrations_to_debug_error_.py} | 10 +- ...c_added_status_to_rental_and_available_.py | 30 ------ 9 files changed, 209 insertions(+), 176 deletions(-) create mode 100644 app/models/validation.py rename migrations/versions/{8b1b64498a3f_initialized_all_attributes_for_rental_.py => 1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py} (85%) delete mode 100644 migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py diff --git a/app/models/customer.py b/app/models/customer.py index 5eafe7934..28ea69119 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -29,22 +29,9 @@ def check_out_videos(self, n): def check_in_videos(self, n): self.n_rented_videos -= n - - # def update_attr(self, attr, val): - # """ - # Updates an attr given a *modifiable attr and value. - # * modifiable attrs: name, postal_code, phone - # """ - # if attr == "name" and val.isalpha(): - # self.name = val - # elif attr == "postal_code" and not val.isalpha(): - # self.postal_code = val - # elif attr == "phone" and not val.isalpha(): - # self.phone = val - - # return False - def create_from_dict(dict): + @classmethod + def create_from_dict(cls, dict): """ Creates customer instance from dict values. """ @@ -53,3 +40,10 @@ def create_from_dict(dict): postal_code=dict["postal_code"], phone=dict["phone"] ) + + @classmethod + def get_all_attrs(cls): + """ + Returns list of attributes for Customer class + """ + return ["name", "postal_code", "phone", "registered_at"] \ No newline at end of file diff --git a/app/models/rental.py b/app/models/rental.py index e065e4c84..a5c53a401 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -1,5 +1,7 @@ from app import db import datetime as dt +from .video import Video +from .customer import Customer class Rental(db.Model): __tablename__ = "rentals" @@ -14,11 +16,12 @@ class Rental(db.Model): def to_dict(self): """ - Returns dictionary of rental data and customer data if customer + Returns dictionary of rental data and *customer/video data if exists """ rental_dict = { - "due_date": self.due_date, - "n_video_copies": self.n_video_copies, + "id": self.id, + "due_date": self.due_date, + "n_video_copies": self.n_video_copies, } if self.customer: customer_dict = self.customer.to_dict() @@ -38,31 +41,9 @@ def from_dict(cls, rental_data): ) return new_rental - # def check_out_video(self, n): - # """ - # Updates video available inventory when customer checks out video(s) - # """ - # # decrement the video inventory - # self.video.calculate_available_inventory() - # # increment the customer's number of rented videos - # self.customer.check_out_videos(n) - - # def check_in_video(self): - # """ - # Updates video available inventory when customer checks out video(s) - # """ - # # decrement the video inventory - # self.video.calculate_available_inventory() - # # increment the customer's number of rented videos - # self.customer.check_out_videos() - - # def check_in_video(self, n): - # """ - # Updates videos checked out count and video available inventory - # :params: - # - n: number of videos to check in - # """ - # self.num_duplicates_video -= n - # self.video.calculate_available_inventory() - - \ No newline at end of file + @classmethod + def get_all_attrs(cls): + """ + Returns list of attributes for Rental class + """ + return ["name", "postal_code", "registered_at", "title", "release_date"] \ No newline at end of file diff --git a/app/models/validation.py b/app/models/validation.py new file mode 100644 index 000000000..466cb9254 --- /dev/null +++ b/app/models/validation.py @@ -0,0 +1,102 @@ +from flask import make_response, abort, request +from app.models.rental import Rental + + +# ~~~~~~ validation checkers and processing functions ~~~~~~ +def validate_model(cls, model_id): + """ + Checks if model id is correct dtype (int) and if exists in db. + :params: + - model_id + :returns: + - response_msg (dict), status_code (int) + """ + # check if model id is integer dtype + try: + model_id = int(model_id) + except: + abort(make_response({"message": f"{model_id} should be an integer dtype"}, 400)) + # fetch model by id + model = cls.query.get(model_id) + if not model: + return abort(make_response({"message": f"{cls.__name__} {model_id} was not found"}, 404)) + else: + return model + +def validate_request(request, reqs): + """ + Validates that http requests satisfy all requirements for PUT methods + :params: + - request (from client) + :returns: + - request_body (if requirements met) + """ + + # collect request + request_body = request.get_json() + # check if all requirements in request + set_request_keys = set(request_body.keys()) + set_reqs= set(reqs) + if not set_request_keys == set_reqs: + missing_key = "".join(set_reqs-set_request_keys) + return abort(make_response({ + "details": f"Request body must include {missing_key}." + }, 400)) + return request_body + +def validate_and_process_query_params(cls, queries): + """ + Parse query parameters from HTTP request and separate into separate dictionaries + based on SQLAlchemy query method + :params: + - cls (class) + - queries (dict) + :returns: + - sort (dict): query params for order_by sorting method (ascending) + - count (dict): selected number of results for limit method + - page_num (dict): page + """ + attrs = cls.get_all_attrs() + sort = {} + count = {} + page_num = {} + for kwarg in queries: + # check for sort method query param + if kwarg == "sort": + # if sort string is not a model attribute + if queries[kwarg] not in attrs: + abort(make_response({ + "message": f"{queries[kwarg]} is not a valid attribute to sort by" + }, 400)) + else: + # add sort string to sort kwarg dict + sort[kwarg] = queries[kwarg] + # check for limit method query param + elif kwarg == "count": + # add count to count kwarg dict + count[kwarg] = queries[kwarg] + # check for page count method query param + elif kwarg == "page_num": + page_num[kwarg] == queries[kwarg] + else: + # query param is not a valid query method + abort(make_response( + {"message" : f"{kwarg} is an invalid query"}, 400 + )) + return sort, count, page_num + +def create_model_query(models_query, cls, sort, count, page_num): + pass + # if sort: + # # sort asc by given attribute e.g. sort=name + # clause = getattr(cls, sort["sort"]) + # model_query = models_query.order_by(clause.asc()) + # # else: + # # models = models.order_by(cls.id.asc()) + # if count: + # # limit selection of customers to view + # models_query = models_query.limit(count["count"]) + # if page_num: + # # check documentation for this!!! + # pass + # return models_query \ No newline at end of file diff --git a/app/models/video.py b/app/models/video.py index a49c49883..ad86823fd 100644 --- a/app/models/video.py +++ b/app/models/video.py @@ -12,19 +12,17 @@ class Video(db.Model): rentals = db.relationship("Rental", back_populates="video") def to_dict(self): - video_as_dict = { - "id": self.id, - "title": self.title, - "release_date": self.release_date, - "total_inventory": self.total_inventory, - "available_inventory": self.available_inventory, - } - # video_as_dict["video_id"] = self.id - # video_as_dict["title"] = self.title - # video_as_dict["release_date"] = self.release_date - # video_as_dict["total_inventory"] = self.total_inventory - # video_as_dict["available_inventory"] = self.available_inventory - return video_as_dict + """ + Returns dictionary of video data + """ + video_dict = { + "id": self.id, + "title": self.title, + "release_date": self.release_date, + "total_inventory": self.total_inventory, + "available_inventory": self.available_inventory, + } + return video_dict def calculate_available_inventory(self): """ @@ -40,5 +38,11 @@ def from_dict(cls, video_data): total_inventory=video_data["total_inventory"] ) return new_video - + + @classmethod + def get_all_attrs(cls): + """ + Returns list of attributes for Video class + """ + return ["title", "release_date"] diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 68894d615..c7034b27b 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -1,59 +1,32 @@ from app import db -from flask import Blueprint, jsonify, make_response, abort, request +from flask import Blueprint, jsonify, make_response, request from ..models.customer import Customer - - -# ~~~~~~ validation checkers ~~~~~~ -def validate_model(cls, model_id): - """ - Checks if model id is correct dtype (int) and if exists in db. - :params: - - model_id - :returns: - - response_msg (dict), status_code (int) - """ - # check if model id is integer dtype - try: - model_id = int(model_id) - except: - abort(make_response({"message": f"{model_id} should be an integer dtype"}, 400)) - # fetch model by id - model = cls.query.get(model_id) - if not model: - return abort(make_response({"message": f"{cls.__name__} {model_id} was not found"}, 404)) - else: - return model - -def validate_request(request, reqs): - """ - Validates that http requests satisfy all requirements for PUT methods - :params: - - request (from client) - :returns: - - request_body (if requirements met) - """ - - # collect request - request_body = request.get_json() - # check if all requirements in request - set_request_keys = set(request_body.keys()) - set_reqs= set(reqs) - if not set_request_keys == set_reqs: - missing_key = "".join(set_reqs-set_request_keys) - return abort(make_response({ - "details": f"Request body must include {missing_key}." - }, 400)) - return request_body +from ..models.video import Video +from ..models.rental import Rental +from app.models.validation import validate_model, validate_request, validate_and_process_query_params, create_model_query # ~~~~~~ initialize customers blueprint ~~~~~~ customers_bp = Blueprint("customers", __name__, url_prefix="/customers") # ~~~~~~ customers endpoints ~~~~~~ @customers_bp.route("", methods=["GET"]) -def display_all_customers(): - # query all customers - customers = Customer.query.all() - # initialize response list +def display_all_customers(): + request_query = request.args.to_dict() + # collect & process query params from http request + sort, count, page_num = validate_and_process_query_params(Customer, request_query) + # collect customers + customer_query = Customer.query + # default is sorted by ascending customer id + customers = customer_query.order_by(Customer.id.asc()) + # check for additional query params + if sort: + # sort asc by given attribute e.g. sort=name + clause = getattr(Customer, sort["sort"]) + customers = customer_query.order_by(clause.asc()) + if count: + # limit selection of customers to view + customers = customer_query.limit(count["count"]) + # fill http response list response = [] for customer in customers: response.append(customer.to_dict()) @@ -99,11 +72,18 @@ def delete_a_customer(customer_id): @customers_bp.route("//rentals", methods=["GET"]) def display_customer_rentals(customer_id): customer = validate_model(Customer, customer_id) + # collect & process query params from http request + request_query = request.args.to_dict() + sort, count, page_num = validate_and_process_query_params(Rental, request_query) + # collect rentals by customer id + rental_query = Rental.query.filter_by(customer_id = customer.id) + # default is sorted by asc rental id + rentals = rental_query.order_by(Rental.id.asc()) + if count: + # limit selection of customers to view + rentals = rental_query.limit(count["count"]) + # fill http response list rentals_response = [] - for rental in customer.rentals: - rentals_response.append({ - "release_date": rental.video.release_date, - "title": rental.video.title, - "due_date": rental.due_date - }) + for rental in rentals: + rentals_response.append(rental.to_dict()) return make_response(jsonify(rentals_response), 200) \ No newline at end of file diff --git a/app/routes/rentals_routes.py b/app/routes/rentals_routes.py index 4f8baf46a..a2baf3c47 100644 --- a/app/routes/rentals_routes.py +++ b/app/routes/rentals_routes.py @@ -1,10 +1,9 @@ from app import db -from flask import Blueprint, jsonify, make_response, abort, request +from flask import Blueprint, jsonify, make_response, request from ..models.rental import Rental from ..models.customer import Customer from ..models.video import Video -from ..routes.customer_routes import validate_model -from ..routes.customer_routes import validate_request +from app.models.validation import validate_model, validate_request # ~~~~~~ initialize rentals blueprint ~~~~~~ rentals_bp = Blueprint("rentals", __name__, url_prefix="/rentals") diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index f6d8d2673..a78062092 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,24 +1,24 @@ from app import db from app.models.video import Video from app.models.rental import Rental -from app.routes.customer_routes import validate_model -from app.routes.customer_routes import validate_request -from flask import Blueprint, jsonify, abort, make_response, request +from app.models.validation import validate_model, validate_request, validate_and_process_query_params, create_model_query +from flask import Blueprint, jsonify, make_response, request video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") @video_bp.route("", methods=["GET"]) def get_all_videos(): - + request_query = request.args.to_dict() + # collect & process query params from http request + sort, count, page_num = validate_and_process_query_params(Video, request_query) + # create query + videos = Video.query + videos = create_model_query(videos, Video, sort, count, page_num) + # fill http response list videos_response = [] - videos = Video.query.all() - if not videos: - make_response(jsonify(videos_response), 200) - for video in videos: videos_response.append(video.to_dict()) - - return jsonify(videos_response) + return make_response(jsonify(videos_response), 200) @video_bp.route("", methods=["GET"]) def get_video_by_id(video_id): @@ -68,25 +68,26 @@ def delete_video_by_id(video_id): "message": f"Video #{video_id} successfully deleted" }, 200) -#@video_bp.route("/rentals", methods=["GET"]) -# def display_rentals_by_video_id(video_id): -# video = validate_model(Video, video_id) -# rentals = Rental.query.get(video_id) -# return make_response( -# jsonify(rentals.to_dict()), 200) - @video_bp.route("/rentals", methods=["GET"]) def get_rentals_by_video_id(video_id): video = validate_model(Video, video_id) + # collect & process query params from http request + queries = request.args.to_dict() + sort, count, page_num = validate_and_process_query_params(Rental, queries) + # collect rentals using query params + rentals = create_model_query(video, sort, count, page_num) rentals_response = [] for rental in video.rentals: - rentals_response.append({ - "due_date": rental.due_date, - "name": rental.customer.name, - "phone": rental.customer.phone, - "postal_code": rental.customer.postal_code - }) - - return jsonify(rentals_response) + rentals_response.append(rental.to_dict()) + print(rental.to_dict()) + return make_response(jsonify(rentals_response), 200) + + + # request_query = request.args.to_dict() + # # collect & process query params from http request + # sort, count, page_num = validate_and_process_query_params(Video, request_query) + # # create query + # videos = Video.query + # videos = create_model_query(videos, Video, sort, count, page_num) diff --git a/migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py b/migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py similarity index 85% rename from migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py rename to migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py index 0ec59d288..17b885629 100644 --- a/migrations/versions/8b1b64498a3f_initialized_all_attributes_for_rental_.py +++ b/migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py @@ -1,8 +1,8 @@ -"""Initialized all attributes for Rental, Customer and Video +"""Reinitialized migrations to debug error in finding customer.rentals in db -Revision ID: 8b1b64498a3f +Revision ID: 1ad1bc43f5f1 Revises: -Create Date: 2023-01-06 19:32:08.988351 +Create Date: 2023-01-08 15:42:05.330387 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '8b1b64498a3f' +revision = '1ad1bc43f5f1' down_revision = None branch_labels = None depends_on = None @@ -32,6 +32,7 @@ def upgrade(): sa.Column('title', sa.String(), nullable=True), sa.Column('release_date', sa.Date(), nullable=True), sa.Column('total_inventory', sa.Integer(), nullable=True), + sa.Column('available_inventory', sa.Integer(), nullable=True), sa.PrimaryKeyConstraint('id') ) op.create_table('rentals', @@ -39,6 +40,7 @@ def upgrade(): sa.Column('customer_id', sa.Integer(), nullable=True), sa.Column('video_id', sa.Integer(), nullable=True), sa.Column('due_date', sa.DateTime(), nullable=True), + sa.Column('status', sa.String(), nullable=True), sa.Column('n_video_copies', sa.Integer(), nullable=True), sa.ForeignKeyConstraint(['customer_id'], ['customer.id'], ), sa.ForeignKeyConstraint(['video_id'], ['video.id'], ), diff --git a/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py b/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py deleted file mode 100644 index 6685a3d70..000000000 --- a/migrations/versions/7f3a3cc43a5c_added_status_to_rental_and_available_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""added status to rental and available_inventory to video models. - -Revision ID: 7f3a3cc43a5c -Revises: 8b1b64498a3f -Create Date: 2023-01-07 16:24:54.863815 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7f3a3cc43a5c' -down_revision = '8b1b64498a3f' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('rentals', sa.Column('status', sa.String(), nullable=True)) - op.add_column('video', sa.Column('available_inventory', sa.Integer(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('video', 'available_inventory') - op.drop_column('rentals', 'status') - # ### end Alembic commands ### From 643624ef32872ef8368f19f92564d35047daf4c9 Mon Sep 17 00:00:00 2001 From: larissa Date: Sun, 8 Jan 2023 19:57:24 -0800 Subject: [PATCH 42/56] Fixed typo --- app/models/validation.py | 27 +++++++++++++-------------- app/routes/video_routes.py | 23 ++++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/models/validation.py b/app/models/validation.py index 466cb9254..4d8d2ecf4 100644 --- a/app/models/validation.py +++ b/app/models/validation.py @@ -86,17 +86,16 @@ def validate_and_process_query_params(cls, queries): return sort, count, page_num def create_model_query(models_query, cls, sort, count, page_num): - pass - # if sort: - # # sort asc by given attribute e.g. sort=name - # clause = getattr(cls, sort["sort"]) - # model_query = models_query.order_by(clause.asc()) - # # else: - # # models = models.order_by(cls.id.asc()) - # if count: - # # limit selection of customers to view - # models_query = models_query.limit(count["count"]) - # if page_num: - # # check documentation for this!!! - # pass - # return models_query \ No newline at end of file + if sort: + # sort asc by given attribute e.g. sort=name + clause = getattr(cls, sort["sort"]) + model_query = models_query.order_by(clause.asc()) + # else: + # models = models.order_by(cls.id.asc()) + if count: + # limit selection of customers to view + models_query = models_query.limit(count["count"]) + if page_num: + # check documentation for this!!! + pass + return models_query \ No newline at end of file diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index a78062092..2c62d11c5 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -73,21 +73,22 @@ def get_rentals_by_video_id(video_id): video = validate_model(Video, video_id) # collect & process query params from http request - queries = request.args.to_dict() - sort, count, page_num = validate_and_process_query_params(Rental, queries) + #queries = request.args.to_dict() + #sort, count, page_num = validate_and_process_query_params(Rental, queries) # collect rentals using query params - rentals = create_model_query(video, sort, count, page_num) + request_query = request.args.to_dict() + sort, count, page_num = validate_and_process_query_params(Rental, request_query) + # collect rentals by customer id + rental_query = Rental.query.filter_by(video_id = video.id) + # default is sorted by asc rental id + rentals = rental_query.order_by(Rental.id.asc()) + if count: + # limit selection of customers to view + rentals = rental_query.limit(count["count"]) + #rentals = create_model_query(video, Video, sort, count, page_num) rentals_response = [] for rental in video.rentals: rentals_response.append(rental.to_dict()) print(rental.to_dict()) return make_response(jsonify(rentals_response), 200) - - # request_query = request.args.to_dict() - # # collect & process query params from http request - # sort, count, page_num = validate_and_process_query_params(Video, request_query) - # # create query - # videos = Video.query - # videos = create_model_query(videos, Video, sort, count, page_num) - From 577623c05e42e4c046a65ba09cf09916aea9a74b Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sun, 8 Jan 2023 21:06:32 -0800 Subject: [PATCH 43/56] updated migrations. --- ...zed_migrations_to_debug_error_.py => aaec7f59811c_.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename migrations/versions/{1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py => aaec7f59811c_.py} (91%) diff --git a/migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py b/migrations/versions/aaec7f59811c_.py similarity index 91% rename from migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py rename to migrations/versions/aaec7f59811c_.py index 17b885629..9d046d9ca 100644 --- a/migrations/versions/1ad1bc43f5f1_reinitialized_migrations_to_debug_error_.py +++ b/migrations/versions/aaec7f59811c_.py @@ -1,8 +1,8 @@ -"""Reinitialized migrations to debug error in finding customer.rentals in db +"""empty message -Revision ID: 1ad1bc43f5f1 +Revision ID: aaec7f59811c Revises: -Create Date: 2023-01-08 15:42:05.330387 +Create Date: 2023-01-08 20:56:08.280510 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '1ad1bc43f5f1' +revision = 'aaec7f59811c' down_revision = None branch_labels = None depends_on = None From 3a03c906d6deafaa8aac98fe6f33a3136203de08 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sun, 8 Jan 2023 21:43:52 -0800 Subject: [PATCH 44/56] fixed issue where rentals was showing checked in videos also. --- app/models/rental.py | 1 + app/routes/customer_routes.py | 3 ++- app/routes/video_routes.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/rental.py b/app/models/rental.py index a5c53a401..9c8194324 100644 --- a/app/models/rental.py +++ b/app/models/rental.py @@ -22,6 +22,7 @@ def to_dict(self): "id": self.id, "due_date": self.due_date, "n_video_copies": self.n_video_copies, + "status": self.status } if self.customer: customer_dict = self.customer.to_dict() diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index c7034b27b..87fb9bc65 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -85,5 +85,6 @@ def display_customer_rentals(customer_id): # fill http response list rentals_response = [] for rental in rentals: - rentals_response.append(rental.to_dict()) + if rental.status == "checked_out": + rentals_response.append(rental.to_dict()) return make_response(jsonify(rentals_response), 200) \ No newline at end of file diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 2c62d11c5..5ad847170 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -88,7 +88,7 @@ def get_rentals_by_video_id(video_id): #rentals = create_model_query(video, Video, sort, count, page_num) rentals_response = [] for rental in video.rentals: - rentals_response.append(rental.to_dict()) - print(rental.to_dict()) + if rental.status == "checked_out": + rentals_response.append(rental.to_dict()) return make_response(jsonify(rentals_response), 200) From 82e861a6d5b1b9245bb7d144c15647be1056201d Mon Sep 17 00:00:00 2001 From: larissa Date: Sun, 8 Jan 2023 22:24:44 -0800 Subject: [PATCH 45/56] LA-sort-and-count-queries-working --- app/models/validation.py | 2 +- app/routes/customer_routes.py | 58 ++++++++++++++++++++++++++--------- app/routes/video_routes.py | 52 +++++++++++++++++++------------ 3 files changed, 78 insertions(+), 34 deletions(-) diff --git a/app/models/validation.py b/app/models/validation.py index 4d8d2ecf4..8d222c708 100644 --- a/app/models/validation.py +++ b/app/models/validation.py @@ -77,7 +77,7 @@ def validate_and_process_query_params(cls, queries): count[kwarg] = queries[kwarg] # check for page count method query param elif kwarg == "page_num": - page_num[kwarg] == queries[kwarg] + page_num[kwarg] = queries[kwarg] else: # query param is not a valid query method abort(make_response( diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index c7034b27b..e93c1b153 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -1,5 +1,5 @@ from app import db -from flask import Blueprint, jsonify, make_response, request +from flask import Blueprint, jsonify, make_response, request, abort from ..models.customer import Customer from ..models.video import Video from ..models.rental import Rental @@ -72,18 +72,48 @@ def delete_a_customer(customer_id): @customers_bp.route("//rentals", methods=["GET"]) def display_customer_rentals(customer_id): customer = validate_model(Customer, customer_id) - # collect & process query params from http request request_query = request.args.to_dict() sort, count, page_num = validate_and_process_query_params(Rental, request_query) - # collect rentals by customer id - rental_query = Rental.query.filter_by(customer_id = customer.id) - # default is sorted by asc rental id - rentals = rental_query.order_by(Rental.id.asc()) - if count: - # limit selection of customers to view - rentals = rental_query.limit(count["count"]) - # fill http response list - rentals_response = [] - for rental in rentals: - rentals_response.append(rental.to_dict()) - return make_response(jsonify(rentals_response), 200) \ No newline at end of file + + join_query = ( + db.session.query(Rental, Video) + .join(Video, Rental.video_id==Video.id) + .filter(Rental.customer_id == customer_id) + ) + + sort_params = ["title", "release_date"] + for param in sort_params: + if sort: + if sort["sort"] == param: + join_query = join_query.order_by(param) + if count: + try: + count["count"] = int(count["count"]) + except: + abort(make_response({"message": "count is not an integer"}, 400)) + + join_query = join_query.limit(count["count"]) + + ### PAGINATE NOT WORKING ####### + # if page_num: + # try: + # page_num["page_num"] = int(page_num["page_num"]) + # except: + # abort(make_response({"message": "page_num is not an integer"}, 400)) + # else: + # page_num["page_num"] = 1 + + # join_query = join_query.paginate(page=page_num["page_num"], per_page=count["count"]).items + else: + join_query = join_query + + response_body = [] + for row in join_query: + response_body.append({ + "id": row.Video.id, + "title": row.Video.title, + "total_inventory": row.Video.total_inventory, + "release_date": row.Video.release_date + }) + + return make_response(jsonify(response_body),200) \ No newline at end of file diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 2c62d11c5..4feec7a31 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -1,8 +1,9 @@ from app import db from app.models.video import Video from app.models.rental import Rental +from app.models.customer import Customer from app.models.validation import validate_model, validate_request, validate_and_process_query_params, create_model_query -from flask import Blueprint, jsonify, make_response, request +from flask import Blueprint, jsonify, make_response, request, abort video_bp = Blueprint("video_bp", __name__, url_prefix="/videos") @@ -70,25 +71,38 @@ def delete_video_by_id(video_id): @video_bp.route("/rentals", methods=["GET"]) def get_rentals_by_video_id(video_id): - video = validate_model(Video, video_id) - # collect & process query params from http request - #queries = request.args.to_dict() - #sort, count, page_num = validate_and_process_query_params(Rental, queries) # collect rentals using query params request_query = request.args.to_dict() - sort, count, page_num = validate_and_process_query_params(Rental, request_query) - # collect rentals by customer id - rental_query = Rental.query.filter_by(video_id = video.id) - # default is sorted by asc rental id - rentals = rental_query.order_by(Rental.id.asc()) - if count: - # limit selection of customers to view - rentals = rental_query.limit(count["count"]) - #rentals = create_model_query(video, Video, sort, count, page_num) - rentals_response = [] - for rental in video.rentals: - rentals_response.append(rental.to_dict()) - print(rental.to_dict()) - return make_response(jsonify(rentals_response), 200) + sort, count, page_num = validate_and_process_query_params(Customer, request_query) + + join_query = ( + db.session.query(Rental, Customer) + .join(Customer, Rental.customer_id==Customer.id) + .filter(Rental.video_id == video_id) + ) + sort_params=["name", "registered_at", "postal_code"] + for param in sort_params: + if sort: + if sort["sort"] == param: + join_query = join_query.order_by(param) + if count: + try: + count["count"] = int(count["count"]) + except: + abort(make_response({"message": "count is not an integer"}, 400)) + + join_query = join_query.limit(count["count"]) + else: + join_query = join_query + ### PAGINATE QUERY MISSING ###### + response_body = [] + for row in join_query: + response_body.append({ + "id": row.Customer.id, + "title": row.Customer.name, + "total_inventory": row.Customer.registered_at, + "release_date": row.Customer.postal_code, + }) + return make_response(jsonify(response_body),200) From c0e359fdc6f9b9f4c871bce5ee106f420e63569a Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Sun, 8 Jan 2023 22:36:10 -0800 Subject: [PATCH 46/56] fixes after merge. --- app/routes/video_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 4feec7a31..16af71323 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -100,7 +100,7 @@ def get_rentals_by_video_id(video_id): for row in join_query: response_body.append({ "id": row.Customer.id, - "title": row.Customer.name, + "name": row.Customer.name, "total_inventory": row.Customer.registered_at, "release_date": row.Customer.postal_code, }) From 719c1059776c3c714f0f093a53ae68138acaec72 Mon Sep 17 00:00:00 2001 From: larissa Date: Mon, 9 Jan 2023 00:07:23 -0800 Subject: [PATCH 47/56] LA-pagination-feature-working --- app/models/validation.py | 16 ++-------- app/routes/customer_routes.py | 48 ++++++++++------------------ app/routes/video_routes.py | 60 ++++++++++++++++++++--------------- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/app/models/validation.py b/app/models/validation.py index 8d222c708..fc008392b 100644 --- a/app/models/validation.py +++ b/app/models/validation.py @@ -64,25 +64,15 @@ def validate_and_process_query_params(cls, queries): # check for sort method query param if kwarg == "sort": # if sort string is not a model attribute - if queries[kwarg] not in attrs: - abort(make_response({ - "message": f"{queries[kwarg]} is not a valid attribute to sort by" - }, 400)) - else: - # add sort string to sort kwarg dict + if queries[kwarg] in attrs: sort[kwarg] = queries[kwarg] # check for limit method query param - elif kwarg == "count": + if kwarg == "count": # add count to count kwarg dict count[kwarg] = queries[kwarg] # check for page count method query param - elif kwarg == "page_num": + if kwarg == "page_num": page_num[kwarg] = queries[kwarg] - else: - # query param is not a valid query method - abort(make_response( - {"message" : f"{kwarg} is an invalid query"}, 400 - )) return sort, count, page_num def create_model_query(models_query, cls, sort, count, page_num): diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index e93c1b153..9f5021bf0 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -16,16 +16,19 @@ def display_all_customers(): sort, count, page_num = validate_and_process_query_params(Customer, request_query) # collect customers customer_query = Customer.query - # default is sorted by ascending customer id - customers = customer_query.order_by(Customer.id.asc()) # check for additional query params if sort: # sort asc by given attribute e.g. sort=name - clause = getattr(Customer, sort["sort"]) + clause = getattr(Customer, sort["sort"]) customers = customer_query.order_by(clause.asc()) - if count: + else: + # default is sorted by ascending customer id + customers = customer_query.order_by(Customer.id.asc()) + if count and not page_num: # limit selection of customers to view customers = customer_query.limit(count["count"]) + if page_num: + customers = customer_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items # fill http response list response = [] for customer in customers: @@ -73,39 +76,22 @@ def delete_a_customer(customer_id): def display_customer_rentals(customer_id): customer = validate_model(Customer, customer_id) request_query = request.args.to_dict() - sort, count, page_num = validate_and_process_query_params(Rental, request_query) + sort, count, page_num = validate_and_process_query_params(Video, request_query) join_query = ( db.session.query(Rental, Video) .join(Video, Rental.video_id==Video.id) .filter(Rental.customer_id == customer_id) ) - - sort_params = ["title", "release_date"] - for param in sort_params: - if sort: - if sort["sort"] == param: - join_query = join_query.order_by(param) - if count: - try: - count["count"] = int(count["count"]) - except: - abort(make_response({"message": "count is not an integer"}, 400)) - - join_query = join_query.limit(count["count"]) - - ### PAGINATE NOT WORKING ####### - # if page_num: - # try: - # page_num["page_num"] = int(page_num["page_num"]) - # except: - # abort(make_response({"message": "page_num is not an integer"}, 400)) - # else: - # page_num["page_num"] = 1 - - # join_query = join_query.paginate(page=page_num["page_num"], per_page=count["count"]).items - else: - join_query = join_query + if sort: + join_query = join_query.order_by(sort["sort"]) + else: + # default sort is ascending rental id + join_query = join_query.order_by(Rental.id.asc()) + if count and not page_num: + join_query = join_query.limit(count["count"]) + if page_num: + join_query = join_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items response_body = [] for row in join_query: diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 4feec7a31..fa65644f9 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -12,9 +12,21 @@ def get_all_videos(): request_query = request.args.to_dict() # collect & process query params from http request sort, count, page_num = validate_and_process_query_params(Video, request_query) - # create query - videos = Video.query - videos = create_model_query(videos, Video, sort, count, page_num) + video_query = Video.query + # check for additional query params + if sort: + # sort asc by given attribute e.g. sort=name + clause = getattr(Video, sort["sort"]) + videos = video_query.order_by(clause.asc()) + else: + # default is sorted by ascending customer id + videos = video_query.order_by(Video.id.asc()) + if count and not page_num: + # limit selection of customers to view + videos = video_query.limit(count["count"]) + if page_num: + videos = video_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + # fill http response list videos_response = [] for video in videos: @@ -74,35 +86,31 @@ def get_rentals_by_video_id(video_id): video = validate_model(Video, video_id) # collect rentals using query params request_query = request.args.to_dict() - sort, count, page_num = validate_and_process_query_params(Customer, request_query) + sort, count, page_num = validate_and_process_query_params(Rental, request_query) join_query = ( - db.session.query(Rental, Customer) - .join(Customer, Rental.customer_id==Customer.id) - .filter(Rental.video_id == video_id) + db.session.query(Rental, Video) + .join(Video, Rental.video_id==Video.id) + .filter(Rental.customer_id == Customer.id) ) - sort_params=["name", "registered_at", "postal_code"] - for param in sort_params: - if sort: - if sort["sort"] == param: - join_query = join_query.order_by(param) - if count: - try: - count["count"] = int(count["count"]) - except: - abort(make_response({"message": "count is not an integer"}, 400)) - - join_query = join_query.limit(count["count"]) - else: - join_query = join_query - ### PAGINATE QUERY MISSING ###### + if sort: + join_query = join_query.order_by(sort["sort"]) + else: + # default sort is ascending rental id + join_query = join_query.order_by(Rental.id.asc()) + if count and not page_num: + join_query = join_query.limit(count["count"]) + if page_num: + join_query = join_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + response_body = [] for row in join_query: response_body.append({ - "id": row.Customer.id, - "title": row.Customer.name, - "total_inventory": row.Customer.registered_at, - "release_date": row.Customer.postal_code, + "video_id": row.Video.id, + "rental_id": row.Rental.id, + "title": row.Video.title, + "total_inventory": row.Video.total_inventory, + "release_date": row.Video.release_date, }) return make_response(jsonify(response_body),200) From 353fb08e59468f34f3f78d4c041aa7daaeda662a Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 01:39:36 -0800 Subject: [PATCH 48/56] bug fixing for merges. --- app/routes/video_routes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 0ceacb06f..55f8ca8e3 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -93,6 +93,7 @@ def get_rentals_by_video_id(video_id): .join(Video, Rental.video_id==Video.id) .filter(Rental.customer_id == Customer.id) ) + if sort: join_query = join_query.order_by(sort["sort"]) else: @@ -106,10 +107,10 @@ def get_rentals_by_video_id(video_id): response_body = [] for row in join_query: response_body.append({ - "id": row.Customer.id, - "title": row.Customer.name, - "total_inventory": row.Customer.registered_at, - "release_date": row.Customer.postal_code, + "due_date": row.Rental.due_date, + "name": row.Rental.customer.name, + "phone": row.Rental.customer.phone, + "postal_code": row.Rental.customer.postal_code, }) return make_response(jsonify(response_body),200) From 496aa80fad4e37f9ddcad8396bfb745c2b2814f4 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 03:12:22 -0800 Subject: [PATCH 49/56] added customer id to response in get rentals. --- app/routes/video_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 55f8ca8e3..4a426e08f 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -109,6 +109,7 @@ def get_rentals_by_video_id(video_id): response_body.append({ "due_date": row.Rental.due_date, "name": row.Rental.customer.name, + "id":row.Rental.customer.id, "phone": row.Rental.customer.phone, "postal_code": row.Rental.customer.postal_code, }) From 27e5bb5815291fcf62d34b72d4fe7c02e2879b49 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 03:13:07 -0800 Subject: [PATCH 50/56] comment out optional Wav4 tests. --- tests/test_wave_03.py | 58 +++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index a127b6677..b903d1aa7 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -747,32 +747,32 @@ def test_get_renters_invalid_p_param(client, customer_one_video_three, customer_ -def test_get_customers_rental_history(client, one_checked_out_video, one_returned_video): - # Act - response = client.get("/customers/1/history") - response_body = response.get_json() - - #Assert - assert response.status_code == 200 - assert len(response_body) == 1 - assert response_body[0]["title"] == VIDEO_2_TITLE - -def test_get_customer_not_found_rental_history(client, one_checked_out_video, one_returned_video): - # Act - response = client.get("/customers/2/history") - response_body = response.get_json() - - #Assert - assert response.status_code == 404 - assert response_body == {"message": "Customer 2 was not found"} - - -def test_get_customer_no_rental_history(client, one_checked_out_video): - # Act - response = client.get("/customers/1/history") - response_body = response.get_json() - - #Assert - assert response.status_code == 200 - assert len(response_body) == 0 - assert response_body == [] \ No newline at end of file +# def test_get_customers_rental_history(client, one_checked_out_video, one_returned_video): +# # Act +# response = client.get("/customers/1/history") +# response_body = response.get_json() + +# #Assert +# assert response.status_code == 200 +# assert len(response_body) == 1 +# assert response_body[0]["title"] == VIDEO_2_TITLE + +# def test_get_customer_not_found_rental_history(client, one_checked_out_video, one_returned_video): +# # Act +# response = client.get("/customers/2/history") +# response_body = response.get_json() + +# #Assert +# assert response.status_code == 404 +# assert response_body == {"message": "Customer 2 was not found"} + + +# def test_get_customer_no_rental_history(client, one_checked_out_video): +# # Act +# response = client.get("/customers/1/history") +# response_body = response.get_json() + +# #Assert +# assert response.status_code == 200 +# assert len(response_body) == 0 +# assert response_body == [] \ No newline at end of file From 6dceb47fa5e586248e865b5d676bbc1982f3ec48 Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 03:36:51 -0800 Subject: [PATCH 51/56] validate request args count and page_num are int type. --- app/models/validation.py | 18 ++++++++++++------ app/routes/customer_routes.py | 12 ++++++------ app/routes/video_routes.py | 12 ++++++------ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/models/validation.py b/app/models/validation.py index fc008392b..77c67225b 100644 --- a/app/models/validation.py +++ b/app/models/validation.py @@ -57,22 +57,28 @@ def validate_and_process_query_params(cls, queries): - page_num (dict): page """ attrs = cls.get_all_attrs() - sort = {} - count = {} - page_num = {} + sort = None + count = None + page_num = None for kwarg in queries: # check for sort method query param if kwarg == "sort": # if sort string is not a model attribute if queries[kwarg] in attrs: - sort[kwarg] = queries[kwarg] + sort = queries[kwarg] # check for limit method query param if kwarg == "count": # add count to count kwarg dict - count[kwarg] = queries[kwarg] + try: + count = int(queries[kwarg]) + except ValueError: + pass # check for page count method query param if kwarg == "page_num": - page_num[kwarg] = queries[kwarg] + try: + page_num = int(queries[kwarg]) + except ValueError: + pass return sort, count, page_num def create_model_query(models_query, cls, sort, count, page_num): diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 9f5021bf0..a6aefbdbe 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -19,16 +19,16 @@ def display_all_customers(): # check for additional query params if sort: # sort asc by given attribute e.g. sort=name - clause = getattr(Customer, sort["sort"]) + clause = getattr(Customer, sort) customers = customer_query.order_by(clause.asc()) else: # default is sorted by ascending customer id customers = customer_query.order_by(Customer.id.asc()) if count and not page_num: # limit selection of customers to view - customers = customer_query.limit(count["count"]) + customers = customer_query.limit(count) if page_num: - customers = customer_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + customers = customer_query.paginate(page=int(page_num), per_page=int(count)).items # fill http response list response = [] for customer in customers: @@ -84,14 +84,14 @@ def display_customer_rentals(customer_id): .filter(Rental.customer_id == customer_id) ) if sort: - join_query = join_query.order_by(sort["sort"]) + join_query = join_query.order_by(sort) else: # default sort is ascending rental id join_query = join_query.order_by(Rental.id.asc()) if count and not page_num: - join_query = join_query.limit(count["count"]) + join_query = join_query.limit(count) if page_num: - join_query = join_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + join_query = join_query.paginate(page=int(page_num), per_page=int(count)).items response_body = [] for row in join_query: diff --git a/app/routes/video_routes.py b/app/routes/video_routes.py index 4a426e08f..e96c1cce6 100644 --- a/app/routes/video_routes.py +++ b/app/routes/video_routes.py @@ -16,16 +16,16 @@ def get_all_videos(): # check for additional query params if sort: # sort asc by given attribute e.g. sort=name - clause = getattr(Video, sort["sort"]) + clause = getattr(Video, sort) videos = video_query.order_by(clause.asc()) else: # default is sorted by ascending customer id videos = video_query.order_by(Video.id.asc()) if count and not page_num: # limit selection of customers to view - videos = video_query.limit(count["count"]) + videos = video_query.limit(count) if page_num: - videos = video_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + videos = video_query.paginate(page=int(page_num), per_page=int(count)).items # fill http response list videos_response = [] @@ -95,14 +95,14 @@ def get_rentals_by_video_id(video_id): ) if sort: - join_query = join_query.order_by(sort["sort"]) + join_query = join_query.order_by(sort) else: # default sort is ascending rental id join_query = join_query.order_by(Rental.id.asc()) if count and not page_num: - join_query = join_query.limit(count["count"]) + join_query = join_query.limit(count) if page_num: - join_query = join_query.paginate(page=int(page_num["page_num"]), per_page=int(count["count"])).items + join_query = join_query.paginate(page=int(page_num), per_page=int(count)).items response_body = [] for row in join_query: From 237305ff6ed936e741ec2ea6dc5cbdd120906d5b Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 04:39:58 -0800 Subject: [PATCH 52/56] bug fix for customer query. --- app/routes/customer_routes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index a6aefbdbe..0d96b8ed4 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -20,16 +20,17 @@ def display_all_customers(): if sort: # sort asc by given attribute e.g. sort=name clause = getattr(Customer, sort) - customers = customer_query.order_by(clause.asc()) + customer_query = customer_query.order_by(clause.asc()) else: # default is sorted by ascending customer id - customers = customer_query.order_by(Customer.id.asc()) + customer_query = customer_query.order_by(Customer.id.asc()) if count and not page_num: # limit selection of customers to view - customers = customer_query.limit(count) + customer_query = customer_query.limit(count) if page_num: - customers = customer_query.paginate(page=int(page_num), per_page=int(count)).items + customer_query = customer_query.paginate(page=int(page_num), per_page=int(count)).items # fill http response list + customers = customer_query.all() response = [] for customer in customers: response.append(customer.to_dict()) From d7477d7cff91109dc64001a63b073295a8fce63c Mon Sep 17 00:00:00 2001 From: Soumya Sah <85212732+smysh@users.noreply.github.com> Date: Mon, 9 Jan 2023 11:50:33 -0800 Subject: [PATCH 53/56] Co-authored-by: Larissa Ault --- app/routes/customer_routes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index 0d96b8ed4..bb6b55bed 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -24,15 +24,16 @@ def display_all_customers(): else: # default is sorted by ascending customer id customer_query = customer_query.order_by(Customer.id.asc()) - if count and not page_num: - # limit selection of customers to view - customer_query = customer_query.limit(count) - if page_num: - customer_query = customer_query.paginate(page=int(page_num), per_page=int(count)).items + + if count: + page=int(page_num) if page_num else 1 + customer_query = customer_query.paginate(page=page, per_page=int(count)).items + else: + customer_query = customer_query.all() # fill http response list - customers = customer_query.all() + response = [] - for customer in customers: + for customer in customer_query: response.append(customer.to_dict()) return jsonify(response) From ae23326162b128fd1086ad890ebbde5322a17aa0 Mon Sep 17 00:00:00 2001 From: larissa Date: Mon, 9 Jan 2023 14:07:05 -0800 Subject: [PATCH 54/56] Added requirements and Procfile for heroku deployment --- requirements.txt | 175 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 147 insertions(+), 28 deletions(-) diff --git a/requirements.txt b/requirements.txt index 89e00b497..88708cdd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,32 +1,151 @@ -alembic==1.5.4 -attrs==21.2.0 -autopep8==1.5.5 -blinker==1.4 -certifi==2020.12.5 -chardet==4.0.0 -click==7.1.2 -Flask==1.1.2 -Flask-Migrate==2.6.0 -Flask-SQLAlchemy==2.4.4 -idna==2.10 +adynform @ file:///Users/lollapalarza/adyn/adynform +alembic==1.8.1 +altgraph @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/altgraph-0.17.2-py2.py3-none-any.whl +analyzer==0.1.1 +analyzerdam==0.1.2 +analyzerstrategies==0.1.5 +anyio==3.6.2 +appnope==0.1.3 +argon2-cffi==21.3.0 +argon2-cffi-bindings==21.2.0 +ast2json==0.3 +asttokens==2.0.8 +async-generator==1.10 +attrs==22.1.0 +Babel==2.10.3 +backcall==0.2.0 +beautifulsoup4==4.11.1 +bleach==5.0.1 +bokeh==2.4.3 +certifi==2022.9.24 +cffi==1.15.1 +charset-normalizer==2.0.12 +click==8.1.3 +contourpy==1.0.6 +cycler==0.11.0 +databases==0.6.1 +debugpy==1.6.3 +decorator==5.1.1 +defusedxml==0.7.1 +distlib==0.3.6 +dominate==2.6.0 +entrypoints==0.4 +exceptiongroup==1.0.0rc9 +executing==1.1.1 +fastjsonschema==2.16.2 +filelock==3.8.0 +Flask==2.2.2 +Flask-Bootstrap==3.3.7.1 +Flask-SQLAlchemy==2.5.1 +Flask-WTF==1.0.1 +fonttools==4.38.0 +future @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/future-0.18.2-py3-none-any.whl +googlefinance==0.7 +greenlet==1.1.2 +gunicorn==20.1.0 +h11==0.14.0 +idna==3.4 +importlib-metadata==5.0.0 iniconfig==1.1.1 -itsdangerous==1.1.0 -Jinja2==2.11.3 -Mako==1.1.4 -MarkupSafe==1.1.1 -packaging==21.2 +ipdb==0.13.9 +ipykernel==6.17.1 +ipython==8.5.0 +ipython-genutils==0.2.0 +ipywidgets==8.0.2 +itsdangerous==2.1.2 +jedi==0.18.1 +Jinja2==3.1.2 +jsonschema==4.17.0 +jupyter==1.0.0 +jupyter-console==6.4.4 +jupyter-server==1.23.1 +jupyter_client==7.4.5 +jupyter_core==5.0.0 +jupyterlab-pygments==0.2.2 +jupyterlab-widgets==3.0.3 +kiwisolver==1.4.4 +logger==1.4 +macholib @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/macholib-1.15.2-py2.py3-none-any.whl +Mako==1.2.3 +MarkupSafe==2.1.1 +matplotlib==3.6.0 +matplotlib-inline==0.1.6 +mistune==2.0.4 +mox==0.5.3 +nbclassic==0.4.8 +nbclient==0.7.0 +nbconvert==7.2.4 +nbformat==5.7.0 +nest-asyncio==1.5.6 +notebook==6.5.2 +notebook_shim==0.2.2 +numpy==1.23.4 +outcome==1.2.0 +packaging==21.3 +pandas==1.5.1 +pandocfilters==1.5.0 +parso==0.8.3 +pbr==1.6.0 +pep8==1.7.1 +pexpect==4.8.0 +pickleshare==0.7.5 +Pillow==9.2.0 +platformdirs==2.5.3 pluggy==1.0.0 +prometheus-client==0.15.0 +prompt-toolkit==3.0.31 +psutil==5.9.4 +psycopg==3.1.4 +psycopg2==2.9.5 psycopg2-binary==2.9.4 -py==1.10.0 -pycodestyle==2.6.0 -pyparsing==2.4.7 -pytest==7.1.1 -python-dateutil==2.8.1 -python-dotenv==0.15.0 -python-editor==1.0.4 -requests==2.25.1 -six==1.15.0 -SQLAlchemy==1.3.23 +ptyprocess==0.7.0 +pure-eval==0.2.2 +py==1.11.0 +py-moneyed==2.0 +pycodestyle==2.9.1 +pycparser==2.21 +Pygments==2.13.0 +pyodbc==4.0.34 +pyparsing==3.0.9 +pyrsistent==0.19.2 +PySocks==1.7.1 +pystock==0.1.6 +pytest==7.1.3 +python-dateutil==2.8.2 +python-dotenv==0.21.0 +pytz==2022.5 +PyYAML==6.0 +pyzmq==24.0.1 +qtconsole==5.4.0 +QtPy==2.3.0 +requests==2.26.0 +scipy==1.9.3 +seaborn==0.12.1 +selenium==4.5.0 +Send2Trash==1.8.0 +six @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/six-1.15.0-py2.py3-none-any.whl +sniffio==1.3.0 +sortedcontainers==2.4.0 +soupsieve==2.3.2.post1 +SQLAlchemy==1.4.39 +stack-data==0.5.1 +terminado==0.17.0 +tinycss2==1.2.1 toml==0.10.2 -urllib3==1.26.5 -Werkzeug==1.0.1 +tomli==2.0.1 +tornado==6.2 +traitlets==5.5.0 +trio==0.22.0 +trio-websocket==0.9.2 +typing_extensions==4.4.0 +urllib3==1.26.12 +virtualenv==20.16.7 +visitor==0.1.3 +wcwidth==0.2.5 +webencodings==0.5.1 +websocket-client==1.4.2 +Werkzeug==2.2.2 +widgetsnbextension==4.0.3 +wsproto==1.2.0 +WTForms==3.0.1 +zipp==3.10.0 From ce4084d67dec10ff5f718d9a21786ba969492535 Mon Sep 17 00:00:00 2001 From: larissa Date: Mon, 9 Jan 2023 14:17:54 -0800 Subject: [PATCH 55/56] Refroze the dependencies while in activated venv --- requirements.txt | 173 ++++++++--------------------------------------- 1 file changed, 28 insertions(+), 145 deletions(-) diff --git a/requirements.txt b/requirements.txt index 88708cdd5..971fcb7d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,151 +1,34 @@ -adynform @ file:///Users/lollapalarza/adyn/adynform -alembic==1.8.1 -altgraph @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/altgraph-0.17.2-py2.py3-none-any.whl -analyzer==0.1.1 -analyzerdam==0.1.2 -analyzerstrategies==0.1.5 -anyio==3.6.2 -appnope==0.1.3 -argon2-cffi==21.3.0 -argon2-cffi-bindings==21.2.0 -ast2json==0.3 -asttokens==2.0.8 -async-generator==1.10 -attrs==22.1.0 -Babel==2.10.3 -backcall==0.2.0 -beautifulsoup4==4.11.1 -bleach==5.0.1 -bokeh==2.4.3 -certifi==2022.9.24 -cffi==1.15.1 -charset-normalizer==2.0.12 -click==8.1.3 -contourpy==1.0.6 -cycler==0.11.0 -databases==0.6.1 -debugpy==1.6.3 -decorator==5.1.1 -defusedxml==0.7.1 -distlib==0.3.6 -dominate==2.6.0 -entrypoints==0.4 -exceptiongroup==1.0.0rc9 -executing==1.1.1 -fastjsonschema==2.16.2 -filelock==3.8.0 -Flask==2.2.2 -Flask-Bootstrap==3.3.7.1 -Flask-SQLAlchemy==2.5.1 -Flask-WTF==1.0.1 -fonttools==4.38.0 -future @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/future-0.18.2-py3-none-any.whl -googlefinance==0.7 -greenlet==1.1.2 +alembic==1.5.4 +attrs==21.2.0 +autopep8==1.5.5 +blinker==1.4 +certifi==2020.12.5 +chardet==4.0.0 +click==7.1.2 +Flask==1.1.2 +Flask-Migrate==2.6.0 +Flask-SQLAlchemy==2.4.4 gunicorn==20.1.0 -h11==0.14.0 -idna==3.4 -importlib-metadata==5.0.0 +idna==2.10 iniconfig==1.1.1 -ipdb==0.13.9 -ipykernel==6.17.1 -ipython==8.5.0 -ipython-genutils==0.2.0 -ipywidgets==8.0.2 -itsdangerous==2.1.2 -jedi==0.18.1 -Jinja2==3.1.2 -jsonschema==4.17.0 -jupyter==1.0.0 -jupyter-console==6.4.4 -jupyter-server==1.23.1 -jupyter_client==7.4.5 -jupyter_core==5.0.0 -jupyterlab-pygments==0.2.2 -jupyterlab-widgets==3.0.3 -kiwisolver==1.4.4 -logger==1.4 -macholib @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/macholib-1.15.2-py2.py3-none-any.whl -Mako==1.2.3 -MarkupSafe==2.1.1 -matplotlib==3.6.0 -matplotlib-inline==0.1.6 -mistune==2.0.4 -mox==0.5.3 -nbclassic==0.4.8 -nbclient==0.7.0 -nbconvert==7.2.4 -nbformat==5.7.0 -nest-asyncio==1.5.6 -notebook==6.5.2 -notebook_shim==0.2.2 -numpy==1.23.4 -outcome==1.2.0 -packaging==21.3 -pandas==1.5.1 -pandocfilters==1.5.0 -parso==0.8.3 -pbr==1.6.0 -pep8==1.7.1 -pexpect==4.8.0 -pickleshare==0.7.5 -Pillow==9.2.0 -platformdirs==2.5.3 +itsdangerous==1.1.0 +Jinja2==2.11.3 +Mako==1.1.4 +MarkupSafe==1.1.1 +packaging==21.2 pluggy==1.0.0 -prometheus-client==0.15.0 -prompt-toolkit==3.0.31 -psutil==5.9.4 -psycopg==3.1.4 -psycopg2==2.9.5 psycopg2-binary==2.9.4 -ptyprocess==0.7.0 -pure-eval==0.2.2 -py==1.11.0 -py-moneyed==2.0 -pycodestyle==2.9.1 -pycparser==2.21 -Pygments==2.13.0 -pyodbc==4.0.34 -pyparsing==3.0.9 -pyrsistent==0.19.2 -PySocks==1.7.1 -pystock==0.1.6 -pytest==7.1.3 -python-dateutil==2.8.2 -python-dotenv==0.21.0 -pytz==2022.5 -PyYAML==6.0 -pyzmq==24.0.1 -qtconsole==5.4.0 -QtPy==2.3.0 -requests==2.26.0 -scipy==1.9.3 -seaborn==0.12.1 -selenium==4.5.0 -Send2Trash==1.8.0 -six @ file:///System/Volumes/Data/SWE/Apps/DT/BuildRoots/BuildRoot2/ActiveBuildRoot/Library/Caches/com.apple.xbs/Sources/python3/python3-124/six-1.15.0-py2.py3-none-any.whl -sniffio==1.3.0 -sortedcontainers==2.4.0 -soupsieve==2.3.2.post1 -SQLAlchemy==1.4.39 -stack-data==0.5.1 -terminado==0.17.0 -tinycss2==1.2.1 +py==1.10.0 +pycodestyle==2.6.0 +pyparsing==2.4.7 +pytest==7.1.1 +python-dateutil==2.8.1 +python-dotenv==0.15.0 +python-editor==1.0.4 +requests==2.25.1 +six==1.15.0 +SQLAlchemy==1.3.23 toml==0.10.2 tomli==2.0.1 -tornado==6.2 -traitlets==5.5.0 -trio==0.22.0 -trio-websocket==0.9.2 -typing_extensions==4.4.0 -urllib3==1.26.12 -virtualenv==20.16.7 -visitor==0.1.3 -wcwidth==0.2.5 -webencodings==0.5.1 -websocket-client==1.4.2 -Werkzeug==2.2.2 -widgetsnbextension==4.0.3 -wsproto==1.2.0 -WTForms==3.0.1 -zipp==3.10.0 +urllib3==1.26.5 +Werkzeug==1.0.1 From 842ccddb16d7ce7e00fedb5ac9a934541665fe9f Mon Sep 17 00:00:00 2001 From: larissa Date: Mon, 9 Jan 2023 16:40:42 -0800 Subject: [PATCH 56/56] Deleted extra line --- app/routes/customer_routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/routes/customer_routes.py b/app/routes/customer_routes.py index bb6b55bed..50e57adbe 100644 --- a/app/routes/customer_routes.py +++ b/app/routes/customer_routes.py @@ -31,7 +31,6 @@ def display_all_customers(): else: customer_query = customer_query.all() # fill http response list - response = [] for customer in customer_query: response.append(customer.to_dict())