From d65f18be4f8acac75cd25057b4a05e58519cddd4 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 08:43:54 +0300 Subject: [PATCH 01/66] CI started --- .travis.yml | 9 +++++++++ README.md | 7 +++++++ 2 files changed, 16 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b995082 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: python +python: + - "3.6" +cache: pip + +install: + - pip install -r requirements.txt +script: + - pytest diff --git a/README.md b/README.md index 9554b15..e8350ba 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # storeManager A web application to help store managers maintain their inventories and manage sale records. + + + + + + +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) From b6ca0dec0765e10a5ab409bb0f85b1c23f154d7e Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 08:49:04 +0300 Subject: [PATCH 02/66] CI started --- requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 From 38a8370cbbbc3eefdefb5035e8fbe72f9c0b55e0 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 09:40:07 +0300 Subject: [PATCH 03/66] CI started --- .gitignore | 0 README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index e8350ba..612a62c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # storeManager +[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) A web application to help store managers maintain their inventories and manage sale records. - [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) From 59912b3c34dac7556ee339004225d74f68dcdd58 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 09:45:12 +0300 Subject: [PATCH 04/66] CI started --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 612a62c..f1d8985 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # storeManager [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) + + + A web application to help store managers maintain their inventories and manage sale records. From 2e9c99d62db524c2899d4bc195d9086ba2041a8c Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 09:49:10 +0300 Subject: [PATCH 05/66] Coveralls --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f1d8985..fc163f5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) +[![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg?branch=project-app)](https://coveralls.io/github/hogum/storeManager?branch=project-app) A web application to help store managers maintain their inventories and manage sale records. From 9c11059bb00c946452030e566f71f52e68bce66d Mon Sep 17 00:00:00 2001 From: root Date: Wed, 17 Oct 2018 10:12:09 +0300 Subject: [PATCH 06/66] Coveralls --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index fc163f5..466daeb 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg?branch=project-app)](https://coveralls.io/github/hogum/storeManager?branch=project-app) + +[![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) + A web application to help store managers maintain their inventories and manage sale records. From a05015534cc4c25eafdb44c15d6c850bc4c3741f Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:02:15 +0300 Subject: [PATCH 07/66] sales resourcecreated --- .gitignore | 11 ++++++++++ app/app.py | 57 +++++++++++++++++++++++++++--------------------- requirements.txt | 11 ++++++++++ 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index e69de29..f1d4863 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store + +__pycache__/ +*.py[cod] +*$py.class + +# virtualenv +.pyvirtual/ +pyvirtual/ +env/ + diff --git a/app/app.py b/app/app.py index 3599952..cefeaee 100644 --- a/app/app.py +++ b/app/app.py @@ -1,46 +1,53 @@ -from flask import Flask, jsonify +from flask import Flask, abort +my_app = Flask(__name__, static_url_path="") sales = [ { 'sales_record': 1, 'attendant': u'Attendant One', + + # Customer contacts + 'name': u'Customer One', + 'address': u'45 bright street', + 'contact': [u'+00012345', u'customer_c@example.co'], + + # Transaction Info 'product': u'Spam 2.0', 'quantity': u'1', - 'complete': False, + 'date': datetime(2018, 6, 6, 5, 28, 56, 243), 'description': u'Hot with extra extra spam', - 'customer': { - 'name': u'Customer One', - 'address': u'45 bright street', - 'contact': [u'+00012345', - u'customer_one@example.co' - ] - }, - 'transaction type': u'Cash on Delivery', - 'gifts': None, - 'total for this sale': u'Ksh 276' + 'transaction_type': u'Cash on Delivery', + 'complete': False, + + 'gifts': 1, # Anything to reduce sale e.g discounts + 'total': 276 }, { 'sales_record': 2, 'attendant': u'Attendant Six', - 'product': u'Spam 2.0', + + # Customer contacts + 'name': u'Customer fifty', + 'address': u'42 bright street', + 'contact': [u'+00012345', u'customer_c@example.co'], + + # Transaction Info + 'product': u'Spam 2.2', 'quantity': u'1', - 'complete': False, + 'date': datetime(2018, 3, 16, 10, 3, 10, 7345), 'description': u'Black with red eatable margins', - 'customer': { - 'name': u'Customer fifty', - 'address': u'42 bright street', - 'contact': [u'+00012345', - u'customer_c@example.co' - ] - }, - 'transaction type': u'Credit Card', - 'gifts':None, - 'total for this sale': u'Ksh 355' - + 'transaction_type': u'Credit', + 'complete': False, + 'gifts': 0, # Any reduction in sale price + 'total': 355 } ] + + + + diff --git a/requirements.txt b/requirements.txt index e69de29..f747882 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,11 @@ +aniso8601==3.0.2 +Click==7.0 +Flask==1.0.2 +Flask-HTTPAuth==3.2.4 +Flask-RESTful==0.3.6 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +pytz==2018.5 +six==1.11.0 +Werkzeug==0.14.1 From 417f580f65ac57bc5b46f1eade48b5eb865b0516 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:03:52 +0300 Subject: [PATCH 08/66] specified outfut fields for elements --- app/app.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/app/app.py b/app/app.py index cefeaee..40a94e3 100644 --- a/app/app.py +++ b/app/app.py @@ -48,6 +48,50 @@ ] +sale_fields = { + 'sales_uri': fields.Url('sale'), + 'attendant': fields.String, + 'gifts': fields.Integer, + 'total': fields.Fixed +} +""" + Output nested customer details + """ +sale_fields['customer'] = {} +sale_fields['customer']['Name'] = fields.String(attribute='name') +sale_fields['customer']['Address'] = fields.String(attribute='address') +# Create list for customer contacts +sale_fields['customer']['Contact'] = fields.List( + fields.String, attribute='contact' +) + +""" + Nest the transaction info + """ + +sale_fields['transaction_info'] = {} + +sale_fields['transaction_info']['Product'] = fields.String(attribute='product') +sale_fields['transaction_info']['Quantity'] = fields.String( + attribute='quantity' +) +sale_fields['transaction_info']['Date'] = fields.DateTime( + attribute='date', dt_format='rfc822' +) +sale_fields['transaction_info']['Description'] = fields.String( + attribute='description' +) +sale_fields['transaction_info']['Transaction_type'] = fields.String( + attribute='transaction_type' +) +sale_fields['transaction_info']['Complete'] = fields.Boolean( + attribute='complete' +) + + + +if __name__ == '__main__': + my_app.run(debug=True) From 30ecd090883ae62f2aee9b0a9404109c76ebd447 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:05:19 +0300 Subject: [PATCH 09/66] all-sales resource made existent --- app/app.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/app.py b/app/app.py index 40a94e3..de76e63 100644 --- a/app/app.py +++ b/app/app.py @@ -92,6 +92,41 @@ ) +class AllSalesAPI(Resource): + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('attendant', type=str, + required=True, + help="A sale need's an attendant", + location='json') + + self.parse.add_argument('customer', type=dict, + default={ + 'name': 'Anonymous', + 'address': 'unknown', + 'contact': 'not stated' + }, + location='json') + + self.parse.add_argument('product', type=str, + required=True, + help="A product to sell sure has a name", + location='json') + + self.parse.add_argument('quantity', type=int, + help="How many items", default=1, + location='json') + + super(AllSalesAPI, self).__init__() + + def get(self): + return { + 'sales': [marshal(sale, sale_fields) for sale in sales] + } + + + +api.add_resource(AllSalesAPI, '/stman/api/v1.0/sales', endpoint='sales') if __name__ == '__main__': my_app.run(debug=True) From 75f62c5622187998f773ee18a08f7720c9913466 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:06:21 +0300 Subject: [PATCH 10/66] solved for datetime format --- app/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/app.py b/app/app.py index de76e63..a1beccb 100644 --- a/app/app.py +++ b/app/app.py @@ -1,7 +1,9 @@ from flask import Flask, abort +from flask_restful import Api, fields, Resource, reqparse, marshal my_app = Flask(__name__, static_url_path="") +api = Api(my_app) sales = [ { From 64ec1c9f87f68937f6202c6dda667f47e3ef31c7 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:07:30 +0300 Subject: [PATCH 11/66] access to individual sale record added --- app/app.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/app.py b/app/app.py index a1beccb..a18f0b3 100644 --- a/app/app.py +++ b/app/app.py @@ -1,6 +1,7 @@ from flask import Flask, abort from flask_restful import Api, fields, Resource, reqparse, marshal +from datetime import datetime my_app = Flask(__name__, static_url_path="") api = Api(my_app) @@ -127,8 +128,32 @@ def get(self): } +class SaleAPI(Resource): + """docstring for SaleAPI""" + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('attendant', type=str, location='json') + self.parse.add_argument('transaction_info', type=dict, location='json') + self.parse.add_argument('gifts', type=int, location='json') + self.parse.add_argument('total', + type=float, + location='json' + ) + super(SaleAPI, self).__init__() + + def get(self, sales_record): + sale = [sale for sale in sales if sale['sales_record'] == sales_record] + + if not sale: + abort(404) + + return {'sale': marshal(sale[0], sale_fields)} + api.add_resource(AllSalesAPI, '/stman/api/v1.0/sales', endpoint='sales') +api.add_resource(SaleAPI, '/stman/api/v1.0/sales/', + endpoint='sale' + ) if __name__ == '__main__': my_app.run(debug=True) From a57d1bf57c823e1d2190fe0ca43effb591f6c036 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 12:26:48 +0300 Subject: [PATCH 12/66] parse and validate request data --- app/app.py | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/app/app.py b/app/app.py index a18f0b3..ace7958 100644 --- a/app/app.py +++ b/app/app.py @@ -18,7 +18,7 @@ # Transaction Info 'product': u'Spam 2.0', - 'quantity': u'1', + 'quantity': 1, 'date': datetime(2018, 6, 6, 5, 28, 56, 243), 'description': u'Hot with extra extra spam', 'transaction_type': u'Cash on Delivery', @@ -39,7 +39,7 @@ # Transaction Info 'product': u'Spam 2.2', - 'quantity': u'1', + 'quantity': 1, 'date': datetime(2018, 3, 16, 10, 3, 10, 7345), 'description': u'Black with red eatable margins', 'transaction_type': u'Credit', @@ -78,7 +78,7 @@ sale_fields['transaction_info'] = {} sale_fields['transaction_info']['Product'] = fields.String(attribute='product') -sale_fields['transaction_info']['Quantity'] = fields.String( +sale_fields['transaction_info']['Quantity'] = fields.Integer( attribute='quantity' ) sale_fields['transaction_info']['Date'] = fields.DateTime( @@ -97,20 +97,31 @@ class AllSalesAPI(Resource): def __init__(self): + + """ + verify arguments' are in correct format + """ + self.parse = reqparse.RequestParser() self.parse.add_argument('attendant', type=str, required=True, help="A sale need's an attendant", location='json') - self.parse.add_argument('customer', type=dict, - default={ - 'name': 'Anonymous', - 'address': 'unknown', - 'contact': 'not stated' - }, + # Customer details + self.parse.add_argument('name', type=str, + default='Anonymous', + location='json') + + self.parse.add_argument('address', type=str, + default='Unknown', + location='json') + + self.parse.add_argument('contact', type=list, location='json') + # transaction details + self.parse.add_argument('product', type=str, required=True, help="A product to sell sure has a name", @@ -120,6 +131,23 @@ def __init__(self): help="How many items", default=1, location='json') + self.parse.add_argument('transaction_type', type=str, + default='Cash on Delivery', + location='json') + + self.parse.add_argument('gifts', type=int, + default='0', + location='json') + + self.parse.add_argument('total', type=int, required=True, + location='json') + + self.parse.add_argument('description', type=str, + default='', + location='json') + + + super(AllSalesAPI, self).__init__() def get(self): @@ -127,6 +155,13 @@ def get(self): 'sales': [marshal(sale, sale_fields) for sale in sales] } + def post(self): + elements = self.parse.parse_args() + + sale = { + + } + class SaleAPI(Resource): """docstring for SaleAPI""" From 98032fff04aee3e040182bc737d4f418a2c47c6d Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 13:34:14 +0300 Subject: [PATCH 13/66] option to create new sale resource --- app/app.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/app/app.py b/app/app.py index ace7958..e8a2232 100644 --- a/app/app.py +++ b/app/app.py @@ -24,8 +24,8 @@ 'transaction_type': u'Cash on Delivery', 'complete': False, - 'gifts': 1, # Anything to reduce sale e.g discounts - 'total': 276 + 'gifts': 100, # Anything to reduce sale e.g discounts + 'price': 276, }, { @@ -46,15 +46,27 @@ 'complete': False, 'gifts': 0, # Any reduction in sale price - 'total': 355 + 'price': 355 } ] +# calculate total cost of sale + +for each_sale in sales: + each_sale.update( + { + 'total': each_sale.get('quantity') * + each_sale.get('price') - + each_sale.get('gifts') + } + ) + sale_fields = { 'sales_uri': fields.Url('sale'), 'attendant': fields.String, 'gifts': fields.Integer, + 'price': fields.Fixed, 'total': fields.Fixed } @@ -118,6 +130,7 @@ def __init__(self): location='json') self.parse.add_argument('contact', type=list, + default=['phone', 'email'], location='json') # transaction details @@ -139,15 +152,15 @@ def __init__(self): default='0', location='json') - self.parse.add_argument('total', type=int, required=True, + self.parse.add_argument('price', type=int, required=True, + help="""You sure are not giving it away for free + """, location='json') self.parse.add_argument('description', type=str, default='', location='json') - - super(AllSalesAPI, self).__init__() def get(self): @@ -159,9 +172,37 @@ def post(self): elements = self.parse.parse_args() sale = { - + 'sales_record': sales[-1]['sales_record'] + 1, + 'attendant': elements['attendant'], + 'name': elements['name'], + 'address': elements['address'], + 'contact': elements['contact'], + 'product': elements['product'], + 'quantity': elements['quantity'], + 'date': datetime.now(), + 'description': elements['description'], + 'transaction_type': elements['transaction_type'], + 'complete': False, + 'gifts': 0, + 'price': elements['price'] } + # Find total cost of sale + sale.update( + { + 'total': sale.get('quantity') * + sale.get('price') - + sale.get('gifts') + } + ) + + # Add new sale to sales record + sales.append(sale) + + return { + 'sale': marshal(sale, sale_fields) + }, 201 + class SaleAPI(Resource): """docstring for SaleAPI""" From c641fe516fb83e7b09b0d354855d9f5f1fa0c637 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 16:49:45 +0300 Subject: [PATCH 14/66] edit sale resource made possible # looked for this bug for hours - I was working on a different filegit add --allgit add --allgit add --all! whatgit add --all? --- app/app.py | 85 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/app/app.py b/app/app.py index e8a2232..963ff7d 100644 --- a/app/app.py +++ b/app/app.py @@ -51,7 +51,7 @@ ] -# calculate total cost of sale +# calculate total cost of every sale for each_sale in sales: each_sale.update( @@ -110,10 +110,6 @@ class AllSalesAPI(Resource): def __init__(self): - """ - verify arguments' are in correct format - """ - self.parse = reqparse.RequestParser() self.parse.add_argument('attendant', type=str, required=True, @@ -149,7 +145,7 @@ def __init__(self): location='json') self.parse.add_argument('gifts', type=int, - default='0', + default=0, location='json') self.parse.add_argument('price', type=int, required=True, @@ -207,25 +203,84 @@ def post(self): class SaleAPI(Resource): """docstring for SaleAPI""" def __init__(self): + self.parse = reqparse.RequestParser() - self.parse.add_argument('attendant', type=str, location='json') - self.parse.add_argument('transaction_info', type=dict, location='json') - self.parse.add_argument('gifts', type=int, location='json') - self.parse.add_argument('total', - type=float, - location='json' - ) + self.parse.add_argument('attendant', type=str, + required=True, + help="A sale need's an attendant", + location='json') + + # Customer details + self.parse.add_argument('name', type=str, + default='Anonymous', + location='json') + + self.parse.add_argument('address', type=str, + default='Unknown', + location='json') + + self.parse.add_argument('contact', type=list, + default=['phone', 'email'], + location='json') + + # transaction details + + self.parse.add_argument('product', type=str, + required=True, + help="A product to sell sure has a name", + location='json') + + self.parse.add_argument('quantity', type=int, + help="How many items", default=1, + location='json') + + self.parse.add_argument('transaction_type', type=str, + default='Cash on Delivery', + location='json') + + self.parse.add_argument('gifts', type=int, + default='0', + location='json') + + self.parse.add_argument('price', type=int, required=True, + help="""You sure are not giving it away for free + """, + location='json') + + self.parse.add_argument('description', type=str, + default='', + location='json') + super(SaleAPI, self).__init__() - def get(self, sales_record): - sale = [sale for sale in sales if sale['sales_record'] == sales_record] + def get(self, requested_sales_record): + sale = [sale for sale + in sales if sale['sales_record'] is requested_sales_record] + + if not sale: + abort(404) + + return {'sale': marshal(sale[0], sale_fields)} + + def put(self, requested_sales_record): + sale = [sale for sale + in sales if sale['sales_record'] is requested_sales_record + ] if not sale: abort(404) + elements = self.parse.parse_args() + + # update any changed element + for key, value in elements.items(): + if value: + sale[0][key] = value + return {'sale': marshal(sale[0], sale_fields)} + api.add_resource(AllSalesAPI, '/stman/api/v1.0/sales', endpoint='sales') api.add_resource(SaleAPI, '/stman/api/v1.0/sales/', endpoint='sale' From b6cff4be6bebbb17cabbcc7710d17612705048b4 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 16:51:23 +0300 Subject: [PATCH 15/66] added delete-sale-resource --- app/app.py | 83 +++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/app/app.py b/app/app.py index 963ff7d..5d439db 100644 --- a/app/app.py +++ b/app/app.py @@ -51,7 +51,7 @@ ] -# calculate total cost of every sale +# calculate total cost of sale for each_sale in sales: each_sale.update( @@ -110,6 +110,10 @@ class AllSalesAPI(Resource): def __init__(self): + """ + verify arguments' are in correct format + """ + self.parse = reqparse.RequestParser() self.parse.add_argument('attendant', type=str, required=True, @@ -145,7 +149,7 @@ def __init__(self): location='json') self.parse.add_argument('gifts', type=int, - default=0, + default='0', location='json') self.parse.add_argument('price', type=int, required=True, @@ -203,68 +207,27 @@ def post(self): class SaleAPI(Resource): """docstring for SaleAPI""" def __init__(self): - self.parse = reqparse.RequestParser() - self.parse.add_argument('attendant', type=str, - required=True, - help="A sale need's an attendant", - location='json') - - # Customer details - self.parse.add_argument('name', type=str, - default='Anonymous', - location='json') - - self.parse.add_argument('address', type=str, - default='Unknown', - location='json') - - self.parse.add_argument('contact', type=list, - default=['phone', 'email'], - location='json') - - # transaction details - - self.parse.add_argument('product', type=str, - required=True, - help="A product to sell sure has a name", - location='json') - - self.parse.add_argument('quantity', type=int, - help="How many items", default=1, - location='json') - - self.parse.add_argument('transaction_type', type=str, - default='Cash on Delivery', - location='json') - - self.parse.add_argument('gifts', type=int, - default='0', - location='json') - - self.parse.add_argument('price', type=int, required=True, - help="""You sure are not giving it away for free - """, - location='json') - - self.parse.add_argument('description', type=str, - default='', - location='json') - + self.parse.add_argument('attendant', type=str, location='json') + self.parse.add_argument('transaction_info', type=dict, location='json') + self.parse.add_argument('gifts', type=int, location='json') + self.parse.add_argument('total', + type=float, + location='json' + ) super(SaleAPI, self).__init__() - def get(self, requested_sales_record): - sale = [sale for sale - in sales if sale['sales_record'] is requested_sales_record] + def get(self, sales_record): + sale = [sale for sale in sales if sale['sales_record'] == sales_record] if not sale: abort(404) return {'sale': marshal(sale[0], sale_fields)} - def put(self, requested_sales_record): + def put(self, sales_record): sale = [sale for sale - in sales if sale['sales_record'] is requested_sales_record + in sales if sale['sales_record'] is sales_record ] if not sale: @@ -279,6 +242,18 @@ def put(self, requested_sales_record): return {'sale': marshal(sale[0], sale_fields)} + def delete(self, sales_record): + sale = [sale for sale + in sales if sale['sales_record'] is sales_record + ] + + if not sale: + abort(404) + sales.remove(sale[0]) + + return { + 'Effect': True + } api.add_resource(AllSalesAPI, '/stman/api/v1.0/sales', endpoint='sales') From a9f5f9540a11d0879d35e0db500ecd57451cc215 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 18:18:09 +0300 Subject: [PATCH 16/66] verification for users --- app/app.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/app/app.py b/app/app.py index 5d439db..265757a 100644 --- a/app/app.py +++ b/app/app.py @@ -1,10 +1,13 @@ -from flask import Flask, abort +from flask import Flask, abort, jsonify, make_response from flask_restful import Api, fields, Resource, reqparse, marshal from datetime import datetime +from flask_httpauth import HTTPBasicAuth my_app = Flask(__name__, static_url_path="") api = Api(my_app) +admin_auth = HTTPBasicAuth() +auth = HTTPBasicAuth() sales = [ { @@ -106,8 +109,51 @@ attribute='complete' ) +admin_users = { + 'manager': 'man', +} + +users = { + 'manager': 'man', + 'attendant': 'att' +} + + +@admin_auth.get_password +def use_password(username): + if username in users: + return admin_users.get(username) + + return None + + +@admin_auth.error_handler +def restricted(): + return make_response(jsonify({ + 'message': "Access not allowed" + }), 403 + ) + + +@auth.get_password +def u_password(username): + if username in users: + return users.get(username) + + return None + + +@auth.error_handler +def restrict(): + return make_response(jsonify({ + 'message': "Access not allowed" + }), 403 + ) + class AllSalesAPI(Resource): + decorators = [admin_auth.login_required] + def __init__(self): """ @@ -205,6 +251,7 @@ def post(self): class SaleAPI(Resource): + """docstring for SaleAPI""" def __init__(self): self.parse = reqparse.RequestParser() @@ -217,6 +264,7 @@ def __init__(self): ) super(SaleAPI, self).__init__() + @auth.login_required def get(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] == sales_record] @@ -225,6 +273,7 @@ def get(self, sales_record): return {'sale': marshal(sale[0], sale_fields)} + @admin_auth.login_required def put(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] is sales_record @@ -236,12 +285,13 @@ def put(self, sales_record): elements = self.parse.parse_args() # update any changed element - for key, value in elements.items(): + for key, value in list(elements.items()): if value: sale[0][key] = value return {'sale': marshal(sale[0], sale_fields)} + @admin_auth.login_required def delete(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] is sales_record From 49867643b9432a74e4a1af72fb68dfb4c6925ebc Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 18:53:46 +0300 Subject: [PATCH 17/66] solved for verification types --- app/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/app.py b/app/app.py index 265757a..6256eff 100644 --- a/app/app.py +++ b/app/app.py @@ -152,7 +152,7 @@ def restrict(): class AllSalesAPI(Resource): - decorators = [admin_auth.login_required] + decorators = [auth.login_required] def __init__(self): @@ -251,6 +251,7 @@ def post(self): class SaleAPI(Resource): + decorators = [admin_auth.login_required] """docstring for SaleAPI""" def __init__(self): @@ -264,7 +265,6 @@ def __init__(self): ) super(SaleAPI, self).__init__() - @auth.login_required def get(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] == sales_record] @@ -273,7 +273,6 @@ def get(self, sales_record): return {'sale': marshal(sale[0], sale_fields)} - @admin_auth.login_required def put(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] is sales_record @@ -291,7 +290,6 @@ def put(self, sales_record): return {'sale': marshal(sale[0], sale_fields)} - @admin_auth.login_required def delete(self, sales_record): sale = [sale for sale in sales if sale['sales_record'] is sales_record From 8c5373877be0821288e6a7babe10901d01870fe9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 21:47:32 +0300 Subject: [PATCH 18/66] access to individual product resource --- app/app.py | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/app/app.py b/app/app.py index 6256eff..55d69ad 100644 --- a/app/app.py +++ b/app/app.py @@ -54,7 +54,29 @@ ] -# calculate total cost of sale +products = [ + { + 'title': 'Bacon&Spam', + 'category': 'Hair Likes Food', + 'price': 934, + 'in stock': True, + 'date received': datetime(2018, 7, 10, 4, 2, 8, 564), + 'id': 1 + + }, + + { + 'title': 'Innocent Coconut Water', + 'category': 'Women sure can Sleep', + 'price': 94534, + 'in stock': True, + 'date received': datetime( + 2018, 5, 30, 22, 12, 38, 649), + 'id': 2 + } +] + +# calculate total cost of each sale for each_sale in sales: each_sale.update( @@ -65,6 +87,16 @@ } ) +product_fields = { + 'title': fields.String, + 'category': fields.String, + 'price': fields.Float, + 'in stock': fields.Boolean, + 'date received': fields.DateTime, + 'url': fields.Url('product') # Ensure user doen't + # need to know how to generate url +} + sale_fields = { 'sales_uri': fields.Url('sale'), 'attendant': fields.String, @@ -109,6 +141,7 @@ attribute='complete' ) + admin_users = { 'manager': 'man', } @@ -304,10 +337,127 @@ def delete(self, sales_record): } +class AllProductsAPI(Resource): + """docstring for AllProductsAPI""" + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('title', type=str, required=True, + help="Please add a title", + location='json' + ) + + self.parse.add_argument('category', type=str, + default='None', + location='json' + ) + + self.parse.add_argument('price', type=int, + required=True, + help="You are not \ + allowed to give out stuff for free", + location='json' + ) + + self.parse.add_argument('in stock', type=bool, + default=True, + location='json' + ) + + super(AllProductsAPI, self).__init__() + + def get(self): + return { + 'product': [marshal(product, product_fields) + for product in products] + } + + def post(self): + elements = self.parse.parse_args() + + product = { + 'title': elements['title'], + 'category': elements['category'], + 'price': elements['price'], + 'in stock': True, + 'date received': datetime.now(), + 'id': products[-1]['id'] + 1 + } + + products.append(product) + + return { + 'product': marshal(product, product_fields) + }, 201 + + +class ProductAPI(Resource): + """docstring for ProductAPI""" + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('title', type=str, + location='json' + ) + + self.parse.add_argument('category', type=str, + location='json' + ) + + self.parse.add_argument('price', type=int, + location='json' + ) + + self.parse.add_argument('in stock', type=bool, + location='json' + ) + + super(ProductAPI, self).__init__() + + def get(self, id): + product = [product for product in products if product['id'] is id] + + if not product: + abort(404) + + return { + 'product': marshal(product[0], product_fields) + } + + def put(self, id): + product = [product for product in products if product['id'] is id] + + if not product: + abort(404) + elements = self.parse.parse_args() + + for key, value in list(elements.items()): + if value: + product[0][key] = value + + return { + 'product': marshal(product, product_fields) + } + + def delete(self, id): + product = [product for product in products if product['id'] is id] + + if not product: + abort(404) + product.remove(product[0]) + + return { + 'Status': True + } + + api.add_resource(AllSalesAPI, '/stman/api/v1.0/sales', endpoint='sales') api.add_resource(SaleAPI, '/stman/api/v1.0/sales/', endpoint='sale' ) +api.add_resource(AllProductsAPI, '/stman/api/v1.0/products', + endpoint='products') +api.add_resource(ProductAPI, '/stman/api/v1.0/products/', + endpoint='product') + if __name__ == '__main__': my_app.run(debug=True) From 115ec18c4b5329aaad98b6486ff8b7b585af46ca Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 22:18:01 +0300 Subject: [PATCH 19/66] authorized access to product resource --- app/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/app.py b/app/app.py index 55d69ad..b6d21ea 100644 --- a/app/app.py +++ b/app/app.py @@ -371,6 +371,7 @@ def get(self): for product in products] } + @admin_auth.login_required def post(self): elements = self.parse.parse_args() @@ -422,6 +423,7 @@ def get(self, id): 'product': marshal(product[0], product_fields) } + @admin_auth.login_required def put(self, id): product = [product for product in products if product['id'] is id] @@ -437,6 +439,7 @@ def put(self, id): 'product': marshal(product, product_fields) } + @admin_auth.login_required def delete(self, id): product = [product for product in products if product['id'] is id] @@ -458,6 +461,3 @@ def delete(self, id): endpoint='products') api.add_resource(ProductAPI, '/stman/api/v1.0/products/', endpoint='product') - -if __name__ == '__main__': - my_app.run(debug=True) From bd48ac8bc4454d14f433e838a06874eb329ac7b9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Oct 2018 22:22:37 +0300 Subject: [PATCH 20/66] changed path to module --- run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.py b/run.py index 86c4d03..eccfc95 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ from app import app if __name__ == '__main__': - app.run() + app.my_app.run() From 5434c309ce9a071965fb9005baddf87ecf7c9eba Mon Sep 17 00:00:00 2001 From: root Date: Sat, 20 Oct 2018 20:04:35 +0300 Subject: [PATCH 21/66] tests for product resource --- README.md | 2 + app/__init__.py | 0 app/app.py | 6 +- app/tests/__init__.py | 0 app/tests/test_basic.py | 341 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 8 + 6 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/tests/__init__.py create mode 100644 app/tests/test_basic.py diff --git a/README.md b/README.md index 466daeb..e42d1ce 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) +[![GitHub issues](https://img.shields.io/github/issues/hogum/storeManager.svg?style=for-the-badge)](https://github.com/hogum/storeManager/issues) + A web application to help store managers maintain their inventories and manage sale records. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/app.py b/app/app.py index b6d21ea..77584a3 100644 --- a/app/app.py +++ b/app/app.py @@ -101,8 +101,8 @@ 'sales_uri': fields.Url('sale'), 'attendant': fields.String, 'gifts': fields.Integer, - 'price': fields.Fixed, - 'total': fields.Fixed + 'price': fields.Integer, + 'total': fields.Integer } """ @@ -190,7 +190,7 @@ class AllSalesAPI(Resource): def __init__(self): """ - verify arguments' are in correct format + Verify arguments' are in correct type. """ self.parse = reqparse.RequestParser() diff --git a/app/tests/__init__.py b/app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/test_basic.py b/app/tests/test_basic.py new file mode 100644 index 0000000..880254c --- /dev/null +++ b/app/tests/test_basic.py @@ -0,0 +1,341 @@ + +import unittest +from app import app +import mock +from base64 import b64encode +import json +import sys +from random import randint + +my_app = app.my_app + + +class BasicSaleTests(unittest.TestCase): + def setUp(self): + my_app.testing = True + self.app = my_app.test_client() + self.path = '/stman/api/v1.0/sales' + self.headers = { # User credentials to test + 'Authorization': 'Basic %s' % b64encode( + b"manager:man").decode("ascii") + + } + self.response_get = self.app.get(self.path, + headers=self.headers, + follow_redirects=True + ) + + # Url Path to individual sale resource + self.single_sale_path = self.path + '/' + str(1) + self.response_single_sale = self.app.get(self.single_sale_path, + headers=self.headers, + follow_redirects=True + ) + + def test_start_authorization_without_credentials(self): + response = self.app.get(self.path, follow_redirects=True) + self.assertEqual(response.status_code, 403, + "Grants unauthorized access" + ) + + def test_call_for_AllSalesAPI(self): + # Generate url for resourse directly # context unavailable + + resrce = app.AllSalesAPI() + + self.assertRaises(RuntimeError, resrce.get), "Calls non-creatable url" + + def test_true_authorization_for_verified_user(self): + # Test for access using valid credentials + + self.assertEqual(self.response_get.status_code, 200, + "Denies verified user access" + ) + + def test_get_sales(self): + # check data request comes in a dictionary + + response_unpack = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertTrue(isinstance(response_unpack, dict), + msg="Records not received back as dict") + + def test_fielding_of_ouputs(self): + # Check if sale record is given back as a nested response + + response_unpack = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertFalse(app.sales is response_unpack['sales'], + msg="Sale record details not organised in groups" + ) + + def test_post_sale(self): + """ + Verify the passed arguments create new details + for the new sale. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + attendant='Call me A', + price=354, + product='Baby Soap')), + content_type='application/json', + follow_redirects=True + ) + response_unpack = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertEqual(response_unpack['sale']['price'], 354, + "Sale not created with given data") + + def test_post_sale_arguments(self): + """ + Verify required arguments are passed for + resource to be created. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + attend='Call me A', + price=354, + product='Baby Soap')), + content_type='application/json', + follow_redirects=True + ) + response_unpack = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + fail_resp = {'attendant': "A sale need's an attendant"} + + self.assertDictEqual(fail_resp, response_unpack['message'], + msg="Fails to request \ + user for required arguments") + + def test_make_correct_post_sale(self): + """ + Verify that, with correct arguments, + a sale record is created + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + attendant='Her Again', + price=354, + product='Baby Soap')), + content_type='application/json', + follow_redirects=True + ) + + self.assertTrue(response_post_sale.status_code is 201, + msg="Fails to create new sale record") + + def test_post_sale_forbidden_arguments(self): + """ + Verify that unknown data arguments do not create new details + for the new sale. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + attendant='Somebody I know', + price=354, + product='Baby Soap', + missing_detail='absent')), + content_type='application/json', + follow_redirects=True + ) + response_unpack = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertRaises(KeyError, lambda: response_unpack[ + 'sale']['missing_detail']), + "Failed to ignore unrequired sale arguments" + + def test_post_sale_that_will_exists(self): + """ + Verify that the created is a brand new uri. + #actually do this ****************************** + Created sale is added to the existing sales. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + attendant='Call me A', + price=354, + product='Baby Soap')), + content_type='application/json', + follow_redirects=True + ) + response_unpack_post = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + + # GET Request from all-sales uri + response_unpack_get = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + # Uri for this sale + new_sale_uri = int(response_unpack_post['sale']['sales_uri'][-1]) + + # Uri for last sale + last_sale_uri = int( + response_unpack_get[ + 'sales'][-1]['sales_uri'][-1]) + 1 + + self.assertEqual(new_sale_uri, last_sale_uri, + msg="Fails to add new sale to existing sales") + + def test_put_for_AllSales(self): + """ + Our subclass Resource for 'sales' has no put method. + Check that changing of this resouce does not work + """ + how_flask_says_it = "The method is not allowed for the requested URL." + resput_sales = self.app.put(self.path, + headers=self.headers, + data=json.dumps(dict( + this=1, + thing=2, + fails=3)), + follow_redirects=True + ) + response_unpack = json.loads( + resput_sales.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertEqual(response_unpack['message'], how_flask_says_it) + + +class BasicSingleSaleTest(BasicSaleTests): + def test_post_sale_that_will_exists(self): + pass # Interferes with working of super + + def test_randomized_uri(self): + guessed_path = self.path + '/' + str(randint(2345, 998888)) + response = self.app.get(guessed_path, + headers=self.headers, + follow_redirects=True + ) + + self.assertTrue(response.status_code == 404, + msg="Failed to return error for nonexistent url" + ) + + def test_sale_uri(self): + """ + Check that the Sale url element is added from the path + that leads to that sale. + """ + + response_unpack = json.loads( + self.response_single_sale.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertEqual(response_unpack['sale']['sales_uri'], + self.single_sale_path) + + def test_sale_response_type(self): + # Response given as dict + + response_unpack = json.loads( + self.response_single_sale.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertTrue(isinstance(response_unpack, dict)) + + def test_put_sale(self): + """ + Verify a resource can be updated + """ + resput_single_sale = self.app.put(self.single_sale_path, + headers=self.headers, + data=json.dumps(dict( + this=1, + sale=2, + fails=3)), + content_type='application/json', + follow_redirects=True + ) + success = 200 + """ response_unpack = json.loads( + resput_single_sale.get_data(). + decode(sys.getdefaultencoding()) + ) """ + self.assertEqual(resput_single_sale.status_code, success) + + def test_put_sale_with_wrong_url(self): + """ + Verify a resource can be updated + """ + path = self.path + '/' + '664' + resput_single_sale = self.app.put(path, + headers=self.headers, + data=json.dumps(dict( + this=1, + sale=2, + fails=3)), + follow_redirects=True + ) + missing = 404 + """ response_unpack = json.loads( + resput_single_sale.get_data(). + decode(sys.getdefaultencoding()) + ) """ + self.assertEqual(resput_single_sale.status_code, missing) + + def test_delete_sale(self): + + # Try not remove 'path/../../1', I think every test around wants her + rm_path = self.path + '/' + '2' + resdel_sale = self.app.delete(rm_path, + headers=self.headers, + follow_redirects=True + ) + response_unpack = json.loads( + resdel_sale.get_data(). + decode(sys.getdefaultencoding()) + ) + resdel_sale = self.app.get(rm_path, + headers=self.headers, + follow_redirects=True + ) + # Assert delete + self.assertDictEqual(response_unpack, {"Effect": True}, + msg="Failed to delete sale" + ) + + # Assert sale is discarded + self.assertEqual(resdel_sale.status_code, 404, + msg="Failed to remove deleted item from records" + ) + + def test_delete_for_missing_resource(self): + guessed_path = self.path + '/' + str(randint(2345, 998888)) + response = self.app.get(guessed_path, + headers=self.headers, + follow_redirects=True + ) + + self.assertFalse(response.status_code == 200, + msg="Deletes a nonexistent sale" + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/requirements.txt b/requirements.txt index f747882..5a4f142 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,19 @@ aniso8601==3.0.2 +certifi==2018.10.15 +chardet==3.0.4 Click==7.0 +coverage==4.5.1 +coveralls==1.5.1 +docopt==0.6.2 Flask==1.0.2 Flask-HTTPAuth==3.2.4 Flask-RESTful==0.3.6 +idna==2.7 itsdangerous==0.24 Jinja2==2.10 MarkupSafe==1.0 pytz==2018.5 +requests==2.20.0 six==1.11.0 +urllib3==1.24 Werkzeug==0.14.1 From 8a17a9e676167536b6ab99183142014ecc035f17 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 01:42:59 +0300 Subject: [PATCH 22/66] tests for productsresource --- app/app.py | 2 +- app/tests/test_basic_.py | 326 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 38 ++--- 3 files changed, 346 insertions(+), 20 deletions(-) create mode 100644 app/tests/test_basic_.py diff --git a/app/app.py b/app/app.py index 77584a3..798c4e3 100644 --- a/app/app.py +++ b/app/app.py @@ -90,7 +90,7 @@ product_fields = { 'title': fields.String, 'category': fields.String, - 'price': fields.Float, + 'price': fields.Integer, 'in stock': fields.Boolean, 'date received': fields.DateTime, 'url': fields.Url('product') # Ensure user doen't diff --git a/app/tests/test_basic_.py b/app/tests/test_basic_.py new file mode 100644 index 0000000..9de2b8d --- /dev/null +++ b/app/tests/test_basic_.py @@ -0,0 +1,326 @@ +import unittest +from app import app +import mock +from base64 import b64encode +import json +import sys +from random import randint + +my_app = app.my_app + + +class BasicProductTests(unittest.TestCase): + def setUp(self): + my_app.testing = True + self.app = my_app.test_client() + self.path = '/stman/api/v1.0/products' + self.headers = { # User credentials to test + 'Authorization': 'Basic %s' % b64encode( + b"manager:man").decode("ascii") + + } + self.single_path = '/stman/api/v1.0/products' + '/' + '1' + + self.response_get = self.app.get(self.path, + follow_redirects=True + ) + self.response_get_auth = self.app.get(self.path, + headers=self.headers, + follow_redirects=True + ) + self.response_get_product = self.app.get(self.single_path, + headers=self.headers, + follow_redirects=True + ) + + # Load json data to compare with raw data + self.response_unpack_get = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + def test_access_without_credentials(self): + """ + Credentials not required not view products + """ + self.assertNotEqual(self.response_get.status_code, 403, + "Failed to show \ + products without requesting for credentials") + + self.assertNotEqual(self.response_get_auth.status_code, 403, + "Failed to show \ + products without requesting for credentials") + + def test_get_products(self): + # check data request comes in a dictionary + + response_unpack = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertTrue(isinstance(response_unpack, dict), + msg="Failed to output Records in a dictionary") + + def test_get_products_contents(self): + """ + Product records are lists wrapped in dicts + """ + self.assertIsInstance(self.response_unpack_get['product'], list, + msg="Failed to give record as a list of products" + ) + + def test_fielding_of_ouputs(self): + # Check if fields give back data as nested output + + self.assertFalse(app.products is self.response_unpack_get['product'], + msg="Failed to group Product record details" + ) + + def test_access_to_post_products(self): + """ + Verify that only allowed users(manager) can create product. + """ + + response_post_product = self.app.post(self.path, + follow_redirects=True + ) + self.assertTrue(response_post_product.status_code == 403, + msg="Fails to deny\ + anuthorized user access to create new item") + + def test_post_product(self): + """ + Verify that necessary arguments are passed for + needed for product to be created. + """ + + response_post_product = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + missing_price='Call her A', + title='Almost Ours', + category="I'm taking her\ + to coffee")), + content_type='application/json', + follow_redirects=True + ) + response_unpack = json.loads( + response_post_product.get_data().decode(sys.getdefaultencoding()) + ) + fail_resp = "You are not \ + allowed to give out stuff for free" + + self.assertIn(fail_resp, response_unpack['message']['price'], + msg="Fails to request \ + user for required arguments") + + def test_make_sensible_post_for_product(self): + """ + Verify that a new product can be created. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + title='Loose a Screw', + price=7854, + category="Vodkaless Alcohol")), + content_type='application/json', + follow_redirects=True + ) + + self.assertTrue(response_post_sale.status_code is 201, + msg="Fails to create new product") + + def test_post_product_using_unknown_details(self): + """ + Verify that unknown data arguments do not create new details + for the new sale. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + I_='Somebody I know', + got=354, + drunk='Baby Soap', + a_little='absent')), + content_type='application/json', + follow_redirects=True + ) + response_unpack = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + + self.assertRaises(KeyError, lambda: response_unpack[ + 'sale']['drunk']), + "Fails to ignore unknown product details" + + def test_post_product_really_creates_product(self): + """ + Verify that the created product gets a uri. + This uri should be added to those of existing products. + """ + + response_post_sale = self.app.post(self.path, + headers=self.headers, + data=json.dumps(dict( + title='Call me A', + price=354, + category='Bubbless Soap')), + content_type='application/json', + follow_redirects=True + ) + response_unpack_post = json.loads( + response_post_sale.get_data().decode(sys.getdefaultencoding()) + ) + + # Request we get from fetch-all-products + response_unpack_get = json.loads( + self.response_get.get_data().decode(sys.getdefaultencoding()) + ) + + # Uri we created for this product + product_uri = int(response_unpack_post['product']['url'][-1]) + + # Uri we had last + last_product_known = int( + response_unpack_get[ + 'product'][-1]['url'][-1]) + 1 + + self.assertEqual(product_uri, last_product_known, + msg="Fails to add new sale to existing sales") + + def test_put_for_Products(self): + """ + We are 'not supposed to be able' to add + data to the Proucts resource. + 'Put' should fail. + """ + what_we_expect = "The method is not allowed for the requested URL." + resput_sales = self.app.put(self.path, + headers=self.headers, + data=json.dumps(dict( + this=1, + thing=2, + fails=3)), + follow_redirects=True + ) + response_unpack = json.loads( + resput_sales.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertTrue(response_unpack['message'] == what_we_expect, + msg="Fails to give \ + error for use of a nonexistent method") + + +class BasicSingleProductTests(BasicProductTests): + def test_guess_path_to_product(self): + guessed_path = self.path + '/' + str(randint(2345, 998888)) + response = self.app.get(guessed_path, + headers=self.headers, + follow_redirects=True + ) + + self.assertTrue(response.status_code == 404, + msg="Failed to return\ + 'not found error' for nonexistent url" + ) + + def test_product_output_uri(self): + """ + Check that the Product url element is added from the path + leading to the product. + """ + + response_unpack = json.loads( + self.response_get_product.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertEqual(response_unpack['product']['url'], + self.single_path) + + def test_delete_for_missing_resource(self): + guessed_path = self.path + '/' + str(randint(2345, 998888)) + response = self.app.get(guessed_path, + headers=self.headers, + follow_redirects=True + ) + + self.assertFalse(response.status_code == 200, + msg="Deletes a nonexistent sale" + ) + + def test_delete_product(self): + + # Try not remove 'path/../../1', I think every test around wants her + rm_path = self.path + '/' + '2' + resdel_prod = self.app.delete(rm_path, + headers=self.headers, + follow_redirects=True + ) + response_unpack = json.loads( + resdel_prod.get_data(). + decode(sys.getdefaultencoding()) + ) + resdel_prod = self.app.get(rm_path, + headers=self.headers, + follow_redirects=True + ) + # Assert delete + self.assertDictEqual(response_unpack, {"Status": True}, + msg="Failed to delete sale" + ) + + # Assert sale is discarded + self.assertNotEqual(resdel_prod.status_code, 404, + msg="Failed to remove deleted item from records" + ) + + def test_get_product_ouput(self): + # Product is received wrapped in dict + + response_unpack = json.loads( + self.response_get_product.get_data(). + decode(sys.getdefaultencoding()) + ) + + self.assertTrue(isinstance(response_unpack, dict)) + + def test_put_product(self): + """ + Verify an existing product can be updated + """ + resput_single_prod = self.app.put(self.single_path, + headers=self.headers, + data=json.dumps(dict( + this=1, + is_=2, + testing=3)), + content_type='application/json', + follow_redirects=True + ) + success = 200 + """ response_unpack = json.loads( + resput_single_sale.get_data(). + decode(sys.getdefaultencoding()) + ) """ + self.assertEqual(resput_single_prod.status_code, success) + + def test_put_product_using_bad_url(self): + """ + Verify a resource can be updated + """ + path = self.path + '/' + '664' + resput_single_prod = self.app.put(path, + headers=self.headers, + data=json.dumps(dict( + one=1, + two=2, + JUMP=3)), + follow_redirects=True + ) + missing = 404 + self.assertEqual(resput_single_prod.status_code, missing) diff --git a/requirements.txt b/requirements.txt index 5a4f142..438869f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ -aniso8601==3.0.2 -certifi==2018.10.15 -chardet==3.0.4 -Click==7.0 -coverage==4.5.1 -coveralls==1.5.1 -docopt==0.6.2 -Flask==1.0.2 -Flask-HTTPAuth==3.2.4 -Flask-RESTful==0.3.6 -idna==2.7 -itsdangerous==0.24 -Jinja2==2.10 -MarkupSafe==1.0 -pytz==2018.5 -requests==2.20.0 -six==1.11.0 -urllib3==1.24 -Werkzeug==0.14.1 + aniso8601==3.0.2 +-certifi==2018.10.15 +-chardet==3.0.4 + Click==7.0 +-coverage==4.5.1 +-coveralls==1.5.1 +-docopt==0.6.2 + Flask==1.0.2 + Flask-HTTPAuth==3.2.4 + Flask-RESTful==0.3.6 +-idna==2.7 + itsdangerous==0.24 + Jinja2==2.10 + MarkupSafe==1.0 + pytz==2018.5 +-requests==2.20.0 + six==1.11.0 +-urllib3==1.24 + Werkzeug==0.14.1 From 8f27ac24543f7dd78478687f7d34533ab7899317 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:02:50 +0300 Subject: [PATCH 23/66] changed before-build --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b995082..25b818a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ python: - "3.6" cache: pip -install: +before_script: + - easy_install distribute - pip install -r requirements.txt + script: - pytest From 52287aa8edb80fc4596020070b6011a2a931b785 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:07:01 +0300 Subject: [PATCH 24/66] changed before-build --- .travis.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25b818a..598acde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,15 @@ python: - "3.6" cache: pip -before_script: - - easy_install distribute +install: - pip install -r requirements.txt +script: + - coverage run -m pytest + - py.test --cov=project + +after_success: + - coveralls + script: - pytest From 2e57e8960c36dc0bb343328c8b86c8d4ef4b947b Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:15:50 +0300 Subject: [PATCH 25/66] changed before-build --- requirements.txt | 174 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 155 insertions(+), 19 deletions(-) diff --git a/requirements.txt b/requirements.txt index 438869f..ce93fda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,155 @@ - aniso8601==3.0.2 --certifi==2018.10.15 --chardet==3.0.4 - Click==7.0 --coverage==4.5.1 --coveralls==1.5.1 --docopt==0.6.2 - Flask==1.0.2 - Flask-HTTPAuth==3.2.4 - Flask-RESTful==0.3.6 --idna==2.7 - itsdangerous==0.24 - Jinja2==2.10 - MarkupSafe==1.0 - pytz==2018.5 --requests==2.20.0 - six==1.11.0 --urllib3==1.24 - Werkzeug==0.14.1 +AdvancedHTTPServer==2.0.11 +ajpy==0.0.4 +alembic==1.0.0 +aniso8601==3.0.2 +asn1crypto==0.24.0 +atomicwrites==1.2.1 +attrs==18.2.0 +basemap==1.1.0 +bcrypt==3.1.4 +beautifulsoup4==4.6.3 +binwalk==2.1.2 +blinker==1.4 +boltons==18.0.1 +Brlapi==0.6.7 +Brotli==1.0.6 +certifi==2018.8.24 +cffi==1.11.5 +chardet==3.0.4 +chrome-gnome-shell==0.0.0 +click==7.0.dev0 +colorama==0.3.7 +crcelk==1.2 +cryptography==2.3.1 +cupshelpers==1.0 +cycler==0.10.0 +debtags==2.1 +decorator==4.3.0 +distro==1.3.0 +dnspython==1.15.0 +ecdsa==0.13 +email-validator==1.0.3 +entrypoints==0.2.3.post3 +Flask==1.0.2 +Flask-HTTPAuth==3.2.4 +Flask-RESTful==0.3.6 +Forensic1394==0.1 +geoip2==2.9.0 +geojson==2.4.0 +graphene==2.0.1 +graphene-sqlalchemy==2.0.0 +graphql-core==2.1 +graphql-relay==0.4.5 +h11==0.8.1 +h2==3.0.1 +hashID==3.1.4 +hpack==3.0.0 +html5lib==1.0.1 +httplib2==0.11.3 +hyperframe==5.1.0 +icalendar==4.0.2 +idna==2.7 +IPy==0.83 +iso8601==0.1.12 +itsdangerous==0.24 +Jinja2==2.10 +jsonschema==2.6.0 +kaitaistruct==0.8 +keyring==15.1.0 +keyrings.alt==3.1 +kiwisolver==1.0.1 +ldap3==2.4.1 +louis==3.7.0 +lxml==4.2.5 +Mako==1.0.7 +Markdown==3.0.1 +MarkupSafe==1.0 +matplotlib==3.0.0 +maxminddb==1.4.1 +mitmproxy==4.0.4 +mock==2.0.0 +more-itertools==4.3.0 +msgpack==0.5.6 +msgpack-python==0.5.6 +mysqlclient==1.3.10 +numpy==1.15.2 +olefile==0.46 +paramiko==2.4.2 +passlib==1.7.1 +patator==0.7 +pbr==4.3.0 +Pillow==5.2.0 +pluggy==0.8.0 +pluginbase==0.7 +ply==3.11 +promise==2.2.1 +psycopg2==2.7.5 +py==1.7.0 +py-gfm==0.1.4 +pyasn1==0.4.4 +pycairo==1.16.2 +pycparser==2.19 +pycrypto==2.6.1 +pycryptodomex==3.6.1 +pycups==1.9.73 +pycurl==7.43.0.1 +pyenchant==2.0.0 +PyGObject==3.30.1 +pygtkspellcheck==4.0.5 +PyNaCl==1.3.0 +PyOpenGL==3.1.0 +pyOpenSSL==18.0.0 +pyotp==2.2.6 +pyparsing==2.2.2 +pyperclip==1.6.4 +pyproj==1.9.5.1 +pyqtgraph==0.10.0 +pyserial==3.4 +pyshp==1.2.12 +pysmbc==1.0.15.6 +pysmi==0.2.2 +pysnmp==4.4.6 +PySocks==1.6.8 +pytest==3.9.1 +python-apt==1.7.0 +python-dateutil==2.7.3 +python-debian==0.1.33 +python-debianbts==2.7.2 +python-editor==1.0.3 +python-pam==1.8.4 +pytz==2018.5 +pyxdg==0.25 +PyYAML==3.13 +reportbug==7.5.0 +requests==2.19.1 +requests-file==1.4.3 +ruamel.yaml==0.15.34 +Rx==1.6.1 +scipy==1.1.0 +SecretStorage==2.3.1 +singledispatch==3.4.0.3 +six==1.11.0 +smoke-zephyr==1.3.3 +sortedcontainers==2.0.4 +SQLAlchemy==1.2.12 +stevedore==1.29.0 +tabulate==0.8.2 +termcolor==1.1.0 +termineter==1.0.4 +tld==0.9.1 +tornado==5.1.1 +tzlocal==1.5.1 +unattended-upgrades==0.1 +urllib3==1.23 +urwid==2.0.1 +virtualenv==16.0.0 +virtualenv-clone==0.3.0 +virtualenvwrapper==4.8.2 +wafw00f==0.9.5 +wapiti3==3.0.1 +webencodings==0.5 +websocket-client==0.49.0 +Werkzeug==0.14.1 +wsproto==0.11.0 +XlsxWriter==1.1.1 +yaswfp==0.9.3 From c2b4185d04a0446d7f01097848a49ce895f077d0 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:34:29 +0300 Subject: [PATCH 26/66] changed before-build --- requirements.txt | 150 +++-------------------------------------------- 1 file changed, 7 insertions(+), 143 deletions(-) diff --git a/requirements.txt b/requirements.txt index ce93fda..5a4f142 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,155 +1,19 @@ -AdvancedHTTPServer==2.0.11 -ajpy==0.0.4 -alembic==1.0.0 aniso8601==3.0.2 -asn1crypto==0.24.0 -atomicwrites==1.2.1 -attrs==18.2.0 -basemap==1.1.0 -bcrypt==3.1.4 -beautifulsoup4==4.6.3 -binwalk==2.1.2 -blinker==1.4 -boltons==18.0.1 -Brlapi==0.6.7 -Brotli==1.0.6 -certifi==2018.8.24 -cffi==1.11.5 +certifi==2018.10.15 chardet==3.0.4 -chrome-gnome-shell==0.0.0 -click==7.0.dev0 -colorama==0.3.7 -crcelk==1.2 -cryptography==2.3.1 -cupshelpers==1.0 -cycler==0.10.0 -debtags==2.1 -decorator==4.3.0 -distro==1.3.0 -dnspython==1.15.0 -ecdsa==0.13 -email-validator==1.0.3 -entrypoints==0.2.3.post3 +Click==7.0 +coverage==4.5.1 +coveralls==1.5.1 +docopt==0.6.2 Flask==1.0.2 Flask-HTTPAuth==3.2.4 Flask-RESTful==0.3.6 -Forensic1394==0.1 -geoip2==2.9.0 -geojson==2.4.0 -graphene==2.0.1 -graphene-sqlalchemy==2.0.0 -graphql-core==2.1 -graphql-relay==0.4.5 -h11==0.8.1 -h2==3.0.1 -hashID==3.1.4 -hpack==3.0.0 -html5lib==1.0.1 -httplib2==0.11.3 -hyperframe==5.1.0 -icalendar==4.0.2 idna==2.7 -IPy==0.83 -iso8601==0.1.12 itsdangerous==0.24 Jinja2==2.10 -jsonschema==2.6.0 -kaitaistruct==0.8 -keyring==15.1.0 -keyrings.alt==3.1 -kiwisolver==1.0.1 -ldap3==2.4.1 -louis==3.7.0 -lxml==4.2.5 -Mako==1.0.7 -Markdown==3.0.1 MarkupSafe==1.0 -matplotlib==3.0.0 -maxminddb==1.4.1 -mitmproxy==4.0.4 -mock==2.0.0 -more-itertools==4.3.0 -msgpack==0.5.6 -msgpack-python==0.5.6 -mysqlclient==1.3.10 -numpy==1.15.2 -olefile==0.46 -paramiko==2.4.2 -passlib==1.7.1 -patator==0.7 -pbr==4.3.0 -Pillow==5.2.0 -pluggy==0.8.0 -pluginbase==0.7 -ply==3.11 -promise==2.2.1 -psycopg2==2.7.5 -py==1.7.0 -py-gfm==0.1.4 -pyasn1==0.4.4 -pycairo==1.16.2 -pycparser==2.19 -pycrypto==2.6.1 -pycryptodomex==3.6.1 -pycups==1.9.73 -pycurl==7.43.0.1 -pyenchant==2.0.0 -PyGObject==3.30.1 -pygtkspellcheck==4.0.5 -PyNaCl==1.3.0 -PyOpenGL==3.1.0 -pyOpenSSL==18.0.0 -pyotp==2.2.6 -pyparsing==2.2.2 -pyperclip==1.6.4 -pyproj==1.9.5.1 -pyqtgraph==0.10.0 -pyserial==3.4 -pyshp==1.2.12 -pysmbc==1.0.15.6 -pysmi==0.2.2 -pysnmp==4.4.6 -PySocks==1.6.8 -pytest==3.9.1 -python-apt==1.7.0 -python-dateutil==2.7.3 -python-debian==0.1.33 -python-debianbts==2.7.2 -python-editor==1.0.3 -python-pam==1.8.4 pytz==2018.5 -pyxdg==0.25 -PyYAML==3.13 -reportbug==7.5.0 -requests==2.19.1 -requests-file==1.4.3 -ruamel.yaml==0.15.34 -Rx==1.6.1 -scipy==1.1.0 -SecretStorage==2.3.1 -singledispatch==3.4.0.3 +requests==2.20.0 six==1.11.0 -smoke-zephyr==1.3.3 -sortedcontainers==2.0.4 -SQLAlchemy==1.2.12 -stevedore==1.29.0 -tabulate==0.8.2 -termcolor==1.1.0 -termineter==1.0.4 -tld==0.9.1 -tornado==5.1.1 -tzlocal==1.5.1 -unattended-upgrades==0.1 -urllib3==1.23 -urwid==2.0.1 -virtualenv==16.0.0 -virtualenv-clone==0.3.0 -virtualenvwrapper==4.8.2 -wafw00f==0.9.5 -wapiti3==3.0.1 -webencodings==0.5 -websocket-client==0.49.0 +urllib3==1.24 Werkzeug==0.14.1 -wsproto==0.11.0 -XlsxWriter==1.1.1 -yaswfp==0.9.3 From c6324f684eab800f65f33c8e18e6bd03c8a1fe4b Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:42:05 +0300 Subject: [PATCH 27/66] changed before-build --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e42d1ce..1c9ed0e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # storeManager [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) - - +#[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) +[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg?branch=project-app)](https://coveralls.io/github/hogum/storeManager?branch=project-app) From a7eeb4388e0a5f702ff75e23446d8524fccc638f Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:55:20 +0300 Subject: [PATCH 28/66] solve for build-status --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c9ed0e..961ef08 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) #[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) -[![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg?branch=project-app)](https://coveralls.io/github/hogum/storeManager?branch=project-app) + +[![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) From 916b0ce3998c3f2f6d1c0a860857130f20b11404 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 02:57:09 +0300 Subject: [PATCH 29/66] solve for build-status --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 961ef08..aef9d29 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # storeManager [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) -#[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) From a4fa71392e653fb468e29eef91d811d1f6515b40 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 16:47:50 +0300 Subject: [PATCH 30/66] set up to Initialize app --- app/__init__.py | 13 +++++++++++++ requirements.txt | 1 + 2 files changed, 14 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index e69de29..fa84e67 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -0,0 +1,13 @@ +import os +from flask import Flask + +# Initialize application +my_app = Flask(__name__, static_folder="") + +# app configuration +my_app_settings = os.getenv( + 'APP_SETTINGS', + 'my_app.config.DevelopmentConfig' +) +my_app.config.from_object(my_app_settings) + diff --git a/requirements.txt b/requirements.txt index 5a4f142..a1918d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ docopt==0.6.2 Flask==1.0.2 Flask-HTTPAuth==3.2.4 Flask-RESTful==0.3.6 +gunicorn==19.9.0 idna==2.7 itsdangerous==0.24 Jinja2==2.10 From 1bb8aefda800f366b4165d3d2f5ec32663e4c3cf Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 16:53:20 +0300 Subject: [PATCH 31/66] initial deploy --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..982d380 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn app:my_app From 6f4d3a2d3661496d59cb934527426dae24d7bb9d Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 17:15:32 +0300 Subject: [PATCH 32/66] resolve for app initialzation --- app/__init__.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index fa84e67..5c41953 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,13 +1,8 @@ -import os -from flask import Flask -# Initialize application -my_app = Flask(__name__, static_folder="") +from flask import Flask -# app configuration -my_app_settings = os.getenv( - 'APP_SETTINGS', - 'my_app.config.DevelopmentConfig' -) -my_app.config.from_object(my_app_settings) +# Define the application object +my_app = Flask(__name__) +# Configurations +my_app.config.from_object('config') From 2eed8839ffc2009f385e6747b5e048246d25cc52 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 17:25:31 +0300 Subject: [PATCH 33/66] resolve for app initialzation --- app/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 5c41953..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,8 +0,0 @@ - -from flask import Flask - -# Define the application object -my_app = Flask(__name__) - -# Configurations -my_app.config.from_object('config') From da735f482712435767d81af59bc4eec164a3928a Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 17:41:45 +0300 Subject: [PATCH 34/66] Coverage config --- .travis.yml | 7 ++----- requirements.txt | 4 +++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 598acde..417b403 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,12 +6,9 @@ cache: pip install: - pip install -r requirements.txt + script: - - coverage run -m pytest - - py.test --cov=project + - py.test coveralls/tests.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing after_success: - coveralls - -script: - - pytest diff --git a/requirements.txt b/requirements.txt index a1918d5..97444a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ aniso8601==3.0.2 certifi==2018.10.15 chardet==3.0.4 Click==7.0 -coverage==4.5.1 +coverage==4.0.3 coveralls==1.5.1 docopt==0.6.2 Flask==1.0.2 @@ -13,7 +13,9 @@ idna==2.7 itsdangerous==0.24 Jinja2==2.10 MarkupSafe==1.0 +python-coveralls==2.9.1 pytz==2018.5 +PyYAML==3.13 requests==2.20.0 six==1.11.0 urllib3==1.24 From 2cf3d087842ce0afe9c55af4e90dd013f80a81f7 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 17:50:48 +0300 Subject: [PATCH 35/66] Coverage config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 417b403..a1be49e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,9 @@ cache: pip install: - pip install -r requirements.txt - script: - - py.test coveralls/tests.py --doctest-modules --pep8 coveralls -v --cov coveralls --cov-report term-missing + - coverage run -m pytest + - py.test --cov=app after_success: - coveralls From 72081f7101dbb2a0224842d84e3c588966dd776e Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 17:57:30 +0300 Subject: [PATCH 36/66] Coverage issue solved --- .coverage | 1 + requirements.txt | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000..573ee9d --- /dev/null +++ b/.coverage @@ -0,0 +1 @@ +!coverage.py: This is a private format, don't read it directly!{"lines":{"/root/Documents/save_me/storeManager/app/__init__.py":[1],"/root/Documents/save_me/storeManager/app/tests/__init__.py":[1],"/root/Documents/save_me/storeManager/app/tests/test_basic.py":[2,3,4,5,6,7,8,10,13,14,35,41,48,55,65,76,98,122,141,165,201,223,224,227,238,252,262,282,302,328,340,15,16,17,19,20,23,24,25,29,30,31,32,44,46,68,69,72,73,58,59,62,63,128,129,130,131,132,133,134,135,138,139,82,83,84,85,86,87,88,89,91,92,95,96,104,105,106,107,108,109,110,111,113,114,116,118,120,147,148,149,150,151,152,153,154,155,157,158,161,162,163,172,173,174,175,176,177,178,179,181,182,186,187,191,194,195,196,198,199,206,207,208,209,210,211,212,213,215,216,217,220,36,37,38,51,52,329,330,331,332,335,336,305,306,307,308,310,311,312,314,315,316,319,320,324,325,225,266,267,268,269,270,271,272,273,275,280,286,287,288,289,290,291,292,293,295,300,228,229,230,231,234,235,255,256,257,260,244,245,246,249,250],"/root/Documents/save_me/storeManager/app/app.py":[2,3,4,5,7,8,9,10,14,15,18,19,20,23,24,25,26,27,28,30,31,35,36,39,40,41,44,45,46,47,48,49,51,52,59,60,61,62,63,64,69,70,71,72,73,74,75,81,82,84,85,86,91,92,93,94,95,96,101,102,103,104,105,111,112,113,116,117,125,127,128,129,131,132,134,135,137,138,140,141,146,150,151,155,163,171,179,187,188,190,245,250,286,287,290,301,309,326,340,341,342,368,374,394,395,396,416,426,442,455,456,457,460,461,462,463,173,174,196,197,198,199,200,203,204,205,207,208,209,211,212,213,217,218,219,220,222,223,224,226,227,228,230,231,232,234,236,237,239,240,241,243,247,157,158,291,292,293,294,295,296,297,299,302,304,307,251,254,255,256,257,258,259,260,261,262,263,264,265,266,270,272,273,274,279,282,283,181,182,183,305,327,328,331,333,336,310,311,314,317,320,321,324,315,343,344,345,346,349,350,351,354,355,357,358,361,362,363,366,370,371,397,398,399,402,403,406,407,410,411,414,417,419,423,165,166,167,376,379,380,381,382,383,384,387,390,391,420,444,446,448,451,428,430,432,434,435,439,431],"/root/Documents/save_me/storeManager/app/tests/test_basic_.py":[1,2,3,4,5,6,7,9,12,13,41,53,63,71,78,90,116,134,158,193,218,219,231,245,256,282,292,312,14,15,16,18,19,22,24,25,27,28,29,31,32,33,37,38,83,84,86,88,45,47,49,51,74,75,56,57,60,61,67,68,121,122,123,124,125,126,127,128,131,132,96,97,98,99,100,102,103,104,106,107,110,112,114,164,165,166,167,168,169,170,171,173,174,178,179,183,186,187,188,190,191,140,141,142,143,144,145,146,147,148,150,151,154,155,156,199,200,201,202,203,204,205,206,208,209,210,213,215,246,247,248,249,252,253,259,260,261,262,264,265,266,268,269,270,273,274,278,279,285,286,287,290,220,221,222,223,226,228,237,238,239,242,243,296,297,298,299,300,301,302,303,305,310,316,317,318,319,320,321,322,323,325,326]}} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 97444a4..31369dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ aniso8601==3.0.2 +atomicwrites==1.2.1 +attrs==18.2.0 certifi==2018.10.15 chardet==3.0.4 Click==7.0 -coverage==4.0.3 +coverage==4.5.1 coveralls==1.5.1 docopt==0.6.2 Flask==1.0.2 @@ -13,6 +15,13 @@ idna==2.7 itsdangerous==0.24 Jinja2==2.10 MarkupSafe==1.0 +mock==2.0.0 +more-itertools==4.3.0 +pbr==5.0.0 +pluggy==0.8.0 +py==1.7.0 +pytest==3.9.1 +pytest-cov==2.6.0 python-coveralls==2.9.1 pytz==2018.5 PyYAML==3.13 From 8da784d62456c1cf161f099c28642e8a9cc40d76 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 18:57:30 +0300 Subject: [PATCH 37/66] Change config settings to try deploy --- Procfile | 2 +- app/__init__.py | 8 ++++++++ app/config.py | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 app/config.py diff --git a/Procfile b/Procfile index 982d380..5a3a0ab 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn app:my_app +web: gunicorn - 4 app.app:my_app diff --git a/app/__init__.py b/app/__init__.py index e69de29..6967586 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -0,0 +1,8 @@ +import os +from flask import Flask + +my_app = Flask(__name__) +my_app.config.from_object(os.environ['APP_SETTINGS']) + +return my_app + diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..c1498b1 --- /dev/null +++ b/app/config.py @@ -0,0 +1,24 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + + +class Config(object): + DEBUG = False + TESTING = False + CSRF_ENABLED = True + SECRET_KEY = 'secrtet-key-secrete' + + +class ProductionConfig(Config): + DEBUG = False + + +class StagingConfig(Config): + DEVELOPMENT = True + DEBUG = True + + +class DevelopmentConfig(Config): + DEVELOPMENT = True + DEBUG = True + From 531ddaa90f40338769d1f31cb3d78deb743b9569 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:11:13 +0300 Subject: [PATCH 38/66] Change config settings to try deploy --- Procfile | 2 +- app/__init__.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Procfile b/Procfile index 5a3a0ab..7f4dd6d 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn - 4 app.app:my_app +web: gunicorn "app.app:my_app" diff --git a/app/__init__.py b/app/__init__.py index 6967586..647dbb9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,7 +2,10 @@ from flask import Flask my_app = Flask(__name__) -my_app.config.from_object(os.environ['APP_SETTINGS']) - -return my_app +#my_app.config.from_object(os.environ['APP_SETTINGS']) +my_app_settings = os.getenv( + 'APP_SETTINGS', + 'project.config.DevConfig' +) +app.config.from_object(my_app_settings) From b912fca6970f956429c4d4a2bf3cb0655498c7a1 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:16:18 +0300 Subject: [PATCH 39/66] Traceback actual gunicorn error --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 7f4dd6d..be5721c 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn "app.app:my_app" +web: gunicorn app.app:my_app --preload --workers 4 From 64540e14ed853259a597c4ee0cb0a93474ead144 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:18:52 +0300 Subject: [PATCH 40/66] checked for missing module error --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 647dbb9..a1659a3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -8,4 +8,4 @@ 'APP_SETTINGS', 'project.config.DevConfig' ) -app.config.from_object(my_app_settings) +my_app.config.from_object(my_app_settings) From 32f8f9d2ac9aa6aa496e651638d67ecea6762041 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:22:16 +0300 Subject: [PATCH 41/66] checked missing config module --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index a1659a3..53f2d15 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -6,6 +6,6 @@ my_app_settings = os.getenv( 'APP_SETTINGS', - 'project.config.DevConfig' + 'app.config.DevelopmentConfig' ) my_app.config.from_object(my_app_settings) From a558945a46e276d936b21fe1d6e310223e9231f1 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:51:22 +0300 Subject: [PATCH 42/66] add heroku flat style --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index aef9d29..3299d66 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # storeManager -[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager) -[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) +[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager)[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) - +[![Heroku](http://store-man90.herokuapp.com/?app=store-man90&root=index.html)] [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) From ab18b35f8a9b9973e01f2ae86e0807533b0e11dc Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 19:56:24 +0300 Subject: [PATCH 43/66] add heroku flat style --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3299d66..a4d6b08 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ [![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager)[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) + + [![Heroku](http://store-man90.herokuapp.com/?app=store-man90&root=index.html)] [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) From 3f5dcd87319e4d2365ffc1203ca14f97177bf893 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 21 Oct 2018 20:00:33 +0300 Subject: [PATCH 44/66] add heroku flat style --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index a4d6b08..6c5fab0 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) - -[![Heroku](http://store-man90.herokuapp.com/?app=store-man90&root=index.html)] - [![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/hogum/storeManager) [![GitHub issues](https://img.shields.io/github/issues/hogum/storeManager.svg?style=for-the-badge)](https://github.com/hogum/storeManager/issues) @@ -14,7 +11,7 @@ A web application to help store managers maintain their inventories and manage sale records. - +Try on Heroku: https://store-man90.herokuapp.com/ [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) From 7e52fe8c3beac1a798deb8f0dcebdbaf66591a7c Mon Sep 17 00:00:00 2001 From: root Date: Mon, 22 Oct 2018 21:56:23 +0300 Subject: [PATCH 45/66] feature [#161361755] Move all test to the root folder --- {app/tests => tests}/__init__.py | 0 {app/tests => tests}/test_basic.py | 0 {app/tests => tests}/test_basic_.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {app/tests => tests}/__init__.py (100%) rename {app/tests => tests}/test_basic.py (100%) rename {app/tests => tests}/test_basic_.py (100%) diff --git a/app/tests/__init__.py b/tests/__init__.py similarity index 100% rename from app/tests/__init__.py rename to tests/__init__.py diff --git a/app/tests/test_basic.py b/tests/test_basic.py similarity index 100% rename from app/tests/test_basic.py rename to tests/test_basic.py diff --git a/app/tests/test_basic_.py b/tests/test_basic_.py similarity index 100% rename from app/tests/test_basic_.py rename to tests/test_basic_.py From d5d20103b833a5720e170cbb2a89e6217ac74ffe Mon Sep 17 00:00:00 2001 From: root Date: Mon, 22 Oct 2018 22:00:53 +0300 Subject: [PATCH 46/66] chore [#161395208] Rename tests to show resources tested --- tests/{test_basic_.py => test_products.py} | 0 tests/{test_basic.py => test_sales.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{test_basic_.py => test_products.py} (100%) rename tests/{test_basic.py => test_sales.py} (100%) diff --git a/tests/test_basic_.py b/tests/test_products.py similarity index 100% rename from tests/test_basic_.py rename to tests/test_products.py diff --git a/tests/test_basic.py b/tests/test_sales.py similarity index 100% rename from tests/test_basic.py rename to tests/test_sales.py From 4be09b5866d17253609996dae6575b89c9dd1506 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 16:01:51 +0300 Subject: [PATCH 47/66] feature [#161413576] create a model for the sales resource --- app/api/models/sales.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/api/models/sales.py diff --git a/app/api/models/sales.py b/app/api/models/sales.py new file mode 100644 index 0000000..c485649 --- /dev/null +++ b/app/api/models/sales.py @@ -0,0 +1,34 @@ +from datetime import datetime + +sales = [] + +# example of a sale record + +sale_example = { + 'sales_record': 1, + 'attendant': u'Attendant One', + + # Customer contacts + 'name': u'Customer One', + 'address': u'45 bright street', + 'contact': [u'+00012345', u'customer_c@example.co'], + + # Transaction Info + 'product': u'Spam 2.0', + 'quantity': 1, + 'date': datetime(2018, 6, 6, 5, 28, 56, 243), + 'description': u'Hot with extra extra spam', + 'transaction_type': u'Cash on Delivery', + 'complete': False, + + 'gifts': 100, # Anything to reduce sale e.g discounts + 'price': 276, +} + + +class Sales(): + @staticmethod + def salesList(): + sales.append(sale_example) + + return sales From 8137048d9262576c5553ab72967f402527f9f44d Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 16:03:04 +0300 Subject: [PATCH 48/66] feature [#161413600] create a model for the product resource --- app/api/models/products.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/api/models/products.py diff --git a/app/api/models/products.py b/app/api/models/products.py new file mode 100644 index 0000000..9aa52cc --- /dev/null +++ b/app/api/models/products.py @@ -0,0 +1,22 @@ +from datetime import datetime + + +products = [] + +product_record_example = { + 'title': 'Innocent Coconut Water', + 'category': 'Women sure can Sleep', + 'price': 94534, + 'in stock': True, + 'date received': datetime( + 2018, 5, 30, 22, 12, 38, 649), + 'id': 2 +} + + +class Products(object): + @staticmethod + def productsList(): + products.append(product_record_example) + + return products From 4d09ee75c47ef72407480b70f5497f9ebeb8b8d3 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 16:22:49 +0300 Subject: [PATCH 49/66] feature [#161414536] create a VIEW for the product resource --- app/api/views/product_views.py | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 app/api/views/product_views.py diff --git a/app/api/views/product_views.py b/app/api/views/product_views.py new file mode 100644 index 0000000..f543caf --- /dev/null +++ b/app/api/views/product_views.py @@ -0,0 +1,68 @@ +from flask_restful import Resource, reqparse, marshal, fields +from models import products +from datetime import datetime + +products = products.Products + + +class ProductsAPI(Resource): + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('title', type=str, required=True, + help="Please add a title", + location='json' + ) + + self.parse.add_argument('category', type=str, + default='None', + location='json' + ) + + self.parse.add_argument('price', type=int, + required=True, + help="You are not \ + allowed to give out stuff for free", + location='json' + ) + + self.parse.add_argument('in stock', type=bool, + default=True, + location='json' + ) + + super(ProductsAPI, self).__init__() + + def get(self): + return { + 'product': [marshal(product, product_fields) + for product in products.products] + } + + def post(self): + elements = self.parse.parse_args() + + product = { + 'title': elements['title'], + 'category': elements['category'], + 'price': elements['price'], + 'in stock': True, + 'date received': datetime.now(), + 'id': products[-1]['id'] + 1 + } + + products.append(product) + + return { + 'product': marshal(product, product_fields) + }, 201 + + +product_fields = { + 'title': fields.String, + 'category': fields.String, + 'price': fields.Integer, + 'in stock': fields.Boolean, + 'date received': fields.DateTime, + 'url': fields.Url('product') # Ensure user doen't + # need to know how to generate url +} From e43b99da212f325b934f4e6a31232252fe45eec2 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 16:25:08 +0300 Subject: [PATCH 50/66] feature [#161414982] create a VIEW for the sales resource --- app/api/views/sale_views.py | 143 ++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 app/api/views/sale_views.py diff --git a/app/api/views/sale_views.py b/app/api/views/sale_views.py new file mode 100644 index 0000000..70842bf --- /dev/null +++ b/app/api/views/sale_views.py @@ -0,0 +1,143 @@ +from flask_restful import Resource, reqparse, marshal, fields +from models import sales +from datetime import datetime + +sales = sales.Sales + + +class SalesAPI(Resource): + def __init__(self): + + self.parse = reqparse.RequestParser() + self.parse.add_argument('attendant', type=str, + required=True, + help="A sale need's an attendant", + location='json') + + # Customer details + self.parse.add_argument('name', type=str, + default='Anonymous', + location='json') + + self.parse.add_argument('address', type=str, + default='Unknown', + location='json') + + self.parse.add_argument('contact', type=list, + default=['phone', 'email'], + location='json') + + # transaction details + + self.parse.add_argument('product', type=str, + required=True, + help="A product to sell sure has a name", + location='json') + + self.parse.add_argument('quantity', type=int, + help="How many items", default=1, + location='json') + + self.parse.add_argument('transaction_type', type=str, + default='Cash on Delivery', + location='json') + + self.parse.add_argument('gifts', type=int, + default='0', + location='json') + + self.parse.add_argument('price', type=int, required=True, + help="""You sure are not giving it away for free + """, + location='json') + + self.parse.add_argument('description', type=str, + default='', + location='json') + + super(SalesAPI, self).__init__() + + def get(self): + return { + 'sales': [marshal(sale, sales.sale_fields) for sale in sales.sales] + } + + def post(self): + elements = self.parse.parse_args() + + sale = { + 'sales_record': sales[-1]['sales_record'] + 1, + 'attendant': elements['attendant'], + 'name': elements['name'], + 'address': elements['address'], + 'contact': elements['contact'], + 'product': elements['product'], + 'quantity': elements['quantity'], + 'date': datetime.now(), + 'description': elements['description'], + 'transaction_type': elements['transaction_type'], + 'complete': False, + 'gifts': 0, + 'price': elements['price'] + } + + # Find total cost of sale + sale.update( + { + 'total': sale.get('quantity') * + sale.get('price') - + sale.get('gifts') + } + ) + + # Add new sale to sales record + sales.append(sale) + + return { + 'sale': marshal(sale, sale_fields) + }, 201 + + +sale_fields = { + 'sales_uri': fields.Url('sale'), + 'attendant': fields.String, + 'gifts': fields.Integer, + 'price': fields.Integer, + 'total': fields.Integer +} + +""" + Output nested customer details + """ +sale_fields['customer'] = {} +sale_fields['customer']['Name'] = fields.String(attribute='name') +sale_fields['customer']['Address'] = fields.String(attribute='address') + +# Create list for customer contacts +sale_fields['customer']['Contact'] = fields.List( + fields.String, attribute='contact' +) + + +""" + Nest the transaction info + """ + +sale_fields['transaction_info'] = {} + +sale_fields['transaction_info']['Product'] = fields.String(attribute='product') +sale_fields['transaction_info']['Quantity'] = fields.Integer( + attribute='quantity' +) +sale_fields['transaction_info']['Date'] = fields.DateTime( + attribute='date', dt_format='rfc822' +) +sale_fields['transaction_info']['Description'] = fields.String( + attribute='description' +) +sale_fields['transaction_info']['Transaction_type'] = fields.String( + attribute='transaction_type' +) +sale_fields['transaction_info']['Complete'] = fields.Boolean( + attribute='complete' +) From 46f2f9eb8df4a3a83a4bbc53bb0ed828a4b8c3be Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 17:00:09 +0300 Subject: [PATCH 51/66] feature [#161416118] configure app to run --- app/api/__init__.py | 4 ++++ run.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 app/api/__init__.py diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..d677a2b --- /dev/null +++ b/app/api/__init__.py @@ -0,0 +1,4 @@ +from app import create_app + +def start_app(): + return create_app() diff --git a/run.py b/run.py index eccfc95..a75159e 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ -from app import app +from api import start_app if __name__ == '__main__': - app.my_app.run() + start_app.run() From 849194aad2f20da224470bc5949aa331387b344c Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 17:08:46 +0300 Subject: [PATCH 52/66] feature [#161416449] Solve for module importation errors --- app/api/views/product_views.py | 2 +- app/api/views/sale_views.py | 2 +- app/{ => instance}/config.py | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename app/{ => instance}/config.py (100%) diff --git a/app/api/views/product_views.py b/app/api/views/product_views.py index f543caf..64227aa 100644 --- a/app/api/views/product_views.py +++ b/app/api/views/product_views.py @@ -2,7 +2,7 @@ from models import products from datetime import datetime -products = products.Products +products = products.Products.productsList() class ProductsAPI(Resource): diff --git a/app/api/views/sale_views.py b/app/api/views/sale_views.py index 70842bf..835c9c4 100644 --- a/app/api/views/sale_views.py +++ b/app/api/views/sale_views.py @@ -2,7 +2,7 @@ from models import sales from datetime import datetime -sales = sales.Sales +sales = sales.Sales.salesList() class SalesAPI(Resource): diff --git a/app/config.py b/app/instance/config.py similarity index 100% rename from app/config.py rename to app/instance/config.py From 130177323b488ad95fb3b15473b2ea6dae536939 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 17:12:28 +0300 Subject: [PATCH 53/66] bug [#161416449] Solve for module importation errors --- app/api/app.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/api/app.py diff --git a/app/api/app.py b/app/api/app.py new file mode 100644 index 0000000..494c88c --- /dev/null +++ b/app/api/app.py @@ -0,0 +1,18 @@ +from flask import Flask, Blueprint +from flask_restful import Resource, Api +from views import product_views, sale_views + +store_blueprint = Blueprint("store-man", __name__) + + +def create_app(): + my_app = Flask(__name__) + api = Api(store_blueprint) + + api.add_resource(sale_views.SalesAPI, + '/stman/api/v1.0/sales', endpoint='sales') + api.add_resource(product_views.ProductsAPI, + '/stman/api/v1.0/products', endpoint='products') + + my_app.register_blueprint(store_blueprint) + return my_app From 8dc9a79e83bf7d30e6cc3031adcf7d90d2ba5017 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 17:27:07 +0300 Subject: [PATCH 54/66] bug [#161416449] Solve for module importation errors --- app/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 53f2d15..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +0,0 @@ -import os -from flask import Flask - -my_app = Flask(__name__) -#my_app.config.from_object(os.environ['APP_SETTINGS']) - -my_app_settings = os.getenv( - 'APP_SETTINGS', - 'app.config.DevelopmentConfig' -) -my_app.config.from_object(my_app_settings) From 4ad3b1779b3ba5ea08f039d1eac2c8cf4ba58a37 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 17:36:00 +0300 Subject: [PATCH 55/66] bug [#161417681] Resolve badge conflicts between branches --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 6c5fab0..7a92be1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # storeManager -[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=project-app)](https://travis-ci.com/hogum/storeManager)[![Build Status](https://travis-ci.com/hogum/storeManager.svg?branch=master)](https://travis-ci.com/hogum/storeManager) [![Coverage Status](https://coveralls.io/repos/github/hogum/storeManager/badge.svg)](https://coveralls.io/github/hogum/storeManager) From 8862332e137a9981634567d20185e7e7d41f85ae Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 18:56:06 +0300 Subject: [PATCH 56/66] feature [#161415070] make availbale endpoints for an individual sale record --- app/api/app.py | 5 +++- app/api/views/sale_views.py | 55 ++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/app/api/app.py b/app/api/app.py index 494c88c..693b794 100644 --- a/app/api/app.py +++ b/app/api/app.py @@ -1,5 +1,5 @@ from flask import Flask, Blueprint -from flask_restful import Resource, Api +from flask_restful import Api from views import product_views, sale_views store_blueprint = Blueprint("store-man", __name__) @@ -13,6 +13,9 @@ def create_app(): '/stman/api/v1.0/sales', endpoint='sales') api.add_resource(product_views.ProductsAPI, '/stman/api/v1.0/products', endpoint='products') + api.add_resource(sale_views.SaleAPI, + '/stman/api/v1.0/sales/', + endpoint='sale') my_app.register_blueprint(store_blueprint) return my_app diff --git a/app/api/views/sale_views.py b/app/api/views/sale_views.py index 835c9c4..5554e80 100644 --- a/app/api/views/sale_views.py +++ b/app/api/views/sale_views.py @@ -1,6 +1,7 @@ from flask_restful import Resource, reqparse, marshal, fields from models import sales from datetime import datetime +from flask import abort sales = sales.Sales.salesList() @@ -91,13 +92,65 @@ def post(self): ) # Add new sale to sales record - sales.append(sale) + sales.sales.append(sale) return { 'sale': marshal(sale, sale_fields) }, 201 +class SaleAPI(Resource): + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('attendant', type=str, location='json') + self.parse.add_argument('transaction_info', type=dict, location='json') + self.parse.add_argument('gifts', type=int, location='json') + self.parse.add_argument('total', + type=float, + location='json' + ) + super(SaleAPI, self).__init__() + + def get(self, sales_record): + sale = [sale for sale in sales.sales if sale[ + 'sales_record'] == sales_record] + + if not sale: + abort(404) + + return {'sale': marshal(sale[0], sale_fields)} + + def put(self, sales_record): + sale = [sale for sale + in sales.sales if sale['sales_record'] is sales_record + ] + + if not sale: + abort(404) + + elements = self.parse.parse_args() + + # update any changed element + for key, value in list(elements.items()): + if value: + sale[0][key] = value + + return {'sale': marshal(sale[0], sale_fields)} + + def delete(self, sales_record): + sale = [sale for sale + in sales.sales if sale['sales_record'] is sales_record + ] + + if not sale: + abort(404) + sales.remove(sale[0]) + + return { + 'Effect': True + } + + sale_fields = { 'sales_uri': fields.Url('sale'), 'attendant': fields.String, From b266470a9ff9e801c5ba36237309a0257fed4988 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 19:11:49 +0300 Subject: [PATCH 57/66] feature [#161414822] make availbale endpoints for an individual product record --- .coverage | 2 +- app/.coverage | 1 + app/api/app.py | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 app/.coverage diff --git a/.coverage b/.coverage index 573ee9d..7bc352f 100644 --- a/.coverage +++ b/.coverage @@ -1 +1 @@ -!coverage.py: This is a private format, don't read it directly!{"lines":{"/root/Documents/save_me/storeManager/app/__init__.py":[1],"/root/Documents/save_me/storeManager/app/tests/__init__.py":[1],"/root/Documents/save_me/storeManager/app/tests/test_basic.py":[2,3,4,5,6,7,8,10,13,14,35,41,48,55,65,76,98,122,141,165,201,223,224,227,238,252,262,282,302,328,340,15,16,17,19,20,23,24,25,29,30,31,32,44,46,68,69,72,73,58,59,62,63,128,129,130,131,132,133,134,135,138,139,82,83,84,85,86,87,88,89,91,92,95,96,104,105,106,107,108,109,110,111,113,114,116,118,120,147,148,149,150,151,152,153,154,155,157,158,161,162,163,172,173,174,175,176,177,178,179,181,182,186,187,191,194,195,196,198,199,206,207,208,209,210,211,212,213,215,216,217,220,36,37,38,51,52,329,330,331,332,335,336,305,306,307,308,310,311,312,314,315,316,319,320,324,325,225,266,267,268,269,270,271,272,273,275,280,286,287,288,289,290,291,292,293,295,300,228,229,230,231,234,235,255,256,257,260,244,245,246,249,250],"/root/Documents/save_me/storeManager/app/app.py":[2,3,4,5,7,8,9,10,14,15,18,19,20,23,24,25,26,27,28,30,31,35,36,39,40,41,44,45,46,47,48,49,51,52,59,60,61,62,63,64,69,70,71,72,73,74,75,81,82,84,85,86,91,92,93,94,95,96,101,102,103,104,105,111,112,113,116,117,125,127,128,129,131,132,134,135,137,138,140,141,146,150,151,155,163,171,179,187,188,190,245,250,286,287,290,301,309,326,340,341,342,368,374,394,395,396,416,426,442,455,456,457,460,461,462,463,173,174,196,197,198,199,200,203,204,205,207,208,209,211,212,213,217,218,219,220,222,223,224,226,227,228,230,231,232,234,236,237,239,240,241,243,247,157,158,291,292,293,294,295,296,297,299,302,304,307,251,254,255,256,257,258,259,260,261,262,263,264,265,266,270,272,273,274,279,282,283,181,182,183,305,327,328,331,333,336,310,311,314,317,320,321,324,315,343,344,345,346,349,350,351,354,355,357,358,361,362,363,366,370,371,397,398,399,402,403,406,407,410,411,414,417,419,423,165,166,167,376,379,380,381,382,383,384,387,390,391,420,444,446,448,451,428,430,432,434,435,439,431],"/root/Documents/save_me/storeManager/app/tests/test_basic_.py":[1,2,3,4,5,6,7,9,12,13,41,53,63,71,78,90,116,134,158,193,218,219,231,245,256,282,292,312,14,15,16,18,19,22,24,25,27,28,29,31,32,33,37,38,83,84,86,88,45,47,49,51,74,75,56,57,60,61,67,68,121,122,123,124,125,126,127,128,131,132,96,97,98,99,100,102,103,104,106,107,110,112,114,164,165,166,167,168,169,170,171,173,174,178,179,183,186,187,188,190,191,140,141,142,143,144,145,146,147,148,150,151,154,155,156,199,200,201,202,203,204,205,206,208,209,210,213,215,246,247,248,249,252,253,259,260,261,262,264,265,266,268,269,270,273,274,278,279,285,286,287,290,220,221,222,223,226,228,237,238,239,242,243,296,297,298,299,300,301,302,303,305,310,316,317,318,319,320,321,322,323,325,326]}} \ No newline at end of file +!coverage.py: This is a private format, don't read it directly!{"lines":{"/root/Documents/save_me/storeManager/app/__init__.py":[1],"/root/Documents/save_me/storeManager/app/app.py":[2,3,4,5,7,8,9,10,14,15,18,19,20,23,24,25,26,27,28,30,31,35,36,39,40,41,44,45,46,47,48,49,51,52,59,60,61,62,63,64,69,70,71,72,73,74,75,81,82,84,85,86,91,92,93,94,95,96,101,102,103,104,105,111,112,113,116,117,125,127,128,129,131,132,134,135,137,138,140,141,146,150,151,155,163,171,179,187,188,190,245,250,286,287,290,301,309,326,340,341,342,368,374,394,395,396,416,426,442,455,456,457,460,461,462,463,343,344,345,346,349,350,351,354,355,357,358,361,362,363,366,370,371,397,398,399,402,403,406,407,410,411,414,417,419,423,165,166,167,157,158,376,379,380,381,382,383,384,387,390,391,420,444,446,448,451,428,430,432,434,435,439,431,173,174,196,197,198,199,200,203,204,205,207,208,209,211,212,213,217,218,219,220,222,223,224,226,227,228,230,231,232,234,236,237,239,240,241,243,247,291,292,293,294,295,296,297,299,302,304,307,251,254,255,256,257,258,259,260,261,262,263,264,265,266,270,272,273,274,279,282,283,181,182,183,305,327,328,331,333,336,310,311,314,317,320,321,324,315],"/root/Documents/save_me/storeManager/app/api/app.py":[],"/root/Documents/save_me/storeManager/app/api/__init__.py":[]}} \ No newline at end of file diff --git a/app/.coverage b/app/.coverage new file mode 100644 index 0000000..55093b6 --- /dev/null +++ b/app/.coverage @@ -0,0 +1 @@ +!coverage.py: This is a private format, don't read it directly!{"lines":{"/root/Documents/save_me/storeManager/app/api/app.py":[],"/root/Documents/save_me/storeManager/app/api/__init__.py":[]}} \ No newline at end of file diff --git a/app/api/app.py b/app/api/app.py index 693b794..eba7c5f 100644 --- a/app/api/app.py +++ b/app/api/app.py @@ -16,6 +16,9 @@ def create_app(): api.add_resource(sale_views.SaleAPI, '/stman/api/v1.0/sales/', endpoint='sale') + api.add_resource(product_views.ProductAPI, + '/stman/api/v1.0/products/', + endpoint='product') my_app.register_blueprint(store_blueprint) return my_app From 22ecaa8bb45656e88b293854e1a813d32dd6392e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Oct 2018 19:36:01 +0300 Subject: [PATCH 58/66] feature [#161422102] Provide for a working heroku link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a92be1..d0981f6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A web application to help store managers maintain their inventories and manage sale records. -Try on Heroku: https://store-man90.herokuapp.com/ +Try on Heroku: https://store-man90.herokuapp.com/stman/api/v1.0/products [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) From 5803e987b3fd318f410c04368b9b147b3dfe67fb Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 06:03:27 +0300 Subject: [PATCH 59/66] chore [#161437785] Give app instance access to configurations --- app/api/__init__.py | 4 ++-- app/instance/config.py | 12 ++++++++++++ run.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/api/__init__.py b/app/api/__init__.py index d677a2b..5a6f626 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1,4 +1,4 @@ -from app import create_app +import app def start_app(): - return create_app() + return api.create_app() diff --git a/app/instance/config.py b/app/instance/config.py index c1498b1..43cc60b 100644 --- a/app/instance/config.py +++ b/app/instance/config.py @@ -22,3 +22,15 @@ class DevelopmentConfig(Config): DEVELOPMENT = True DEBUG = True + +class StagingConfig(Config): + DEBUG = True + + + +app_config = { + 'development': DevelopmentConfig, + 'testing': TestingConfig, + 'staging': StagingConfig, + 'production': ProductionConfig +} diff --git a/run.py b/run.py index a75159e..d2415f5 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ -from api import start_app +from app.api import start_app if __name__ == '__main__': start_app.run() From 34606a7aa70b354aed02205ee7db6a94e12d2c5e Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 06:11:29 +0300 Subject: [PATCH 60/66] chore [#161437785] Resolve for environment configuration --- app/api/app.py | 4 +++- app/api/run.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 app/api/run.py diff --git a/app/api/app.py b/app/api/app.py index eba7c5f..a4113ce 100644 --- a/app/api/app.py +++ b/app/api/app.py @@ -1,12 +1,14 @@ from flask import Flask, Blueprint from flask_restful import Api from views import product_views, sale_views +from instance.config import app_config store_blueprint = Blueprint("store-man", __name__) -def create_app(): +def create_app(config_setting): my_app = Flask(__name__) + my_app.config.from_object(app_config[config_setting]) api = Api(store_blueprint) api.add_resource(sale_views.SalesAPI, diff --git a/app/api/run.py b/app/api/run.py new file mode 100644 index 0000000..062a21f --- /dev/null +++ b/app/api/run.py @@ -0,0 +1,4 @@ +import start_app + +if __name__ == '__main__': + start_app.run() From a6b2dd4e3950a21e208ff469af712c2f5ebb09db Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 06:20:09 +0300 Subject: [PATCH 61/66] chore [#161437962] tell importing files where to get the app instance --- app/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index e69de29..8602de1 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -0,0 +1,4 @@ +from api.app import create_app + +def app_instance(app_setting): + return create_app(app_setting) From 4b1e1cd72bf93e801bdd12e68409bb93e010b191 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 06:44:58 +0300 Subject: [PATCH 62/66] bug [#161416449] Give correct paths for every imported module --- app/__init__.py | 4 --- app/api/__init__.py | 6 ++-- app/api/app.py | 4 +-- app/api/models/__init__.py | 0 app/api/run.py | 4 --- app/api/views/__init__.py | 0 app/api/views/product_views.py | 65 +++++++++++++++++++++++++++++++++- app/api/views/sale_views.py | 2 +- app/instance/__init__.py | 0 app/instance/config.py | 2 ++ run.py | 6 ++-- 11 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 app/api/models/__init__.py delete mode 100644 app/api/run.py create mode 100644 app/api/views/__init__.py create mode 100644 app/instance/__init__.py diff --git a/app/__init__.py b/app/__init__.py index 8602de1..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +0,0 @@ -from api.app import create_app - -def app_instance(app_setting): - return create_app(app_setting) diff --git a/app/api/__init__.py b/app/api/__init__.py index 5a6f626..7295c33 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1,4 +1,4 @@ -import app +from .app import create_app -def start_app(): - return api.create_app() +def app_instance(app_setting): + return create_app(app_setting) diff --git a/app/api/app.py b/app/api/app.py index a4113ce..51b8c8a 100644 --- a/app/api/app.py +++ b/app/api/app.py @@ -1,7 +1,7 @@ from flask import Flask, Blueprint from flask_restful import Api -from views import product_views, sale_views -from instance.config import app_config +from .views import product_views, sale_views +from app.instance.config import app_config store_blueprint = Blueprint("store-man", __name__) diff --git a/app/api/models/__init__.py b/app/api/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/run.py b/app/api/run.py deleted file mode 100644 index 062a21f..0000000 --- a/app/api/run.py +++ /dev/null @@ -1,4 +0,0 @@ -import start_app - -if __name__ == '__main__': - start_app.run() diff --git a/app/api/views/__init__.py b/app/api/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/views/product_views.py b/app/api/views/product_views.py index 64227aa..cd185a1 100644 --- a/app/api/views/product_views.py +++ b/app/api/views/product_views.py @@ -1,6 +1,7 @@ from flask_restful import Resource, reqparse, marshal, fields -from models import products +from app.api.models import products from datetime import datetime +from flask import abort products = products.Products.productsList() @@ -57,6 +58,68 @@ def post(self): }, 201 +class ProductAPI(Resource): + """docstring for ProductAPI""" + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('title', type=str, + location='json' + ) + + self.parse.add_argument('category', type=str, + location='json' + ) + + self.parse.add_argument('price', type=int, + location='json' + ) + + self.parse.add_argument('in stock', type=bool, + location='json' + ) + + super(ProductAPI, self).__init__() + + def get(self, id): + product = [product for product in + products.products if product['id'] is id] + + if not product: + abort(404) + + return { + 'product': marshal(product[0], product_fields) + } + + def put(self, id): + product = [product for product in + products.products if product['id'] is id] + + if not product: + abort(404) + elements = self.parse.parse_args() + + for key, value in list(elements.items()): + if value: + product[0][key] = value + + return { + 'product': marshal(product, product_fields) + } + + def delete(self, id): + product = [product for product + in products.products if product['id'] is id] + + if not product: + abort(404) + products.products.remove(product[0]) + + return { + 'Status': True + } + + product_fields = { 'title': fields.String, 'category': fields.String, diff --git a/app/api/views/sale_views.py b/app/api/views/sale_views.py index 5554e80..3737d89 100644 --- a/app/api/views/sale_views.py +++ b/app/api/views/sale_views.py @@ -1,5 +1,5 @@ from flask_restful import Resource, reqparse, marshal, fields -from models import sales +from app.api.models import sales from datetime import datetime from flask import abort diff --git a/app/instance/__init__.py b/app/instance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/instance/config.py b/app/instance/config.py index 43cc60b..53c7846 100644 --- a/app/instance/config.py +++ b/app/instance/config.py @@ -26,6 +26,8 @@ class DevelopmentConfig(Config): class StagingConfig(Config): DEBUG = True +class TestingConfig(Config): + DEBUG = True app_config = { diff --git a/run.py b/run.py index d2415f5..bf26909 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,6 @@ -from app.api import start_app +from app import api + +app = api.app_instance('development') if __name__ == '__main__': - start_app.run() + app.run(debug=True) From 64fb2900c2aa02e3ad61a97a1e5e4b336785af55 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 06:59:27 +0300 Subject: [PATCH 63/66] chore [#161416449] Solve for the missing attribute in records elements --- app/api/views/product_views.py | 10 +++++----- app/api/views/sale_views.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/api/views/product_views.py b/app/api/views/product_views.py index cd185a1..a02e399 100644 --- a/app/api/views/product_views.py +++ b/app/api/views/product_views.py @@ -36,7 +36,7 @@ def __init__(self): def get(self): return { 'product': [marshal(product, product_fields) - for product in products.products] + for product in products] } def post(self): @@ -82,7 +82,7 @@ def __init__(self): def get(self, id): product = [product for product in - products.products if product['id'] is id] + products if product['id'] is id] if not product: abort(404) @@ -93,7 +93,7 @@ def get(self, id): def put(self, id): product = [product for product in - products.products if product['id'] is id] + products if product['id'] is id] if not product: abort(404) @@ -109,11 +109,11 @@ def put(self, id): def delete(self, id): product = [product for product - in products.products if product['id'] is id] + in products if product['id'] is id] if not product: abort(404) - products.products.remove(product[0]) + products.remove(product[0]) return { 'Status': True diff --git a/app/api/views/sale_views.py b/app/api/views/sale_views.py index 3737d89..98e6ca0 100644 --- a/app/api/views/sale_views.py +++ b/app/api/views/sale_views.py @@ -60,7 +60,7 @@ def __init__(self): def get(self): return { - 'sales': [marshal(sale, sales.sale_fields) for sale in sales.sales] + 'sales': [marshal(sale, sale_fields) for sale in sales] } def post(self): @@ -92,7 +92,7 @@ def post(self): ) # Add new sale to sales record - sales.sales.append(sale) + sales.append(sale) return { 'sale': marshal(sale, sale_fields) @@ -112,7 +112,7 @@ def __init__(self): super(SaleAPI, self).__init__() def get(self, sales_record): - sale = [sale for sale in sales.sales if sale[ + sale = [sale for sale in sales if sale[ 'sales_record'] == sales_record] if not sale: @@ -122,7 +122,7 @@ def get(self, sales_record): def put(self, sales_record): sale = [sale for sale - in sales.sales if sale['sales_record'] is sales_record + in sales if sale['sales_record'] is sales_record ] if not sale: @@ -139,7 +139,7 @@ def put(self, sales_record): def delete(self, sales_record): sale = [sale for sale - in sales.sales if sale['sales_record'] is sales_record + in sales if sale['sales_record'] is sales_record ] if not sale: From 986d458b4abfd79f4711dfd55be138b04c7d99b7 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 08:21:45 +0300 Subject: [PATCH 64/66] chore [#161438886] Create a model for users data --- app/api/models/users.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/api/models/users.py diff --git a/app/api/models/users.py b/app/api/models/users.py new file mode 100644 index 0000000..5b684a1 --- /dev/null +++ b/app/api/models/users.py @@ -0,0 +1,27 @@ +from werkzeug.security import generate_password_hash + +users = [] + +cool_user_sample = { + 'name': 'Evil Cow', + 'username': 'e-cow', + 'email': 'ecow@isus.mammals', + 'password': generate_password_hash('wah!-things-we-do!'), + 'user id': 1 +} + + +class Users(object): + """docstring for Users""" + def __init__(self, name, username, email, password): + super(Users, self).__init__() + self.name = name + self.username = username + self.email = email + self.password = password + + @staticmethod + def usersList(): + users.append(cool_user_sample) + + return users From b1e3abf8419fd607f0b292965d18f7a020e42e5b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 09:11:36 +0300 Subject: [PATCH 65/66] feature [#161439619] allow for creation of new users --- app/api/views/user_views.py | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/api/views/user_views.py diff --git a/app/api/views/user_views.py b/app/api/views/user_views.py new file mode 100644 index 0000000..17a4407 --- /dev/null +++ b/app/api/views/user_views.py @@ -0,0 +1,77 @@ +from flask_restful import Resource, reqparse, marshal, fields +from app.api.models import users +from flask import jsonify, make_response +import random + +users = users.Users.usersList() + + +class UsersAPI(Resource): + def __init__(self): + self.parse = reqparse.RequestParser() + self.parse.add_argument('name', type=str, required=True, + help="Please add a name", + location='json' + ) + + self.parse.add_argument('username', type=str, + default='user' + + str(random.randint(500, 5000)), + location='json' + ) + + self.parse.add_argument('email', type=str, + required=True, + help="You are not \ + allowed here without email", + location='json' + ) + + self.parse.add_argument('password', type=str, + required=True, + help='Please specify password', + location='json' + ) + + super(UsersAPI, self).__init__() + + def get(self): + return { + 'users': [marshal(user, user_fields) + for user in users] + } + + def post(self): + elements = self.parse.parse_args() + + user = { + 'name': elements['name'], + 'username': elements['username'], + 'email': elements['email'], + 'password': elements['password'], + 'user id': users[-1]['user id'] + 1 + } + + present = [userr for userr in users + if userr['email'] is elements['email']] + + if not present: + users.append(user) + + else: + return make_response(jsonify({'message': + 'That email is already registered'}), + 409) + + return { + 'Effect': 'Success. User added' + }, 201 + + +user_fields = { + 'name': fields.String, + 'email': fields.String, + 'username': fields.Integer, + 'password': fields.Boolean, + 'url': fields.Url('user id') +} From 2474423fa1a43700537d096f319daa7e7be7479b Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Oct 2018 09:18:34 +0300 Subject: [PATCH 66/66] feature [#161439619] allow for creation of new users --- app/api/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/api/app.py b/app/api/app.py index 51b8c8a..c59715e 100644 --- a/app/api/app.py +++ b/app/api/app.py @@ -1,6 +1,6 @@ from flask import Flask, Blueprint from flask_restful import Api -from .views import product_views, sale_views +from .views import product_views, sale_views, user_views from app.instance.config import app_config store_blueprint = Blueprint("store-man", __name__) @@ -21,6 +21,9 @@ def create_app(config_setting): api.add_resource(product_views.ProductAPI, '/stman/api/v1.0/products/', endpoint='product') + api.add_resource(user_views.UsersAPI, + '/stman/api/v1.0/users/', + endpoint='users') my_app.register_blueprint(store_blueprint) return my_app