Initial commit
This commit is contained in:
54
DEPRECATED - routes.py
Normal file
54
DEPRECATED - routes.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Controller - Webpage routing
|
||||
|
||||
Description:
|
||||
Defines the routes and view functions for each page.
|
||||
Manages the interaction between the frontend and backend.
|
||||
"""
|
||||
|
||||
from flask import render_template, url_for, Blueprint
|
||||
from app import app
|
||||
from app.forms import Form_Contact
|
||||
# from forms import MyForm
|
||||
# from app import MyForm
|
||||
from models.model_view_contact import Model_View_Contact
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
return render_template('_home.html', title='Home')
|
||||
|
||||
@app.route('/store', methods=['GET'])
|
||||
def store_home():
|
||||
return render_template('_store_home.html', title='Store Home')
|
||||
|
||||
@app.route('/contact', methods=['GET', 'POST'])
|
||||
def contact():
|
||||
form = Form_Contact()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
email = form.sender_email.data
|
||||
CC = form.sender_CC.data
|
||||
name = form.sender_name.data
|
||||
msg = form.sender_message.data
|
||||
# return render_template('contact.html', form=form)
|
||||
# return render_template('_contact.html', title='Contact Us')
|
||||
return render_template('contact.html', model=Model_View_Contact(form))
|
||||
|
||||
"""
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return render_template('about.html')
|
||||
|
||||
@app.route('/contact', methods=['GET', 'POST'])
|
||||
def contact():
|
||||
form = MyForm()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
pass
|
||||
return render_template('contact.html', form=form)
|
||||
"""
|
||||
10
README
Normal file
10
README
Normal file
@@ -0,0 +1,10 @@
|
||||
Precision and Research Technology Systems Limited
|
||||
Website with online store
|
||||
|
||||
Powered by flask
|
||||
|
||||
host for machine:
|
||||
python -m flask run
|
||||
|
||||
host for local network:
|
||||
python -m flask run --host=0.0.0.0
|
||||
20
__init__.py
Normal file
20
__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Initialisation
|
||||
|
||||
Description:
|
||||
Initializes the Flask application.
|
||||
Initializes any extensions used in the project.
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('config')
|
||||
|
||||
# from app import routes
|
||||
# import business_objects, lib, models
|
||||
BIN
__pycache__/__init__.cpython-311.pyc
Normal file
BIN
__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/app.cpython-311.pyc
Normal file
BIN
__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/argument_validation.cpython-311.pyc
Normal file
BIN
__pycache__/argument_validation.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/config.cpython-311.pyc
Normal file
BIN
__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/forms.cpython-311.pyc
Normal file
BIN
__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/model_view_base.cpython-311.pyc
Normal file
BIN
__pycache__/model_view_base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/model_view_contact.cpython-311.pyc
Normal file
BIN
__pycache__/model_view_contact.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/model_view_home.cpython-311.pyc
Normal file
BIN
__pycache__/model_view_home.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/model_view_store.cpython-311.pyc
Normal file
BIN
__pycache__/model_view_store.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/model_view_store_home.cpython-311.pyc
Normal file
BIN
__pycache__/model_view_store_home.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/pay_stripe.cpython-311.pyc
Normal file
BIN
__pycache__/pay_stripe.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/product.cpython-311.pyc
Normal file
BIN
__pycache__/product.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/routes.cpython-311.pyc
Normal file
BIN
__pycache__/routes.cpython-311.pyc
Normal file
Binary file not shown.
644
app.py
Normal file
644
app.py
Normal file
@@ -0,0 +1,644 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: App General
|
||||
Feature: App
|
||||
|
||||
Description:
|
||||
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from config import app_config, Config
|
||||
# from routes import bp_home
|
||||
from app.forms import Form_Contact, Form_Basket_Add, Form_Basket_Edit, Form_Billing, Form_Delivery_Region, Form_Currency # , Form_Product
|
||||
from models.model_view_base import Model_View_Base
|
||||
from models.model_view_home import Model_View_Home
|
||||
from models.model_view_store import Model_View_Store
|
||||
from models.model_view_store_home import Model_View_Store_Home
|
||||
from models.model_view_store_product import Model_View_Store_Product
|
||||
from models.model_view_store_basket import Model_View_Store_Basket
|
||||
from models.model_view_store_checkout import Model_View_Store_Checkout
|
||||
from models.model_view_store_checkout_success import Model_View_Store_Checkout_Success
|
||||
from models.model_view_contact import Model_View_Contact
|
||||
from business_objects.product import Product # , Product_Image_Filters, Resolution_Level_Enum
|
||||
import lib.argument_validation as av
|
||||
from business_objects.basket import Basket_Item
|
||||
from datastores.datastore_store import DataStore_Store
|
||||
from business_objects.product import Product_Filters
|
||||
# external
|
||||
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
|
||||
from flask_cors import CORS
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
import stripe
|
||||
import json
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
import os
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
import jwt
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
# app.register_blueprint(bp_home, url_prefix='')
|
||||
email_recipient = 'edward.middletonsmith@gmail.com' # 'noreply'
|
||||
|
||||
app.config.from_object(app_config)
|
||||
print(f'secret key = {app.secret_key}')
|
||||
app.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_DATABASE_URI
|
||||
app.SQLALCHEMY_TRACK_MODIFICATIONS = Config.SQLALCHEMY_TRACK_MODIFICATIONS
|
||||
app.ID_AUTH0_CLIENT = Config.ID_AUTH0_CLIENT
|
||||
app.ID_AUTH0_CLIENT_SECRET = Config.ID_AUTH0_CLIENT_SECRET
|
||||
app.DOMAIN_AUTH0 = Config.DOMAIN_AUTH0
|
||||
app.ID_TOKEN_USER = Config.ID_TOKEN_USER
|
||||
"""
|
||||
app.is_included_VAT = Config.is_included_VAT
|
||||
app.KEY_IS_INCLUDED_VAT = Config.KEY_IS_INCLUDED_VAT
|
||||
app.code_currency = Config.code_currency
|
||||
app.KEY_CODE_CURRENCY = Config.KEY_CODE_CURRENCY
|
||||
app.code_region_delivery = Config.code_region_delivery
|
||||
app.KEY_CODE_REGION_DELIVERY = Config.KEY_CODE_REGION_DELIVERY
|
||||
app.KEY_ID_CURRENCY = Config.KEY_ID_CURRENCY
|
||||
app.KEY_ID_REGION_DELIVERY = Config.KEY_ID_REGION_DELIVERY
|
||||
app.id_currency = Config.id_currency
|
||||
app.id_region_delivery = Config.id_region_delivery
|
||||
"""
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
"auth0",
|
||||
client_id = app.ID_AUTH0_CLIENT,
|
||||
client_secret = app.ID_AUTH0_CLIENT_SECRET, # =env.get("AUTH0_CLIENT_SECRET"),
|
||||
client_kwargs={
|
||||
"scope": "openid profile email",
|
||||
},
|
||||
server_metadata_url=f'https://{app.DOMAIN_AUTH0}/.well-known/openid-configuration'
|
||||
)
|
||||
# session[app.ID_TOKEN_USER] = {'userinfo': {'sub': ''}}
|
||||
|
||||
|
||||
# METHODS
|
||||
|
||||
|
||||
# ROUTING
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
return render_template('_page_home.html', model=Model_View_Home(db, get_info_user(), app))
|
||||
|
||||
|
||||
@app.route('/contact', methods=['GET', 'POST'])
|
||||
def contact():
|
||||
form = Form_Contact()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
email = form.email.data
|
||||
CC = form.CC.data # not in use
|
||||
name = form.name.data
|
||||
msg = form.msg.data
|
||||
# send email
|
||||
pass
|
||||
return render_template('_page_contact.html', model=Model_View_Contact(db, get_info_user(), app, form))
|
||||
|
||||
|
||||
# Store
|
||||
@app.route('/store', methods=['GET'])
|
||||
def store_home():
|
||||
print("store home")
|
||||
try:
|
||||
data = request.json
|
||||
except:
|
||||
data = {}
|
||||
print(f'data={data}')
|
||||
try:
|
||||
id_currency = data.id_currency
|
||||
except:
|
||||
id_currency = Model_View_Store.ID_CURRENCY_DEFAULT
|
||||
print(f"id_currency = {id_currency}")
|
||||
try:
|
||||
id_region_delivery = data.id_region_delivery
|
||||
except:
|
||||
id_region_delivery = Model_View_Store.ID_REGION_DELIVERY_DEFAULT
|
||||
print(f"id_region_delivery = {id_region_delivery}")
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
# model.get_regions_and_currencies()
|
||||
# model.categories = Model_View_Store_Home.get_many_product_category(db)
|
||||
# product = categories[list(categories.keys())[0]][0]
|
||||
model.get_many_product_category(Product_Filters(
|
||||
model.id_user,
|
||||
True, '', False,
|
||||
True, '', False, False,
|
||||
True, '', False,
|
||||
False, '', False, True,
|
||||
False, id_region_delivery, False,
|
||||
False, id_currency, False,
|
||||
True, '', False
|
||||
))
|
||||
"""
|
||||
for cat in model.categories:
|
||||
# for product in model.categories[cat]:
|
||||
i_cat = model.category_index[cat.id_category]
|
||||
for p in cat.products:
|
||||
print(f'adding basket add form for product with id: {p.id_product}')
|
||||
model.categories[i_cat].products[model.categories[i_cat].product_index[p.id_product]].add_form_basket_add()
|
||||
model.categories[i_cat].products[model.categories[i_cat].product_index[p.id_product]].form_basket_add = Form_Basket_Add()
|
||||
print('form added')
|
||||
# print('form added for product {p.id}')
|
||||
print('rendering page store home')
|
||||
"""
|
||||
for cat in model.category_list.categories:
|
||||
# for product in model.categories[cat]:
|
||||
# i_cat = model.category_index[cat.id_category]
|
||||
print(f'category: {cat.name}')
|
||||
for p in cat.products:
|
||||
print(f'product: {p.name}')
|
||||
print(f'selected permutation: {p.get_permutation_selected()}')
|
||||
return render_template('_page_store_home.html', model = model) # "<html><body><h1>Boobs</h1></html></body>"
|
||||
# POST request
|
||||
# return jsonify(Success=True, data={'html_block': render_template('_page_store_home.html', model = model)})
|
||||
|
||||
# update with local basket, if not logged in- partial
|
||||
@app.route('/store/basket_load', methods=['POST'])
|
||||
def basket_load():
|
||||
_m = 'basket_load'
|
||||
print(f'{_m}\nstarting...')
|
||||
data = request.json
|
||||
print(f'data={data}')
|
||||
|
||||
# model, html_block = render_basket_from_JSON(data)
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
# model.import_JSON_basket(data)
|
||||
model.get_basket(data)
|
||||
model.is_included_VAT = is_included_VAT
|
||||
|
||||
html_block = render_template('_block_store_basket.html', model = model)
|
||||
print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success=True, data={'html_block': html_block, 'basket': model.basket.to_json()}) # {'items': [b_i.to_json() for b_i in model.basket]}}) # { 'html': render_template('_block_store_basket.html', model = model), 'basket': model.basket })
|
||||
|
||||
@app.route('/store/basket_add', methods=['POST'])
|
||||
def basket_add():
|
||||
_m = 'basket_add'
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
model.is_included_VAT = is_included_VAT
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
form = Form_Basket_Add(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
print('valid form')
|
||||
# model = input_JSON_basket(model, data)
|
||||
# if not logged in:
|
||||
try:
|
||||
print('importing basket')
|
||||
# print('success' if model.import_JSON_basket(data) else 'failure')
|
||||
model.get_basket(data)
|
||||
permutation_id, quantity = model.import_JSON_basket_item(data, form)
|
||||
print(f'permutation_id: {permutation_id}\nquantity: {quantity}')
|
||||
print(f'editing basket:')
|
||||
model.basket_item_edit(permutation_id, quantity, True) # new_basket =
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}}) # 'items': [b_i.to_json() for b_i in model.basket]}})
|
||||
return jsonify({'status': 'failure', 'Message': 'Invalid quantities'})
|
||||
|
||||
|
||||
|
||||
@app.route('/store/basket_edit', methods=['POST'])
|
||||
def basket_edit():
|
||||
_m = 'basket_edit'
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
model.is_included_VAT = is_included_VAT
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
form = Form_Basket_Edit(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
print('valid form')
|
||||
# model = input_JSON_basket(model, data)
|
||||
# if not logged in:
|
||||
try:
|
||||
# print('importing basket')
|
||||
# model.import_JSON_basket(data)
|
||||
model.get_basket(data)
|
||||
permutation_id, quantity = model.import_JSON_basket_item(data, form)
|
||||
model.basket_item_edit(permutation_id, quantity, False) # new_basket =
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}})
|
||||
return jsonify({'status': 'failure', 'Message': 'Invalid quantities'})
|
||||
|
||||
@app.route('/store/basket_delete', methods=['POST'])
|
||||
def basket_delete():
|
||||
_m = 'basket_delete'
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
model.is_included_VAT = is_included_VAT
|
||||
try:
|
||||
# print('importing basket')
|
||||
# model.import_JSON_basket(data)
|
||||
model.get_basket(data)
|
||||
permutation_id, quantity = model.import_JSON_basket_item(data)
|
||||
model.basket_item_edit(permutation_id, 0, False) # new_basket =
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}})
|
||||
# delete basket item with product_id
|
||||
item_deleted = False
|
||||
for basket_item in model.basket:
|
||||
if basket_item.product.id_product == product_id:
|
||||
model.basket.remove(basket_item)
|
||||
item_deleted = True
|
||||
break
|
||||
if not item_deleted:
|
||||
return jsonify({'status': 'failure', 'Message': 'Basket item removal failure: product not found in basket.'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': [b_i.to_json() for b_i in model.basket]}})
|
||||
|
||||
@app.route('/store/basket', methods=['GET']) # 'POST'
|
||||
def store_basket():
|
||||
# _m = 'basket_review'
|
||||
try:
|
||||
data = request.json
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Basket(db, get_info_user(), app, id_currency, id_region_delivery)
|
||||
model.is_included_VAT = is_included_VAT
|
||||
except:
|
||||
raise Exception('Bad data received by controller')
|
||||
return render_template('_page_store_basket.html', model=model)
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
# if logged in
|
||||
#html_block = render_template('_page_store_basket.html', model = model) # with basket from database
|
||||
try:
|
||||
print('importing basket')
|
||||
model.import_JSON_basket(data)
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad basket data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
html_block = render_template('_page_store_billing.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': [b_i.to_json() for b_i in model.basket]}})
|
||||
|
||||
@app.route('/store/basket_info', methods=['POST'])
|
||||
def basket_info():
|
||||
_m = 'basket_info'
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
model = Model_View_Store_Basket(db, get_info_user(), app)
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
form = Form_Billing(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
print('valid form')
|
||||
# model = input_JSON_basket(model, data)
|
||||
# if not logged in:
|
||||
data_info = {}
|
||||
try:
|
||||
info_type = data[model.key_info_type]
|
||||
print('importing address information')
|
||||
data_info[model.key_region] = form.region.data
|
||||
print(f'region: {data_info[model.key_region]}')
|
||||
data_info[model.key_name_full] = form.name_full.data
|
||||
print(f'full name: {data_info[model.key_name_full]}')
|
||||
data_info[model.key_phone_number] = form.phone_number.data
|
||||
print(f'phone number: {data_info[model.key_phone_number]}')
|
||||
data_info[model.key_postcode] = form.postcode.data
|
||||
print(f'postcode: {data_info[model.key_postcode]}')
|
||||
data_info[model.key_address1] = form.address_1.data
|
||||
print(f'address line 1: {data_info[model.key_address1]}')
|
||||
data_info[model.key_address2] = form.address_2.data
|
||||
print(f'address line 2: {data_info[model.key_address2]}')
|
||||
data_info[model.key_city] = form.city.data
|
||||
print(f'city: {data_info[model.key_city]}')
|
||||
data_info[model.key_county] = form.county.data
|
||||
print(f'county: {data_info[model.key_county]}')
|
||||
data_info[model.key_info_identical] = form.identical
|
||||
print(f'identical: {data_info[model.key_info_identical]}')
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
# html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
data = {}
|
||||
data[model.key_info_type] = model.key_info_billing if (info_type == model.key_info_billing) else model.key_info_delivery
|
||||
data[info_type] = data_info
|
||||
return jsonify(Success = True, data = data)
|
||||
return jsonify({'status': 'failure', 'Message': f'Invalid address information\n{form.errors}'})
|
||||
|
||||
|
||||
@app.route('/store/product?permutationId=<permutation_id>regionId=&<region_id>¤cyId=<currency_id>', methods=['GET']) # <product_id>&
|
||||
def store_product(permutation_id, region_id, currency_id):
|
||||
_m = 'store_product'
|
||||
"""
|
||||
av.full_val_int(product_id, 'product_id', _m)
|
||||
product_id = int(product_id)
|
||||
print(f'product_id: {product_id}')
|
||||
if permutation_id == 'None' or str(type(permutation_id)) == "<class 'NoneType'>":
|
||||
permutation_id = None
|
||||
else:
|
||||
"""
|
||||
# app.id_region_delivery = region_id
|
||||
# app.id_currency = currency_id
|
||||
av.full_val_int(permutation_id, 'permutation_id', _m)
|
||||
permutation_id = int(permutation_id)
|
||||
print(f'{_m}\nstarting...')
|
||||
# print(f'product id: {product_id}')
|
||||
print(f'permutation id: {permutation_id}')
|
||||
try:
|
||||
model = Model_View_Store_Product(db, get_info_user(), app, permutation_id, currency_id, region_id)
|
||||
print('model reached')
|
||||
# model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(request.json)
|
||||
# model.get_many_product_category(product_ids = str(product_id))
|
||||
# print('categories reached')
|
||||
# product = model.categories[0].products[0]# [list(categories.keys())[0]][0]
|
||||
# print('product reached')
|
||||
# return jsonify({'data': render_template('_page_store_product.html', model=model)})
|
||||
permutation_selected = model.product.get_permutation_selected()
|
||||
print(f'selected permutation: {permutation_selected}')
|
||||
return render_template('_page_store_product.html', model=model)
|
||||
except:
|
||||
print('except reached')
|
||||
return jsonify({'status': 'error'})
|
||||
|
||||
# Stripe
|
||||
@app.route('/config', methods=['GET'])
|
||||
def get_publishable_key():
|
||||
price = stripe.Price.retrieve(Model_View_Store_Checkout.get_price_id(Product.template().id))
|
||||
return jsonify({
|
||||
'publicKey': Model_View_Store_Checkout.key_public_stripe, # os.getenv('KEY_PUBLIC_STRIPE'),
|
||||
'unitAmount': price['unit_amount'],
|
||||
'currency': price['currency']
|
||||
})
|
||||
|
||||
# Create Stripe prices
|
||||
@app.route('/store/product_create_all', methods=['GET'])
|
||||
def product_create_all():
|
||||
# _m = 'product_create_all'
|
||||
model = Model_View_Store_Checkout(db, get_info_user(), app)
|
||||
products, currencies = model.get_many_product_new()
|
||||
html = f"<html><body>"
|
||||
for product in products:
|
||||
product.id_stripe_product = model.create_product(product)
|
||||
html += f"<h1>product id = {product.id}</h1><br><h1>id_stripe_product = {product.id_stripe_product}</h1>"
|
||||
html += "</body></html>"
|
||||
|
||||
return html
|
||||
|
||||
@app.route('/store/price_create_all', methods=['GET'])
|
||||
def price_create_all():
|
||||
# _m = 'price_create_all'
|
||||
model = Model_View_Store_Checkout(db, get_info_user(), app)
|
||||
products, currencies = model.get_many_price_new()
|
||||
html = f"<html><body>"
|
||||
for product in products:
|
||||
product.id_stripe_price = model.create_price(product, currencies[product.id])
|
||||
html += f"<h1>product id = {product.id}</h1><br><h1>id_stripe_price = {product.id_stripe_price}</h1><br><h1>currency = {currencies[product.id]}</h1>"
|
||||
html += "</body></html>"
|
||||
|
||||
return html
|
||||
|
||||
# Fetch the Checkout Session to display the JSON result on the success page
|
||||
@app.route('/store/checkout_session?<session_id>', methods=['GET'])
|
||||
def get_checkout_session(session_id):
|
||||
id = request.args.get('session_id')
|
||||
_m = 'get_checkout_session'
|
||||
# av.full_val_int(session_id, 'session_id', _m)
|
||||
av.val_str(session_id, 'session_id', _m)
|
||||
print(f'url var normal session id: {session_id}')
|
||||
print(f'{_m}\nstarting...')
|
||||
# session_id = id
|
||||
session_id = session_id
|
||||
print(f'request.args checkout session id: {session_id}') # for function
|
||||
checkout_session = stripe.checkout.Session.retrieve(session_id)
|
||||
return jsonify(checkout_session)
|
||||
|
||||
|
||||
@app.route('/store/checkout', methods=['POST', 'GET'])
|
||||
def create_checkout_session():
|
||||
# quantity = request.form.get('quantity', 1)
|
||||
# domain_url = os.getenv('DOMAIN')
|
||||
model = Model_View_Store_Checkout(db, get_info_user(), app)
|
||||
print('checkout model created')
|
||||
try:
|
||||
data = request.json # .get('data')
|
||||
print(f'data: {data}')
|
||||
print('importing basket')
|
||||
model.get_basket(data)
|
||||
model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
# model.import_JSON_basket(data)
|
||||
# print('getting is subscription')
|
||||
# is_subscription_checkout_session = data[model.key_is_subscription]
|
||||
code_currency = 'GBP' # data[model.key_code_currency]
|
||||
print(f'currency code: {code_currency}')
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
|
||||
items = []
|
||||
for item in model.basket.items:
|
||||
permutation = item.product.get_permutation_selected()
|
||||
price = permutation.get_price_from_code_currency(code_currency)
|
||||
items.append({'price': price.id_stripe_price,
|
||||
'quantity': item.quantity })
|
||||
# if is_subscription_checkout_session:
|
||||
# break
|
||||
print(f'items = {items}')
|
||||
try:
|
||||
# Create new Checkout Session for the order
|
||||
# Other optional params include:
|
||||
# [billing_address_collection] - to display billing address details on the page
|
||||
# [customer] - if you have an existing Stripe Customer ID
|
||||
# [payment_intent_data] - lets capture the payment later
|
||||
# [customer_email] - lets you prefill the email input in the form
|
||||
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
|
||||
# For full details see https://stripe.com/docs/api/checkout/sessions/create
|
||||
stripe.api_key = model.key_secret_stripe
|
||||
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
|
||||
checkout_session = stripe.checkout.Session.create(
|
||||
success_url=Model_View_Store_Checkout.url_host + '/store/checkout_success%3Fsession_id={CHECKOUT_SESSION_ID}',
|
||||
cancel_url=Model_View_Store_Checkout.url_host + '/store/checkout_cancelled',
|
||||
mode='subscription' if False else 'payment', # is_subscription_checkout_session
|
||||
# automatic_tax={'enabled': True},
|
||||
line_items=items
|
||||
)
|
||||
data_out = {}
|
||||
# data_out['Success'] = True
|
||||
data_out[f'{model.key_id_checkout}'] = checkout_session.id
|
||||
data_out[f'{model.key_url_checkout}'] = checkout_session.url
|
||||
# return jsonify(Success = True, data = data_out) # Success = True, f'{model.key_id_checkout}' = checkout_session.id)
|
||||
print(f'checkout session url: {checkout_session.url}')
|
||||
# redirect(checkout_session.url) # , code=303)
|
||||
return jsonify(Success = True, data = data_out)
|
||||
# return get_checkout_session(checkout_session.id)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
@app.route('/store/checkout_success?<session_id>', methods=['GET'])
|
||||
def checkout_success(session_id):
|
||||
_m = 'store checkout success'
|
||||
# av.full_val_int(session_id, 'session_id', _m)
|
||||
av.val_str(session_id, 'session_id', _m)
|
||||
if (session_id[:len('session_id=')] == 'session_id='):
|
||||
session_id = session_id[len('session_id='):]
|
||||
print(f'url var normal session id: {session_id}')
|
||||
print(f'{_m}\nstarting...')
|
||||
id = request.args.get('sessionId')
|
||||
print(f'request.args checkout session id: {id}')
|
||||
|
||||
checkout_session = stripe.checkout.Session.retrieve(session_id)
|
||||
print(f'checkout session data: {checkout_session}')
|
||||
|
||||
# validate billing information
|
||||
|
||||
|
||||
model = Model_View_Store_Checkout_Success(db, get_info_user(), app)
|
||||
|
||||
return render_template('_page_store_checkout_success.html', model=model)
|
||||
|
||||
@app.route('/store/checkout_cancelled', methods=['GET'])
|
||||
def checkout_cancelled():
|
||||
_m = 'store checkout success'
|
||||
print(f'{_m}\nstarting...')
|
||||
return render_template('_page_store_checkout_cancelled.html', model=Model_View_Store_Checkout(db, get_info_user(), app))
|
||||
|
||||
|
||||
# include VAT in prices?
|
||||
@app.route('/store/set_is_included_VAT', methods=['POST'])
|
||||
def set_is_included_VAT():
|
||||
_m = 'set_is_included_VAT'
|
||||
print(f'{_m}\nstarting...')
|
||||
data = request.json
|
||||
print(f'data={data}')
|
||||
app.is_included_VAT = not app.is_included_VAT # session[app.KEY_IS_INCLUDED_VAT] # data[app.KEY_IS_INCLUDED_VAT]
|
||||
return jsonify(Success=True, data={Model_View_Base.KEY_IS_INCLUDED_VAT: app.is_included_VAT})
|
||||
|
||||
# delivery region
|
||||
@app.route('/store/set_delivery_region', methods=['POST'])
|
||||
def set_delivery_region():
|
||||
_m = 'set_delivery_region'
|
||||
print(f'{_m}\nstarting...')
|
||||
data = request.json
|
||||
print(f'data={data}')
|
||||
# model = Model_View_Store(db, get_info_user(), app)
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
"""
|
||||
form = Form_Delivery_Region(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
app.id_region_delivery = form.id_region_delivery.data
|
||||
"""
|
||||
app.id_region_delivery = form_data[Model_View_Base.KEY_ID_REGION_DELIVERY]
|
||||
print(f'id_region_delivery: {app.id_region_delivery}')
|
||||
return jsonify(Success=True, data={Model_View_Base.KEY_ID_REGION_DELIVERY: app.id_region_delivery})
|
||||
|
||||
# currency
|
||||
@app.route('/store/set_currency', methods=['POST'])
|
||||
def set_currency():
|
||||
_m = 'set_currency'
|
||||
print(f'{_m}\nstarting...')
|
||||
data = request.json
|
||||
print(f'data={data}')
|
||||
# model = Model_View_Store(db, get_info_user(), app)
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
"""
|
||||
form = Form_Currency(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
app.id_currency = form.id_currency.data
|
||||
print(f'id_currency: {app.id_currency}')
|
||||
"""
|
||||
app.id_currency = form_data[Model_View_Base.KEY_ID_CURRENCY]
|
||||
print(f'id_currency: {app.id_currency}')
|
||||
return jsonify(Success=True, data={Model_View_Base.KEY_ID_CURRENCY: app.id_currency})
|
||||
|
||||
|
||||
# User authentication
|
||||
@app.route("/login")
|
||||
def login():
|
||||
print(f'redirect uri: {url_for("login_callback", _external=True)}')
|
||||
return oauth.auth0.authorize_redirect(
|
||||
redirect_uri=url_for("login_callback", _external=True)
|
||||
)
|
||||
|
||||
@app.route("/login_callback", methods=["GET", "POST"])
|
||||
def login_callback():
|
||||
token = oauth.auth0.authorize_access_token()
|
||||
session[app.ID_TOKEN_USER] = token
|
||||
|
||||
# import user id
|
||||
print(f'str(type(token)) = {str(type(token))}')
|
||||
print(f'token = {token}')
|
||||
userinfo = token.get('userinfo')
|
||||
print(f'user info: {userinfo}')
|
||||
# id_user = token.get('sub')
|
||||
id_user = userinfo.get('sub')
|
||||
print(f'user ID: {id_user}')
|
||||
|
||||
# id_user = get_id_user()
|
||||
# add user to database
|
||||
# DataStore_Store(db, userinfo).add_new_user(id_user) # this is part of get basket - should occur on page load
|
||||
|
||||
return redirect("/")
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(
|
||||
"https://" + app.DOMAIN_AUTH0
|
||||
+ "/v2/logout?"
|
||||
+ urlencode(
|
||||
{
|
||||
"returnTo": url_for("home", _external=True),
|
||||
"client_id": app.ID_AUTH0_CLIENT,
|
||||
},
|
||||
quote_via=quote_plus,
|
||||
)
|
||||
)
|
||||
|
||||
def get_info_user():
|
||||
try:
|
||||
return session[app.ID_TOKEN_USER].get('userinfo') # .get('sub')
|
||||
except:
|
||||
return {'sub': ''}
|
||||
|
||||
|
||||
# Onload
|
||||
if __name__ == '__main__':
|
||||
# app.run()
|
||||
app.run(debug=True, host="0.0.0.0", port=5000)
|
||||
11
business_objects/__init__.py
Normal file
11
business_objects/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Module Initialisation
|
||||
Feature: Business Objects
|
||||
|
||||
Description:
|
||||
Initialises business objects module.
|
||||
"""
|
||||
BIN
business_objects/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/basket.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/basket.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/category.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/category.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/currency.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/currency.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/delivery_option.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/delivery_option.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/delivery_region.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/delivery_region.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/discount.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/discount.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/image.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/image.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/order.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/order.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/product.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/product.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/sql_error.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/sql_error.cpython-311.pyc
Normal file
Binary file not shown.
BIN
business_objects/__pycache__/variation.cpython-311.pyc
Normal file
BIN
business_objects/__pycache__/variation.cpython-311.pyc
Normal file
Binary file not shown.
164
business_objects/basket.py
Normal file
164
business_objects/basket.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Basket Business Object
|
||||
|
||||
Description:
|
||||
Business object for basket
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
# from lib import data_types
|
||||
from business_objects.product import Product, Product_Filters
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
# from forms import Form_Product
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
# from datastores.datastore_store import DataStore_Store # circular
|
||||
# from forms import Form_Basket_Add, Form_Basket_Edit # possibly circular
|
||||
# external
|
||||
# from enum import Enum
|
||||
from flask import jsonify
|
||||
import locale
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
# CLASSES
|
||||
class Basket_Item():
|
||||
product: Product
|
||||
quantity: int
|
||||
delivery_option: Delivery_Option
|
||||
discounts: list
|
||||
# form: Form_Product
|
||||
is_included_VAT: bool
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.is_unavailable_in_currency_or_region = False
|
||||
self.is_available = True
|
||||
"""
|
||||
|
||||
def make_from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
|
||||
# Initialiser - validation
|
||||
_m = 'Basket_Item.make_from_product_and_quantity'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity, 'quantity', _m, product.get_quantity_min(), v_arg_type=v_arg_type)
|
||||
basket_item = Basket_Item()
|
||||
basket_item.product = product
|
||||
basket_item.quantity = quantity
|
||||
basket_item.is_included_VAT = is_included_VAT
|
||||
return basket_item
|
||||
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Basket_Item.add_discount', Discount, v_arg_type='class attribute')
|
||||
self.discounts.append(discount)
|
||||
|
||||
def set_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Basket_Item.set_delivery_option', Delivery_Option, v_arg_type='class attribute')
|
||||
self.delivery_option = delivery_option
|
||||
|
||||
def update_quantity(self, quantity):
|
||||
_m = 'Basket_Item.update_quantity'
|
||||
v_arg_type = 'class attribute'
|
||||
av.full_val_float(quantity, 'quantity', _m, self.product.get_quantity_min(), v_arg_type=v_arg_type)
|
||||
self.quantity = quantity
|
||||
|
||||
def jsonify(self):
|
||||
return jsonify(self)
|
||||
|
||||
def to_json(self):
|
||||
permutation = self.product.get_permutation_selected()
|
||||
return {
|
||||
'product_id': self.product.id_product,
|
||||
'permutation_id': permutation.id_permutation,
|
||||
'price': permutation.output_price(self.is_included_VAT),
|
||||
'quantity': self.quantity
|
||||
}
|
||||
|
||||
def get_subtotal(self):
|
||||
permutation = self.product.get_permutation_selected()
|
||||
return round(self.product.get_price_local(self.is_included_VAT) * self.quantity, 2) if permutation.is_available else 0
|
||||
|
||||
def output_currency(self):
|
||||
return self.product.output_currency()
|
||||
|
||||
def output_subtotal(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
subtotal = self.get_subtotal()
|
||||
permutation = self.product.get_permutation_selected()
|
||||
return 'Not available in this currency or region' if permutation.is_unavailable_in_currency_or_region else 'Not available' if not permutation.is_available else f'{self.product.output_currency()} {locale.format_string("%d", subtotal, grouping=True)}'
|
||||
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
product: {self.product}
|
||||
quantity: {self.quantity}
|
||||
subtotal: {self.get_subtotal()}
|
||||
'''
|
||||
|
||||
class Basket():
|
||||
items: list
|
||||
def __init__(self):
|
||||
self.items = []
|
||||
def add_item(self, item):
|
||||
av.val_instance(item, 'item', 'Basket.add_item', Basket_Item)
|
||||
self.items.append(item)
|
||||
def to_csv(self):
|
||||
ids_permutation = ''
|
||||
quantities_permutation = ''
|
||||
for b_i in range(len(self.items)):
|
||||
basket_item = self.items[b_i]
|
||||
product = basket_item.product
|
||||
if b_i > 0:
|
||||
ids_permutation += ','
|
||||
quantities_permutation += ','
|
||||
ids_permutation += str(product.get_id_permutation())
|
||||
quantities_permutation += str(basket_item.quantity)
|
||||
print(f'ids_permutation_basket = {ids_permutation}')
|
||||
print(f'quantities_permutation_basket = {quantities_permutation}')
|
||||
return ids_permutation, quantities_permutation
|
||||
def to_json_list(self):
|
||||
json_list = []
|
||||
for item in self.items:
|
||||
json_list.append(item.to_json())
|
||||
return json_list
|
||||
def to_json(self):
|
||||
return {'items': self.to_json_list()}
|
||||
def output_total(self):
|
||||
sum = 0
|
||||
for b_i in self.items:
|
||||
sum += b_i.get_subtotal()
|
||||
symbol = self.items[0].output_currency() if len(self.items) > 0 else ''
|
||||
|
||||
return f'{symbol} {locale.format_string("%d", sum, grouping=True)}'
|
||||
def len(self):
|
||||
return len(self.items)
|
||||
"""
|
||||
def get_key_product_index_from_ids_product_permutation(id_product, id_permutation):
|
||||
return f'{id_product},{"" if id_permutation is None else id_permutation}'
|
||||
"""
|
||||
def __repr__(self):
|
||||
repr = f'Basket:'
|
||||
for basket_item in self.items:
|
||||
print(f'{basket_item}')
|
||||
repr = f'{repr}\n{basket_item}'
|
||||
return repr
|
||||
|
||||
def get_ids_permutation_unavailable(self):
|
||||
ids_permutation = []
|
||||
for item in self.items:
|
||||
permutation = item.product.get_permutation_selected()
|
||||
if not permutation.is_available:
|
||||
ids_permutation.append(permutation.id_permutation)
|
||||
return ids_permutation
|
||||
273
business_objects/category.py
Normal file
273
business_objects/category.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Category Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from business_objects.product import Product, Product_Permutation, Price
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.discount import Discount
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Enum_Product_Category(Enum):
|
||||
ASSISTIVE_DEVICES = 0
|
||||
HOME_DECOR = 1
|
||||
MISCELLANEOUS = 99
|
||||
|
||||
def text(self):
|
||||
return Enum_Product_Category.Enum_Product_Category_Text(self)
|
||||
|
||||
def Enum_Product_Category_Text(category):
|
||||
av.val_instance(category, 'category', 'Product_Category_Enum_Text', Enum_Product_Category)
|
||||
if category == Enum_Product_Category.ASSISTIVE_DEVICES:
|
||||
return 'Assistive devices'
|
||||
elif category == Enum_Product_Category.HOME_DECOR:
|
||||
return 'Home decor'
|
||||
else:
|
||||
return 'Other'
|
||||
|
||||
def get_member_by_text(text):
|
||||
av.val_str(text, 'text', 'Enum_Product_Category.get_member_by_text')
|
||||
return data_types.get_enum_member_by_text(Enum_Product_Category, text.upper())
|
||||
|
||||
|
||||
class Category(db.Model):
|
||||
id_category = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
display_order = db.Column(db.Integer)
|
||||
"""
|
||||
def __new__(cls, id, name, description, display_order):
|
||||
_m = 'Category.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_str(description, 'description', _m, max_len=4001, v_arg_type=v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
|
||||
return super(Category, cls).__new__(cls)
|
||||
"""
|
||||
def __init__(self): # , id, name, description, display_order):
|
||||
"""
|
||||
self.id_category = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.display_order = display_order
|
||||
"""
|
||||
self.products = []
|
||||
self.product_index = {}
|
||||
super().__init__()
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
category = Category()
|
||||
category.id_category = query_row[0]
|
||||
category.name = query_row[1]
|
||||
category.description = query_row[2]
|
||||
category.display_order = query_row[3]
|
||||
return category
|
||||
"""
|
||||
def key_product_index_from_ids_product_permutation(id_product, id_permutation):
|
||||
return f'{id_product},{"" if id_permutation is None else id_permutation}'
|
||||
def key_product_index_from_product(product):
|
||||
av.val_instance(product, 'product', 'Category.key_product_index_from_product', Product)
|
||||
return f'{product.id_product},{"" if product.id_permutation is None else product.id_permutation}'
|
||||
"""
|
||||
def get_index_product(self, product):
|
||||
return self.get_index_product_from_id(product.id_product)
|
||||
def get_index_product_from_id(self, id_product):
|
||||
try:
|
||||
return self.product_index[id_product]
|
||||
except:
|
||||
raise KeyError(f'product id: {id_product} not in product index keys: {self.product_index.keys()} with category id: {self.id_category}')
|
||||
def get_index_product_from_id_permutation(self, id_permutation):
|
||||
for product in self.products:
|
||||
try:
|
||||
index_permutation = product.get_index_permutation_from_id(id_permutation)
|
||||
return self.get_index_product(product)
|
||||
except:
|
||||
pass
|
||||
raise KeyError(f'permutation id: {id_permutation} not in category id: {self.id_category}')
|
||||
|
||||
def add_product(self, product):
|
||||
_m = 'Category.add_product'
|
||||
av.val_instance(product, 'product', _m, Product)
|
||||
# self.product_index.append(len(self.products))
|
||||
# self.product_index[Category.key_product_index_from_ids_product_permutation(product.id_product, product.id_permutation)] = len(self.products)
|
||||
try:
|
||||
self.get_index_product(product)
|
||||
raise ValueError(f"{av.error_msg_str(product, 'product', _m, Product)}\nProduct already in category.")
|
||||
except KeyError:
|
||||
self.product_index[product.id_product] = len(self.products)
|
||||
self.products.append(product)
|
||||
def add_permutation(self, permutation):
|
||||
_m = 'Category.add_permutation'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
|
||||
# self.product_index.append(len(self.products))
|
||||
# self.product_index[Category.key_product_index_from_ids_product_permutation(product.id_product, product.id_permutation)] = len(self.products)
|
||||
index_product = self.get_index_product_from_id(permutation.id_product)
|
||||
# index_product = self.product_index[permutation.id_product]
|
||||
self.products[index_product].add_permutation(permutation)
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
|
||||
index_product = self.get_index_product_from_id(variation.id_product)
|
||||
self.products[index_product].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Category.add_price', Price)
|
||||
index_product = self.get_index_product_from_id(price.id_product)
|
||||
self.products[index_product].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Category.add_image', Image)
|
||||
index_product = self.get_index_product_from_id(image.id_product)
|
||||
self.products[index_product].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Category.add_delivery_option', Delivery_Option)
|
||||
index_product = self.get_index_product_from_id(delivery_option.id_product)
|
||||
self.products[index_product].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
|
||||
index_product = self.get_index_product_from_id(discount.id_product)
|
||||
self.products[index_product].add_discount(discount)
|
||||
|
||||
def get_all_variation_trees(self):
|
||||
for product in self.products:
|
||||
if product.has_variations:
|
||||
print(f'product with id:{product.id_product} has variations')
|
||||
product.get_variation_trees()
|
||||
|
||||
"""
|
||||
def index_product_from_ids_product_permutation(self, id_product, id_permutation):
|
||||
key = Category.key_product_index_from_ids_product_permutation(id_product, id_permutation)
|
||||
print(f'product_index: {self.product_index}')
|
||||
print(f'Key Error: {key}')
|
||||
try:
|
||||
return self.product_index[key]
|
||||
except KeyError:
|
||||
pass
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_category}
|
||||
name: {self.name}
|
||||
description: {self.description}
|
||||
display_order: {self.display_order}
|
||||
products: {self.products}
|
||||
'''
|
||||
|
||||
def get_permutation_first(self):
|
||||
if not (len(self.products) == 0):
|
||||
print(f'getting first permutation from product')
|
||||
return None if len(self.products) == 0 else self.products[0].get_permutation_selected()
|
||||
|
||||
|
||||
class Product_Category_Filters():
|
||||
category_ids: str # csv
|
||||
product_ids: str # csv
|
||||
|
||||
def __new__(cls, product_ids, product_categories):
|
||||
# Initialiser - validation
|
||||
_m = 'Product_Filters.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
|
||||
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
|
||||
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
|
||||
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
|
||||
return super(Product_Category_Filters, cls).__new__(cls)
|
||||
|
||||
def __init__(self, product_ids, product_categories):
|
||||
# Constructor
|
||||
self.ids = product_ids
|
||||
self.categories = product_categories
|
||||
|
||||
class Category_List():
|
||||
categories: list
|
||||
def __init__(self):
|
||||
self.categories = []
|
||||
def add_category(self, category):
|
||||
av.val_instance(category, 'category', 'Category_List.add_category', Category)
|
||||
self.categories.append(category)
|
||||
def get_index_category_from_id(self, id_category):
|
||||
for index_category in range(len(self.categories)):
|
||||
category = self.categories[index_category]
|
||||
if category.id_category == id_category:
|
||||
return index_category
|
||||
raise ValueError(f"{av.error_msg_str(id_category, 'id_category', 'Category_List.get_index_category_from_id', int)}\nID not in list")
|
||||
def get_index_category_from_id_permutation(self, id_permutation):
|
||||
for index_category in range(len(self.categories)):
|
||||
category = self.categories[index_category]
|
||||
try:
|
||||
index_product = category.get_index_product_from_id_permutation(id_permutation)
|
||||
return index_category
|
||||
except:
|
||||
pass
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Category_List.get_index_category_from_id_permutation', int)}. Permutation ID not in list")
|
||||
def add_product(self, product):
|
||||
av.val_instance(product, 'product', 'Category_List.add_product', Product)
|
||||
index_category = self.get_index_category_from_id(product.id_category)
|
||||
self.categories[index_category].add_product(product)
|
||||
def add_permutation(self, permutation):
|
||||
av.val_instance(permutation, 'permutation', 'Category_List.add_permutation', Product_Permutation)
|
||||
index_category = self.get_index_category_from_id(permutation.id_category)
|
||||
self.categories[index_category].add_permutation(permutation)
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
|
||||
index_category = self.get_index_category_from_id(variation.id_category)
|
||||
self.categories[index_category].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Category.add_price', Price)
|
||||
index_category = self.get_index_category_from_id(price.id_category)
|
||||
self.categories[index_category].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Category.add_image', Image)
|
||||
index_category = self.get_index_category_from_id(image.id_category)
|
||||
self.categories[index_category].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Category.add_delivery_option', Delivery_Option)
|
||||
index_category = self.get_index_category_from_id(delivery_option.id_category)
|
||||
self.categories[index_category].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
|
||||
index_category = self.get_index_category_from_id(discount.id_category)
|
||||
self.categories[index_category].add_discount(discount)
|
||||
|
||||
def get_all_variation_trees(self):
|
||||
for category in self.categories:
|
||||
category.get_all_variation_trees()
|
||||
|
||||
def __repr__(self):
|
||||
return f'categories: {self.categories}'
|
||||
|
||||
def get_permutation_first(self):
|
||||
print(f'getting first permutation from category list')
|
||||
if not (len(self.categories) == 0):
|
||||
print(f'getting first permutation from category')
|
||||
return None if len(self.categories) == 0 else self.categories[0].get_permutation_first()
|
||||
|
||||
def get_count_categories(self):
|
||||
return len(self.categories)
|
||||
96
business_objects/currency.py
Normal file
96
business_objects/currency.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
"""
|
||||
class Currency_Enum(Enum):
|
||||
GBP = 1
|
||||
|
||||
def text(self):
|
||||
return Currency_Enum.Currency_Enum_Text(self)
|
||||
|
||||
def Currency_Enum_Text(currency):
|
||||
av.val_instance(currency, 'currency', 'Currency_Enum_Text', Currency_Enum)
|
||||
if currency == Currency_Enum.GBP:
|
||||
return 'GBP'
|
||||
else:
|
||||
# return 'Unknown'
|
||||
raise ValueError("Unknown Currency Enum.")
|
||||
|
||||
def get_member_by_text(text):
|
||||
for member in Resolution_Level_Enum.__members__.values():
|
||||
if member.name == text:
|
||||
return member
|
||||
raise ValueError("Unknown Currency Enum.")
|
||||
# return Resolution_Level_Enum.HIGH
|
||||
"""
|
||||
|
||||
class Currency(db.Model):
|
||||
id_currency = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String)
|
||||
name = db.Column(db.String)
|
||||
symbol = db.Column(db.String)
|
||||
factor_from_GBP = db.Column(db.Float)
|
||||
display_order = db.Column(db.Integer)
|
||||
|
||||
def make_from_DB_currency(query_row):
|
||||
# _m = 'Currency.make_from_DB_currency'
|
||||
# v_arg_type = 'class attribute'
|
||||
currency = Currency()
|
||||
currency.id_currency = query_row[0]
|
||||
currency.code = query_row[1]
|
||||
currency.name = query_row[2]
|
||||
currency.symbol = query_row[3]
|
||||
currency.factor_from_GBP = query_row[4]
|
||||
currency.display_order = query_row[5]
|
||||
return currency
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Currency.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
currency = Currency()
|
||||
currency.id_permutation = query_row[0]
|
||||
currency.id_product = query_row[1]
|
||||
currency.id_category = query_row[2]
|
||||
currency.id_variation = query_row[3]
|
||||
return currency
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_currency}
|
||||
name: {self.name}
|
||||
code: {self.code}
|
||||
symbol: {self.symbol}
|
||||
factor from GBP: {self.factor_from_GBP}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
115
business_objects/delivery_option.py
Normal file
115
business_objects/delivery_option.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Delivery Option Business Object
|
||||
|
||||
Description:
|
||||
Business object for delivery option
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
"""
|
||||
class Delivery_Option():
|
||||
name: str
|
||||
delay_min: int # days
|
||||
delay_max: int
|
||||
quantity_min: float
|
||||
quantity_max: float
|
||||
regions: list # [Enum_Delivery_Region]
|
||||
cost: float
|
||||
|
||||
def __new__(cls, name, delay_min, delay_max, quantity_min, quantity_max, regions, cost):
|
||||
_m = 'Delivery_Option.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_str(name, 'name', _m, v_arg_type = v_arg_type)
|
||||
av.val_int(delay_min, 'delay_min', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_int(delay_max, 'delay_max', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_float(quantity_min, 'quantity_min', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_float(quantity_max, 'quantity_max', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_list_instances(regions, 'regions', _m, Enum_Delivery_Region, v_arg_type = v_arg_type)
|
||||
av.val_float(cost, 'cost', _m, 0, v_arg_type = v_arg_type)
|
||||
return super(Delivery_Option, cls).__new__(cls)
|
||||
|
||||
def __init__(self, name, delay_min, delay_max, quantity_min, quantity_max, regions, cost):
|
||||
self.name = name
|
||||
self.delay_min = delay_min
|
||||
self.delay_max = delay_max
|
||||
self.quantity_min = quantity_min
|
||||
self.quantity_max = quantity_max
|
||||
self.regions = regions
|
||||
self.cost = cost
|
||||
"""
|
||||
class Delivery_Option(db.Model):
|
||||
id_option = db.Column(db.Integer, primary_key=True)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
code = db.Column(db.String(50))
|
||||
name = db.Column(db.String(100))
|
||||
latency_min = db.Column(db.Integer)
|
||||
latency_max = db.Column(db.Integer)
|
||||
quantity_min = db.Column(db.Integer)
|
||||
quantity_max = db.Column(db.Integer)
|
||||
codes_region = db.Column(db.String(4000))
|
||||
names_region = db.Column(db.String(4000))
|
||||
price_GBP = db.Column(db.Float)
|
||||
display_order = db.Column(db.Integer)
|
||||
def __init__(self):
|
||||
self.delivery_regions = []
|
||||
def make_from_DB_product(query_row):
|
||||
option = Delivery_Option()
|
||||
option.id_option = query_row[0]
|
||||
option.id_product = query_row[1]
|
||||
option.id_permutation = query_row[2]
|
||||
option.id_category = query_row[3]
|
||||
option.code = query_row[4]
|
||||
option.name = query_row[5]
|
||||
option.latency_min = query_row[6]
|
||||
option.latency_max = query_row[7]
|
||||
option.quantity_min = query_row[8]
|
||||
option.quantity_max = query_row[9]
|
||||
option.codes_region = query_row[10]
|
||||
option.names_region = query_row[11]
|
||||
option.price_GBP = query_row[12]
|
||||
option.display_order = query_row[13]
|
||||
return option
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_option}
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
name: {self.name}
|
||||
code: {self.code}
|
||||
latency_min: {self.latency_min}
|
||||
latency_max: {self.latency_max}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
codes_region: {self.codes_region}
|
||||
names_region: {self.names_region}
|
||||
price_GBP: {self.price_GBP}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
96
business_objects/delivery_region.py
Normal file
96
business_objects/delivery_region.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Delivery Region Business Object
|
||||
|
||||
Description:
|
||||
Business object for delivery region
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Enum_Delivery_Region(Enum):
|
||||
UK = 0
|
||||
|
||||
class Delivery_Region(db.Model):
|
||||
id_region = db.Column(db.Integer, primary_key=True)
|
||||
"""
|
||||
id_category = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_discount = db.Column(db.Integer)
|
||||
"""
|
||||
code = db.Column(db.String(50))
|
||||
name = db.Column(db.String(200))
|
||||
active = db.Column(db.Boolean)
|
||||
display_order = db.Column(db.Integer)
|
||||
"""
|
||||
def __new__(cls, id, id_category, id_product, id_discount, code, name, display_order):
|
||||
_m = 'Delivery_Region.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_product, 'id_product', _m, 0, v_arg_type = v_arg_type)
|
||||
av.val_int(id_discount, 'id_discount', _m, v_arg_type = v_arg_type)
|
||||
av.val_str(code, 'code', _m, max_len = 50, v_arg_type = v_arg_type)
|
||||
av.val_str(name, 'name', _m, max_len = 100, v_arg_type = v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type = v_arg_type)
|
||||
return super(Delivery_Region, cls).__new__(cls)
|
||||
|
||||
def __init__(self, id, id_category, id_product, id_discount, code, name, display_order):
|
||||
self.id_region = id
|
||||
self.id_category = id_category
|
||||
self.id_product = id_product
|
||||
self.id_discount = id_discount
|
||||
self.name = name
|
||||
self.code = code
|
||||
self.display_order = display_order
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
region = Delivery_Region()
|
||||
region.id_region = query_row[0]
|
||||
region.name = query_row[1]
|
||||
region.code = query_row[2]
|
||||
# self.display_order = query_row[3]
|
||||
return region
|
||||
def make_from_DB_region(query_row):
|
||||
region = Delivery_Region()
|
||||
region.id_region = query_row[0]
|
||||
region.code = query_row[1]
|
||||
region.name = query_row[2]
|
||||
region.active = query_row[3]
|
||||
region.display_order = query_row[4]
|
||||
return region
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_region}
|
||||
name: {self.name}
|
||||
code: {self.code}
|
||||
active: {self.active}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
92
business_objects/discount.py
Normal file
92
business_objects/discount.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Discount Business Object
|
||||
|
||||
Description:
|
||||
Business object for discount
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Discount(db.Model):
|
||||
id_discount = db.Column(db.Integer, primary_key=True)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
code = db.Column(db.String(50))
|
||||
name = db.Column(db.String(200))
|
||||
multiplier = db.Column(db.Float)
|
||||
subtractor = db.Column(db.Float)
|
||||
apply_multiplier_first = db.Column(db.Boolean)
|
||||
quantity_min = db.Column(db.Integer)
|
||||
quantity_max = db.Column(db.Integer)
|
||||
date_start = db.Column(db.DateTime)
|
||||
date_end = db.Column(db.DateTime)
|
||||
codes_region = db.Column(db.String(4000))
|
||||
names_region = db.Column(db.String(4000))
|
||||
codes_currency = db.Column(db.String(4000))
|
||||
names_currency = db.Column(db.String(4000))
|
||||
display_order = db.Column(db.Integer)
|
||||
|
||||
def __init__(self):
|
||||
self.delivery_regions = []
|
||||
def make_from_DB_product(query_row):
|
||||
discount = Discount()
|
||||
discount.id_discount = query_row[0]
|
||||
discount.id_category = query_row[1]
|
||||
discount.id_product = query_row[2]
|
||||
discount.id_permutation = query_row[3]
|
||||
discount.code = query_row[4]
|
||||
discount.name = query_row[5]
|
||||
discount.multiplier = query_row[6]
|
||||
discount.subtractor = query_row[7]
|
||||
discount.apply_multiplier_first = query_row[8]
|
||||
discount.quantity_min = query_row[9]
|
||||
discount.quantity_max = query_row[10]
|
||||
discount.date_start = query_row[11]
|
||||
discount.date_end = query_row[12]
|
||||
discount.codes_region = query_row[13]
|
||||
discount.names_region = query_row[14]
|
||||
discount.codes_currency = query_row[15]
|
||||
discount.names_currency = query_row[16]
|
||||
discount.display_order = query_row[17]
|
||||
return discount
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_discount}
|
||||
id_category: {self.id_category}
|
||||
id_product: {self.id_product}
|
||||
name: {self.name}
|
||||
code: {self.code}
|
||||
multiplier: {self.multiplier}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
date_start: {self.date_start}
|
||||
date_end: {self.date_end}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
|
||||
132
business_objects/image.py
Normal file
132
business_objects/image.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Image Business Object
|
||||
|
||||
Description:
|
||||
Business object for product image
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Resolution_Level_Enum(Enum):
|
||||
THUMBNAIL = 0
|
||||
LOW = 1
|
||||
HIGH = 2
|
||||
FULL = 3
|
||||
|
||||
def text(self):
|
||||
return Resolution_Level_Enum.Resolution_Level_Enum_Text(self)
|
||||
|
||||
def Resolution_Level_Enum_Text(category):
|
||||
av.val_instance(category, 'category', 'Resolution_Level_Enum_Text', Resolution_Level_Enum)
|
||||
if category == Resolution_Level_Enum.THUMBNAIL:
|
||||
return 'Thumbnail'
|
||||
elif category == Resolution_Level_Enum.LOW:
|
||||
return 'Low resolution'
|
||||
elif category == Resolution_Level_Enum.HIGH:
|
||||
return 'High resolution'
|
||||
elif category == Resolution_Level_Enum.FULL:
|
||||
return 'Full resolution'
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
def get_member_by_text(text):
|
||||
for member in Resolution_Level_Enum.__members__.values():
|
||||
if member.name == text:
|
||||
return member
|
||||
return Resolution_Level_Enum.HIGH
|
||||
|
||||
|
||||
class Image(db.Model):
|
||||
id_image = db.Column(db.Integer, primary_key=True)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
url = db.Column(db.String(255))
|
||||
active = db.Column(db.Boolean)
|
||||
display_order = db.Column(db.Integer)
|
||||
"""
|
||||
def __new__(cls, id, id_product, id_category, url, display_order):
|
||||
_m = 'Image.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_product, 'id_product', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(url, 'url', _m, max_len=254, v_arg_type=v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
|
||||
return super(Image, cls).__new__(cls)
|
||||
|
||||
def __init__(self, id, id_product, id_category, url, display_order):
|
||||
self.id_image = id
|
||||
self.id_product = id_product
|
||||
self.id_category = id_category
|
||||
self.url = url
|
||||
self.display_order = display_order
|
||||
super().__init__()
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Image.make_from_DB_product'
|
||||
# print(f'image: {query_row}')
|
||||
image = Image()
|
||||
image.id_image = query_row[0]
|
||||
image.id_product = query_row[1]
|
||||
image.id_permutation = query_row[2]
|
||||
image.id_category = query_row[3]
|
||||
image.url = query_row[4]
|
||||
image.active = query_row[5]
|
||||
image.display_order = query_row[6]
|
||||
return image
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_image}
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
url: {self.url}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
|
||||
|
||||
class Product_Image_Filters():
|
||||
product_id: int
|
||||
get_thumbnail: bool
|
||||
get_remaining_LQ: bool
|
||||
|
||||
def __new__(cls, product_id, get_thumbnail, get_remaining_LQ):
|
||||
# Initialiser - validation
|
||||
_m = 'Product_Filters.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(product_id, 'product_id', _m, v_arg_type=v_arg_type)
|
||||
av.val_bool(get_thumbnail, 'get_thumbnail', _m, v_arg_type=v_arg_type)
|
||||
av.val_bool(get_remaining_LQ, 'get_remaining_LQ', _m, v_arg_type=v_arg_type)
|
||||
return super(Product, cls).__new__(cls)
|
||||
|
||||
def __init__(self, product_id, get_thumbnail, get_remaining_LQ):
|
||||
# Constructor
|
||||
self.product_id = product_id
|
||||
self.get_thumbnail = get_thumbnail
|
||||
self.get_remaining_LQ = get_remaining_LQ
|
||||
|
||||
93
business_objects/order.py
Normal file
93
business_objects/order.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Order Business Object
|
||||
|
||||
Description:
|
||||
Business object for order
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
# from lib import data_types
|
||||
from business_objects.product import Product
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
# from forms import Form_Product
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
# external
|
||||
# from enum import Enum
|
||||
from flask import jsonify
|
||||
import locale
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
# CLASSES
|
||||
class Order():
|
||||
category: str
|
||||
product: Product
|
||||
quantity: int
|
||||
subtotal: float
|
||||
delivery_option: Delivery_Option
|
||||
# form: Form_Product
|
||||
|
||||
def __new__(cls, category, product, quantity):
|
||||
# Initialiser - validation
|
||||
_m = 'Product.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_str(category, 'category', _m, v_arg_type=v_arg_type)
|
||||
av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity, 'quantity', _m, product.quantity_min, v_arg_type=v_arg_type)
|
||||
return super(Basket_Item, cls).__new__(cls)
|
||||
|
||||
def __init__(self, category, product, quantity):
|
||||
# Constructor
|
||||
self.category = category
|
||||
self.product = product
|
||||
self.quantity = quantity
|
||||
self.subtotal = round(self.product.price_GBP_full * self.quantity, 2)
|
||||
"""
|
||||
self.form = Form_Product()
|
||||
if self.form.validate_on_submit():
|
||||
# Handle form submission
|
||||
|
||||
pass
|
||||
"""
|
||||
|
||||
def update_quantity(self, quantity):
|
||||
_m = 'Basket_Item.update_quantity'
|
||||
v_arg_type = 'class attribute'
|
||||
av.full_val_float(quantity, 'quantity', _m, self.product.quantity_min, v_arg_type=v_arg_type)
|
||||
self.quantity = quantity
|
||||
self.subtotal = round(self.product.price_GBP_full * self.quantity, 2)
|
||||
|
||||
def jsonify(self):
|
||||
return jsonify(self)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'product_id': self.product.id_product,
|
||||
'price': self.product.price_GBP_full,
|
||||
'quantity': self.quantity
|
||||
}
|
||||
|
||||
def output_subtotal(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.subtotal, grouping=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
category: {self.category}
|
||||
product: {self.product}
|
||||
quantity: {self.quantity}
|
||||
subtotal: {self.subtotal}
|
||||
'''
|
||||
704
business_objects/product.py
Normal file
704
business_objects/product.py
Normal file
@@ -0,0 +1,704 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from dataclasses import dataclass
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Enum_Status_Stock(Enum):
|
||||
OUT = 0
|
||||
LOW = 1
|
||||
IN = 99
|
||||
|
||||
def text(self):
|
||||
return Enum_Status_Stock.Enum_Status_Stock_Text(self)
|
||||
|
||||
def Enum_Status_Stock_Text(status):
|
||||
av.val_instance(status, 'category', 'Enum_Status_Stock_Text', Enum_Status_Stock)
|
||||
if status == Enum_Status_Stock.OUT:
|
||||
return 'Out of stock'
|
||||
elif status == Enum_Status_Stock.LOW:
|
||||
return 'Low on stock'
|
||||
else:
|
||||
return 'Fully stocked'
|
||||
|
||||
def get_member_by_text(text):
|
||||
return data_types.get_enum_member_by_text(Enum_Status_Stock, text.upper())
|
||||
|
||||
class Variation_Tree_Node():
|
||||
variation: Variation
|
||||
node_parent: None
|
||||
nodes_child: list
|
||||
def __init__(self):
|
||||
self.nodes_child = []
|
||||
def make_from_variation_and_node_parent(variation, node_parent):
|
||||
node = Variation_Tree_Node()
|
||||
node.variation = variation
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def make_from_node_parent(node_parent):
|
||||
node = Variation_Tree_Node()
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def add_child(self, node_child):
|
||||
self.nodes_child.append(node_child)
|
||||
def is_leaf(self):
|
||||
return (len(self.nodes_child) == 0)
|
||||
class Variation_Tree:
|
||||
node_root: Variation_Tree_Node
|
||||
def make_from_node_root(node_root):
|
||||
tree = Variation_Tree()
|
||||
tree.node_root = node_root
|
||||
return tree
|
||||
def get_variation_type_list(self):
|
||||
variation_types = []
|
||||
node = self.node_root
|
||||
at_leaf_node = node.is_leaf()
|
||||
while not at_leaf_node:
|
||||
variation_types.append(node.variation.name_variation_type)
|
||||
at_leaf_node = node.is_leaf()
|
||||
if not at_leaf_node:
|
||||
node = node.nodes_child[0]
|
||||
return variation_types
|
||||
def is_equal(self, tree):
|
||||
my_type_list = self.get_variation_type_list()
|
||||
sz_me = len(my_type_list)
|
||||
other_type_list = tree.get_variation_type_list()
|
||||
sz_other = len(other_type_list)
|
||||
is_equal = (sz_me == sz_other)
|
||||
if is_equal:
|
||||
for index_type in range(sz_me):
|
||||
if sz_me[index_type] != sz_other[index_type]:
|
||||
is_equal = False
|
||||
break
|
||||
return is_equal
|
||||
def make_from_product_permutation(product_permutation):
|
||||
depth_max = len(product_permutation.variations)
|
||||
node_root = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[0], None)
|
||||
node = node_root
|
||||
for depth in range(depth_max - 1):
|
||||
node = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[depth + 1], node)
|
||||
return Variation_Tree.make_from_node_root(node_root)
|
||||
|
||||
class Product(db.Model):
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_category = db.Column(db.Integer)
|
||||
name = db.Column(db.String(255))
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# has_variations: bool
|
||||
# index_permutation_selected: int
|
||||
|
||||
def __init__(self):
|
||||
self.permutations = []
|
||||
self.permutation_index = {}
|
||||
self.variation_trees = []
|
||||
self.index_permutation_selected = None
|
||||
self.has_variations = False
|
||||
super().__init__()
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Product.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
product = Product()
|
||||
product.id_product = query_row[0]
|
||||
product.id_category = query_row[5]
|
||||
product.name = query_row[2]
|
||||
product.has_variations = av.input_bool(query_row[4], "has_variations", _m, v_arg_type=v_arg_type)
|
||||
product.display_order = query_row[17]
|
||||
product.can_view = av.input_bool(query_row[19], "can_view", _m, v_arg_type=v_arg_type)
|
||||
product.can_edit = av.input_bool(query_row[20], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
product.can_admin = av.input_bool(query_row[21], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return product
|
||||
"""
|
||||
def make_from_permutation(permutation, has_variations = False):
|
||||
_m = 'Product.make_from_permutation'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation, v_arg_type=v_arg_type)
|
||||
product = Product()
|
||||
product.has_variations = has_variations
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutation.id_product
|
||||
product.id_category = permutation.id_category
|
||||
product.display_order = permutation.display_order
|
||||
product.can_view = permutation.can_view
|
||||
product.can_edit = permutation.can_edit
|
||||
product.can_admin = permutation.can_admin
|
||||
product.permutations.append(permutation)
|
||||
# product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def add_permutation(self, permutation):
|
||||
_m = 'Product.add_permutation'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
|
||||
try:
|
||||
self.permutation_index[permutation.id_permutation]
|
||||
raise ValueError(f"{av.error_msg_str(permutation, 'permutation', _m, Product_Permutation)}\nPermutation ID already in product")
|
||||
except KeyError:
|
||||
self.permutation_index[permutation.id_permutation] = len(self.permutations)
|
||||
self.permutations.append(permutation)
|
||||
"""
|
||||
if self.has_variations:
|
||||
self.has_variations = False
|
||||
"""
|
||||
if self.index_permutation_selected is None:
|
||||
self.index_permutation_selected = self.permutation_index[permutation.id_permutation]
|
||||
print(f'setting selected permutation for product {self.id_product} to {self.index_permutation_selected}') # :\n{self.permutations[self.index_permutation_selected]}
|
||||
"""
|
||||
def make_from_permutations(permutations):
|
||||
_m = 'Product.make_from_permutations'
|
||||
v_arg_type = 'class attribute'
|
||||
if len(permutations) == 0:
|
||||
raise ValueError(av.error_msg_str(permutations, 'permutations', _m, list, v_arg_type=v_arg_type))
|
||||
product = Product()
|
||||
product.has_variations = True
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutations[0].id_product
|
||||
product.id_category = permutations[0].id_category
|
||||
product.display_order = permutations[0].display_order
|
||||
product.can_view = True
|
||||
product.can_edit = True
|
||||
product.can_admin = True
|
||||
for permutation in permutations:
|
||||
product.can_view &= permutation.can_view
|
||||
product.can_edit &= permutation.can_edit
|
||||
product.can_admin &= permutation.can_admin
|
||||
product.permutations.append(permutations)
|
||||
product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def get_variation_trees(self):
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
variation_tree = Variation_Tree.make_from_product_permutation(self.permutations[index_permutation])
|
||||
found_variation_tree_match = False
|
||||
for index_tree in range(len(self.variation_trees)):
|
||||
if self.variation_trees[index_tree].is_equal(variation_tree):
|
||||
found_variation_tree_match = True
|
||||
break
|
||||
if not found_variation_tree_match:
|
||||
self.variation_trees.append(variation_tree)
|
||||
|
||||
def make_from_DB_Stripe_product(query_row):
|
||||
permutation = Product_Permutation.make_from_DB_Stripe_product(query_row)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def make_from_DB_Stripe_price(query_row):
|
||||
permutation = Product_Permutation.make_from_DB_Stripe_price(query_row)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
permutation = Product_Permutation.make_from_json(json_basket_item, key_id_product, key_id_permutation)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def get_permutation_selected(self):
|
||||
try:
|
||||
return self.permutations[self.index_permutation_selected]
|
||||
except:
|
||||
raise ValueError(f'list index {self.index_permutation_selected} out of range')
|
||||
def output_lead_time(self):
|
||||
return self.get_permutation_selected().output_lead_time()
|
||||
def output_delivery_date(self):
|
||||
return self.get_permutation_selected().output_delivery_date()
|
||||
def output_price(self, is_included_VAT):
|
||||
return self.get_permutation_selected().output_price(is_included_VAT)
|
||||
def output_price_VAT_incl(self):
|
||||
return self.get_permutation_selected().output_price(True)
|
||||
def output_price_VAT_excl(self):
|
||||
return self.get_permutation_selected().output_price(False)
|
||||
def get_price_local(self, is_included_VAT):
|
||||
if is_included_VAT:
|
||||
return self.get_price_local_VAT_incl()
|
||||
else:
|
||||
return self.get_price_local_VAT_excl()
|
||||
def get_price_local_VAT_incl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_incl()
|
||||
def get_price_local_VAT_excl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_excl()
|
||||
def get_quantity_min(self):
|
||||
return self.get_permutation_selected().quantity_min
|
||||
def get_id_permutation(self):
|
||||
return self.get_permutation_selected().id_permutation
|
||||
def get_image_from_index(self, index_image):
|
||||
return self.get_permutation_selected().images[index_image]
|
||||
def get_name(self):
|
||||
return self.get_permutation_selected().name
|
||||
def get_description(self):
|
||||
return self.get_permutation_selected().description
|
||||
def output_currency(self):
|
||||
return self.get_permutation_selected().get_price().symbol_currency
|
||||
"""
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
name: {self.name}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
has_variations: {self.has_variations}
|
||||
permutations: {self.permutations}
|
||||
variation trees: {self.variation_trees}
|
||||
'''
|
||||
"""
|
||||
def get_index_permutation_from_id(self, id_permutation):
|
||||
if id_permutation is None and not self.has_variations:
|
||||
return 0
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
permutation = self.permutations[index_permutation]
|
||||
if permutation.id_permutation == id_permutation:
|
||||
return index_permutation
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Product.get_index_permutation_from_id', int)}\nPermutation ID not found.")
|
||||
"""
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Product.add_variation', Variation)
|
||||
# print(f'variation: {variation}')
|
||||
index_permutation = self.permutation_index[variation.id_permutation] # self.get_index_permutation_from_id(variation.id_permutation)
|
||||
self.permutations[index_permutation].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Product.add_price', Price)
|
||||
index_permutation = self.permutation_index[price.id_permutation] # self.get_index_permutation_from_id(price.id_permutation)
|
||||
self.permutations[index_permutation].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Product.add_image', Image)
|
||||
index_permutation = self.permutation_index[image.id_permutation] # self.get_index_permutation_from_id(image.id_permutation)
|
||||
self.permutations[index_permutation].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Product.add_delivery_option', Delivery_Option)
|
||||
index_permutation = self.permutation_index[delivery_option.id_permutation] # self.get_index_permutation_from_id(delivery_option.id_permutation)
|
||||
self.permutations[index_permutation].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Product.add_discount', Discount)
|
||||
index_permutation = self.permutation_index[discount.id_permutation] # self.get_index_permutation_from_id(discount.id_permutation)
|
||||
self.permutations[index_permutation].add_discount(discount)
|
||||
|
||||
|
||||
class Product_Permutation(db.Model):
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_permutation = db.Column(db.Integer, primary_key=True)
|
||||
# name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
# price_GBP_full = db.Column(db.Float)
|
||||
# price_GBP_min = db.Column(db.Float)
|
||||
has_variations = db.Column(db.Boolean)
|
||||
id_category = db.Column(db.Integer)
|
||||
latency_manufacture = db.Column(db.Integer)
|
||||
quantity_min = db.Column(db.Float)
|
||||
quantity_max = db.Column(db.Float)
|
||||
quantity_step = db.Column(db.Float)
|
||||
quantity_stock = db.Column(db.Float)
|
||||
id_stripe_product = db.Column(db.String(100))
|
||||
is_subscription = db.Column(db.Boolean)
|
||||
name_recurrence_interval = db.Column(db.String(255))
|
||||
name_plural_recurrence_interval = db.Column(db.String(256))
|
||||
count_recurrence_interval = db.Column(db.Integer)
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# is_unavailable_in_currency_or_region: bool
|
||||
# is_available: bool
|
||||
|
||||
def __init__(self):
|
||||
self.variations = []
|
||||
self.variation_index = {}
|
||||
self.prices = []
|
||||
self.price_index = {}
|
||||
self.images = []
|
||||
self.image_index = {}
|
||||
self.delivery_options = []
|
||||
self.delivery_option_index = {}
|
||||
self.discounts = []
|
||||
self.discount_index = {}
|
||||
super().__init__()
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
self.is_unavailable_in_currency_or_region = False
|
||||
self.is_available = True
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'query_row: {query_row}')
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
permutation.id_permutation = query_row[1]
|
||||
# permutation.name = query_row[2]
|
||||
permutation.description = query_row[3]
|
||||
# permutation.price_GBP_full = query_row[4]
|
||||
# permutation.price_GBP_min = query_row[5]
|
||||
permutation.has_variations = query_row[4]
|
||||
permutation.id_category = query_row[5]
|
||||
permutation.latency_manufacture = query_row[6]
|
||||
permutation.quantity_min = query_row[7]
|
||||
permutation.quantity_max = query_row[8]
|
||||
permutation.quantity_step = query_row[9]
|
||||
permutation.quantity_stock = query_row[10]
|
||||
permutation.id_stripe_product = query_row[11]
|
||||
permutation.is_subscription = av.input_bool(query_row[12], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[13]
|
||||
permutation.name_plural_recurrence_interval = query_row[14]
|
||||
permutation.count_recurrence_interval = query_row[15]
|
||||
permutation.display_order = query_row[18]
|
||||
permutation.can_view = av.input_bool(query_row[19], "can_view", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_edit = av.input_bool(query_row[20], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_admin = av.input_bool(query_row[21], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return permutation
|
||||
|
||||
def make_from_DB_Stripe_product(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_Stripe_product'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.name = query_row[1]
|
||||
permutation.description = query_row[2]
|
||||
return permutation
|
||||
|
||||
def make_from_DB_Stripe_price(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_Stripe_price'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.price_GBP_full = query_row[1]
|
||||
permutation.id_stripe_product = query_row[2]
|
||||
permutation.is_subscription = av.input_bool(query_row[3], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[4]
|
||||
permutation.count_recurrence_interval = query_row[5]
|
||||
return permutation
|
||||
|
||||
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
_m = 'Product_Permutation.make_from_json'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = json_basket_item[key_id_product]
|
||||
permutation.id_permutation = json_basket_item[key_id_permutation]
|
||||
return permutation
|
||||
|
||||
def get_price(self):
|
||||
return self.prices[0]
|
||||
def get_price_local_VAT_incl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_incl
|
||||
def get_price_local_VAT_excl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_excl
|
||||
|
||||
def output_lead_time(self):
|
||||
return '1 day' if self.latency_manufacture == 1 else f'{self.latency_manufacture} days'
|
||||
|
||||
def output_delivery_date(self):
|
||||
return (datetime.now() + timedelta(days=self.latency_manufacture)).strftime('%A, %d %B %Y')
|
||||
|
||||
def output_price(self, is_included_VAT):
|
||||
if self.is_unavailable_in_currency_or_region:
|
||||
return 'Not available in currency and region'
|
||||
if not self.is_available:
|
||||
return 'Not available'
|
||||
price = self.get_price()
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if is_included_VAT:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_incl, grouping=True)}'
|
||||
else:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_excl, grouping=True)}'
|
||||
def output_currency(self):
|
||||
if not self.is_available:
|
||||
return ''
|
||||
price = self.get_price()
|
||||
return price.code_currency
|
||||
"""
|
||||
def output_price_VAT_incl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_incl, grouping=True)
|
||||
def output_price_VAT_excl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_excl, grouping=True)
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product_Permutation
|
||||
id_product: {self.id_product}
|
||||
id_permutation: {self.id_permutation}
|
||||
description: {self.description}
|
||||
id_category: {self.id_category}
|
||||
latency_manufacture: {self.latency_manufacture}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
quantity_step: {self.quantity_step}
|
||||
quantity_stock: {self.quantity_stock}
|
||||
id_stripe_product: {self.id_stripe_product}
|
||||
is_subscription: {self.is_subscription}
|
||||
name_recurrence_interval: {self.name_recurrence_interval}
|
||||
name_plural_recurrence_interval: {self.name_plural_recurrence_interval}
|
||||
count_recurrence_interval: {self.count_recurrence_interval}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
variations: {self.variations}
|
||||
images: {self.images}
|
||||
delivery_options: {self.delivery_options}
|
||||
prices: {self.prices}
|
||||
'''
|
||||
"""
|
||||
price_GBP_full: {self.price_GBP_full}
|
||||
price_GBP_min: {self.price_GBP_min}
|
||||
"""
|
||||
|
||||
def add_variation(self, variation):
|
||||
_m = 'Product_Permutation.add_variation'
|
||||
av.val_instance(variation, 'variation', _m, Variation)
|
||||
try:
|
||||
self.variation_index[variation.id_variation]
|
||||
raise ValueError(f"{av.error_msg_str(variation, 'variation', _m, Variation)}\nVariation already in product.")
|
||||
except KeyError:
|
||||
self.variation_index[variation.id_variation] = len(self.variations)
|
||||
self.variations.append(variation)
|
||||
def add_price(self, price):
|
||||
_m = 'Product_Permutation.add_price'
|
||||
av.val_instance(price, 'price', _m, Price)
|
||||
try:
|
||||
self.price_index[price.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(price, 'price', _m, Price)}\nPrice already in product.")
|
||||
except KeyError:
|
||||
self.price_index[price.display_order] = len(self.prices)
|
||||
self.prices.append(price)
|
||||
def add_image(self, image):
|
||||
_m = 'Product_Permutation.add_image'
|
||||
av.val_instance(image, 'image', _m, Image)
|
||||
try:
|
||||
self.image_index[image.id_image]
|
||||
raise ValueError(f"{av.error_msg_str(image, 'image', _m, Image)}\nImage already in product.")
|
||||
except KeyError:
|
||||
self.image_index[image.id_image] = len(self.images)
|
||||
self.images.append(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
_m = 'Product_Permutation.add_delivery_option'
|
||||
av.val_instance(delivery_option, 'delivery_option', _m, Delivery_Option)
|
||||
try:
|
||||
self.delivery_option_index[delivery_option.id_option]
|
||||
raise ValueError(f"{av.error_msg_str(delivery_option, 'delivery_option', _m, Delivery_Option)}\nDelivery_Option already in product.")
|
||||
except KeyError:
|
||||
self.delivery_option_index[delivery_option.id_option] = len(self.delivery_options)
|
||||
self.delivery_options.append(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
_m = 'Product_Permutation.add_discount'
|
||||
av.val_instance(discount, 'discount', _m, Discount)
|
||||
try:
|
||||
self.discount_index[discount.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(discount, 'discount', _m, Discount)}\nDiscount already in product.")
|
||||
except KeyError:
|
||||
self.discount_index[discount.display_order] = len(self.discounts)
|
||||
self.discounts.append(discount)
|
||||
|
||||
def get_image_from_index(self, index_image):
|
||||
try:
|
||||
return self.images[index_image]
|
||||
except:
|
||||
raise IndexError(f"Invalid image index: {index_image}")
|
||||
|
||||
def get_price_from_code_currency(self, code_currency):
|
||||
for price in self.prices:
|
||||
if price.code_currency == code_currency:
|
||||
return price
|
||||
|
||||
"""
|
||||
class Product_Filters():
|
||||
ids: str # csv
|
||||
categories: str # csv
|
||||
|
||||
def __new__(cls, product_ids, product_categories):
|
||||
# Initialiser - validation
|
||||
_m = 'Product_Filters.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
|
||||
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
|
||||
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
|
||||
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
|
||||
return super(Product, cls).__new__(cls)
|
||||
|
||||
def __init__(self, product_ids, product_categories):
|
||||
# Constructor
|
||||
self.ids = product_ids
|
||||
self.categories = product_categories
|
||||
|
||||
class Price():
|
||||
product: Product
|
||||
currency: Currency_Enum
|
||||
|
||||
def make_from_product_currency(product, code_currency):
|
||||
name_method = 'make_from_product_currency'
|
||||
av.val_instance(product, 'product', name_method, Product)
|
||||
price = Price()
|
||||
price.product = product
|
||||
price.currency = Currency_Enum.get_member_by_text(code_currency)
|
||||
"""
|
||||
|
||||
class Price(db.Model):
|
||||
id_price = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_currency = db.Column(db.Integer)
|
||||
code_currency = db.Column(db.String)
|
||||
name_currency = db.Column(db.String)
|
||||
symbol_currency = db.Column(db.String)
|
||||
id_region = db.Column(db.Integer)
|
||||
value_local_VAT_incl = db.Column(db.Float)
|
||||
value_local_VAT_excl = db.Column(db.Float)
|
||||
display_order = db.Column(db.Float, primary_key=True)
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Price.make_from_DB_product'
|
||||
price = Price()
|
||||
price.id_price = query_row[0]
|
||||
price.id_permutation = query_row[1]
|
||||
price.id_product = query_row[2]
|
||||
price.id_category = query_row[3]
|
||||
price.id_currency = query_row[4]
|
||||
price.code_currency = query_row[5]
|
||||
price.name_currency = query_row[6]
|
||||
price.symbol_currency = query_row[7]
|
||||
price.id_region = query_row[8]
|
||||
price.value_local_VAT_incl = query_row[9]
|
||||
price.value_local_VAT_excl = query_row[10]
|
||||
price.display_order = query_row[11]
|
||||
return price
|
||||
|
||||
def __repr__(self):
|
||||
return f'''Price
|
||||
id: {self.id_price}
|
||||
id_permutation: {self.id_permutation}
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
id_currency: {self.id_currency}
|
||||
code_currency: {self.code_currency}
|
||||
name_currency: {self.name_currency}
|
||||
symbol_currency: {self.symbol_currency}
|
||||
id_region: {self.id_region}
|
||||
value_local (VAT incl): {self.value_local_VAT_incl}
|
||||
value_local (VAT excl): {self.value_local_VAT_excl}
|
||||
display_order (UID): {self.display_order}
|
||||
'''
|
||||
"""
|
||||
class Permutation_Variation_Link(db.Model):
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_variation = db.Column(db.Integer)
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Permutation_Variation_Link.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
link = Permutation_Variation_Link()
|
||||
link.id_permutation = query_row[0]
|
||||
link.id_product = query_row[1]
|
||||
link.id_category = query_row[2]
|
||||
link.id_variation = query_row[3]
|
||||
return link
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Product_Filters():
|
||||
id_user: str
|
||||
get_all_category: bool
|
||||
ids_category: str
|
||||
get_inactive_category: bool
|
||||
get_all_product: bool
|
||||
ids_product: str
|
||||
get_inactive_product: bool
|
||||
get_first_product_only: bool
|
||||
get_all_permutation: bool
|
||||
ids_permutation: str
|
||||
get_inactive_permutation: bool
|
||||
get_all_image: bool
|
||||
ids_image: str
|
||||
get_inactive_image: bool
|
||||
get_first_image_only: bool
|
||||
get_all_region: bool
|
||||
ids_region: str
|
||||
get_inactive_region: bool
|
||||
get_all_currency: bool
|
||||
ids_currency: str
|
||||
get_inactive_currency: bool
|
||||
get_all_discount: bool
|
||||
ids_discount: str
|
||||
get_inactive_discount: bool
|
||||
def to_json(self):
|
||||
return {
|
||||
'a_id_user': self.id_user,
|
||||
'a_get_all_category': self.get_all_category,
|
||||
'a_ids_category': self.ids_category,
|
||||
'a_get_inactive_category': self.get_inactive_category,
|
||||
'a_get_all_product': self.get_all_product,
|
||||
'a_ids_product': self.ids_product,
|
||||
'a_get_inactive_product': self.get_inactive_product,
|
||||
'a_get_first_product_only': self.get_first_product_only,
|
||||
'a_get_all_permutation': self.get_all_permutation,
|
||||
'a_ids_permutation': self.ids_permutation,
|
||||
'a_get_inactive_permutation': self.get_inactive_permutation,
|
||||
'a_get_all_image': self.get_all_image,
|
||||
'a_ids_image': self.ids_image,
|
||||
'a_get_inactive_image': self.get_inactive_image,
|
||||
'a_get_first_image_only': self.get_first_image_only,
|
||||
'a_get_all_delivery_region': self.get_all_region,
|
||||
'a_ids_delivery_region': self.ids_region,
|
||||
'a_get_inactive_delivery_region': self.get_inactive_region,
|
||||
'a_get_all_currency': self.get_all_currency,
|
||||
'a_ids_currency': self.ids_currency,
|
||||
'a_get_inactive_currency': self.get_inactive_currency,
|
||||
'a_get_all_discount': self.get_all_discount,
|
||||
'a_ids_discount': self.ids_discount,
|
||||
'a_get_inactive_discount': self.get_inactive_discount
|
||||
}
|
||||
65
business_objects/sql_error.py
Normal file
65
business_objects/sql_error.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: SQL Error Business Object
|
||||
|
||||
Description:
|
||||
Business object for SQL errors
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class SQL_Error(db.Model):
|
||||
display_order = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String(50))
|
||||
msg = db.Column(db.String(4000))
|
||||
name = db.Column(db.String(500))
|
||||
description = db.Column(db.String(4000))
|
||||
|
||||
"""
|
||||
def __new__(cls, display_order, code, msg):
|
||||
_m = 'SQL_Error.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(display_order, 'display_order', _m)
|
||||
av.val_str(code, 'code', _m, max_len=50, v_arg_type=v_arg_type)
|
||||
av.val_str(msg, 'msg', _m, max_len=4000, v_arg_type=v_arg_type)
|
||||
return super(SQL_Error, cls).__new__(cls)
|
||||
|
||||
def __init__(self, display_order, code, msg):
|
||||
self.display_order = display_order
|
||||
self.code = code
|
||||
self.msg = msg
|
||||
super().__init__()
|
||||
"""
|
||||
|
||||
def make_from_DB_record(record):
|
||||
error = SQL_Error()
|
||||
error.display_order = record[0]
|
||||
error.code = record[1]
|
||||
error.msg = record[2]
|
||||
error.name = record[3]
|
||||
error.description = record[4]
|
||||
return error
|
||||
164
business_objects/stripe.py
Normal file
164
business_objects/stripe.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Stripe Business Object
|
||||
|
||||
Description:
|
||||
Business objects for Stripe
|
||||
"""
|
||||
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Stripe_Product(db.Model):
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
price_GBP_full = db.Column(db.Float)
|
||||
id_category = db.Column(db.Integer)
|
||||
lead_time_manuf = db.Column(db.Integer)
|
||||
quantity_min = db.Column(db.Float)
|
||||
quantity_max = db.Column(db.Float)
|
||||
quantity_step = db.Column(db.Float)
|
||||
quantity_stock = db.Column(db.Float)
|
||||
id_stripe_product = db.Column(db.String(255))
|
||||
id_stripe_price = db.Column(db.String(255))
|
||||
is_subscription = db.Column(db.Boolean)
|
||||
name_recurring_interval = db.Column(db.String(255))
|
||||
name_plural_recurring_interval = db.Column(db.String(256))
|
||||
count_recurring_interval = db.Column(db.Integer)
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
|
||||
def __new__(cls, id, name, description, price_GBP_full, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
|
||||
is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, display_order, can_view, can_edit, can_admin):
|
||||
_m = 'Product.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_str(description, 'description', _m, max_len=4000, v_arg_type=v_arg_type)
|
||||
av.full_val_float(price_GBP_full, 'price_GBP_full', _m, 0., v_arg_type=v_arg_type)
|
||||
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(lead_time_manuf, 'lead_time_manuf', _m, 0, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity_step, 'quantity_step', _m, 0., v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity_min, 'quantity_min', _m, quantity_step, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity_max, 'quantity_max', _m, quantity_min, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity_stock, 'quantity_stock', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(id_stripe_product, 'id_stripe_product', _m, max_len=100, v_arg_type=v_arg_type)
|
||||
av.val_str(id_stripe_price, 'id_stripe_price', _m, max_len=100, v_arg_type=v_arg_type)
|
||||
av.full_val_bool(is_subscription, 'is_subscription', _m, v_arg_type=v_arg_type)
|
||||
print(f'is_subscription: {is_subscription}, {av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)}')
|
||||
is_subscription = av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
if is_subscription:
|
||||
av.val_str(name_recurring_interval, 'name_recurring_interval', _m, max_len=255, v_arg_type=v_arg_type)
|
||||
av.val_str(name_plural_recurring_interval, 'name_plural_recurring_interval', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_int(count_recurring_interval, 'count_recurring_interval', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
|
||||
av.full_val_bool(can_view, 'can_view', _m, v_arg_type=v_arg_type)
|
||||
# can_view = av.input_bool(can_view, "can_view", _m, v_arg_type=v_arg_type)
|
||||
av.full_val_bool(can_edit, 'can_edit', _m, v_arg_type=v_arg_type)
|
||||
# can_edit = av.input_bool(can_edit, "can_edit", _m, v_arg_type=v_arg_type)
|
||||
av.full_val_bool(can_admin, 'can_admin', _m, v_arg_type=v_arg_type)
|
||||
# can_admin = av.input_bool(can_admin, "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return super(Product, cls).__new__(cls) # , id, name, description, price_GBP, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
|
||||
# is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, can_view, can_edit, can_admin)
|
||||
|
||||
def __init__(self, id, name, description, price_GBP_full, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
|
||||
is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, display_order, can_view, can_edit, can_admin):
|
||||
_m = 'Product.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
self.id_product = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.price_GBP_full = price_GBP_full
|
||||
self.id_category = id_category
|
||||
self.lead_time_manuf = lead_time_manuf
|
||||
self.quantity_min = quantity_min
|
||||
self.quantity_max = quantity_max
|
||||
self.quantity_step = quantity_step
|
||||
self.quantity_stock = quantity_stock
|
||||
self.id_stripe_product = id_stripe_product
|
||||
self.id_stripe_price = id_stripe_price
|
||||
self.is_subscription = av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
self.name_recurring_interval = name_recurring_interval
|
||||
self.name_plural_recurring_interval = name_plural_recurring_interval
|
||||
self.count_recurring_interval = count_recurring_interval
|
||||
self.display_order = display_order
|
||||
self.can_view = av.input_bool(can_view, "can_view", _m, v_arg_type=v_arg_type)
|
||||
self.can_edit = av.input_bool(can_edit, "can_edit", _m, v_arg_type=v_arg_type)
|
||||
self.can_admin = av.input_bool(can_admin, "can_admin", _m, v_arg_type=v_arg_type)
|
||||
self.variations = []
|
||||
self.images = []
|
||||
self.delivery_options = []
|
||||
self.discounts = []
|
||||
self.discount_index = {}
|
||||
super().__init__()
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
|
||||
def output_lead_time(self):
|
||||
return '1 day' if self.lead_time_manuf == 1 else f'{self.lead_time_manuf} days'
|
||||
|
||||
def output_delivery_date(self):
|
||||
return (datetime.now() + timedelta(days=self.lead_time_manuf)).strftime('%A, %d %B %Y')
|
||||
|
||||
def output_price(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_full, grouping=True)
|
||||
"""
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product
|
||||
id: {self.id_product}
|
||||
name: {self.name}
|
||||
description: {self.description}
|
||||
price_GBP_full: {self.price_GBP_full}
|
||||
id_category: {self.id_category}
|
||||
lead_time_manuf: {self.lead_time_manuf}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
quantity_step: {self.quantity_step}
|
||||
quantity_stock: {self.quantity_stock}
|
||||
id_stripe_product: {self.id_stripe_product}
|
||||
id_stripe_price: {self.id_stripe_price}
|
||||
is_subscription: {self.is_subscription}
|
||||
name_recurring_interval: {self.name_recurring_interval}
|
||||
name_plural_recurring_interval: {self.name_plural_recurring_interval}
|
||||
count_recurring_interval: {self.count_recurring_interval}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
variations: {self.variations}
|
||||
images: {self.images}
|
||||
delivery_options: {self.delivery_options}
|
||||
'''
|
||||
|
||||
def add_discount(self, discount):
|
||||
_m = 'Category.add_product'
|
||||
av.val_instance(discount, 'discount', _m, Discount)
|
||||
# self.product_index.append(len(self.products))
|
||||
self.discount_index[discount.id_discount] = len(self.discounts)
|
||||
self.discounts.append(discount)
|
||||
94
business_objects/variation.py
Normal file
94
business_objects/variation.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Variation Business Object
|
||||
|
||||
Description:
|
||||
Business object for product variation
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Variation(db.Model):
|
||||
id_variation = db.Column(db.Integer, primary_key=True)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
code_variation_type = db.Column(db.String(50))
|
||||
name_variation_type = db.Column(db.String(255))
|
||||
code_variation = db.Column(db.String(50))
|
||||
name_variation = db.Column(db.String(255))
|
||||
display_order = db.Column(db.Integer)
|
||||
|
||||
"""
|
||||
def __new__(cls, id, id_product, id_category, name_type, code_type, name, code, display_order):
|
||||
_m = 'Variation.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_product, 'id_product', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(code_type, 'code_type', _m, max_len=50, v_arg_type=v_arg_type)
|
||||
av.val_str(name_type, 'name_type', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_str(code, 'code', _m, max_len=50, v_arg_type=v_arg_type)
|
||||
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
|
||||
return super(Variation, cls).__new__(cls)
|
||||
|
||||
def __init__(self, id, id_product, id_category, name_type, code_type, name, code, display_order):
|
||||
self.id_variation = id
|
||||
self.id_product = id_product
|
||||
self.id_category = id_category
|
||||
self.name_variation_type = name_type
|
||||
self.code_variation_type = code_type
|
||||
self.name_variation = name
|
||||
self.code_variation = code
|
||||
self.display_order = display_order
|
||||
super().__init__()
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
variation = Variation()
|
||||
variation.id_variation = query_row[0]
|
||||
variation.id_product = query_row[1]
|
||||
variation.id_permutation = query_row[2]
|
||||
variation.id_category = query_row[3]
|
||||
variation.code_variation_type = query_row[4]
|
||||
variation.name_variation_type = query_row[5]
|
||||
variation.code_variation = query_row[6]
|
||||
variation.name_variation = query_row[7]
|
||||
variation.display_order = query_row[8]
|
||||
return variation
|
||||
def __repr__(self):
|
||||
return f'''
|
||||
id: {self.id_variation}
|
||||
id_product: {self.id_product}
|
||||
id_permutation: {self.id_permutation}
|
||||
id_category: {self.id_category}
|
||||
code_variation_type: {self.code_variation_type}
|
||||
name_variation_type: {self.name_variation_type}
|
||||
code_variation: {self.code_variation}
|
||||
name_variation: {self.name_variation}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
59
config.py
Normal file
59
config.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Configuration
|
||||
|
||||
Description:
|
||||
Configuration variables
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
import os
|
||||
|
||||
# CLASSES
|
||||
class Config:
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
SECRET_KEY = os.getenv('KEY_SECRET_FLASK') # gen cmd: openssl rand -hex 32
|
||||
# Add other configuration variables as needed
|
||||
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
ID_AUTH0_CLIENT = os.getenv('ID_AUTH0_CLIENT')
|
||||
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
|
||||
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
|
||||
ID_TOKEN_USER = 'user'
|
||||
is_included_VAT = True
|
||||
"""
|
||||
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
|
||||
code_currency = 1
|
||||
KEY_CODE_CURRENCY = 'id_currency'
|
||||
code_region_delivery = 1
|
||||
KEY_CODE_REGION_DELIVERY = 'id_region_delivery'
|
||||
KEY_ID_CURRENCY = 'id_currency'
|
||||
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
|
||||
"""
|
||||
id_currency = 1
|
||||
id_region_delivery = 1
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
# Add development-specific configuration variables
|
||||
|
||||
class ProductionConfig(Config):
|
||||
# Add production-specific configuration variables
|
||||
pass
|
||||
|
||||
# Set the configuration class based on the environment
|
||||
# You can change 'development' to 'production' when deploying
|
||||
config_env = 'development'
|
||||
if config_env == 'development':
|
||||
app_config = DevelopmentConfig
|
||||
elif config_env == 'production':
|
||||
app_config = ProductionConfig
|
||||
else:
|
||||
raise ValueError("Invalid configuration environment")
|
||||
|
||||
|
||||
11
datastores/__init__.py
Normal file
11
datastores/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Module Initialisation
|
||||
Feature: DataStores
|
||||
|
||||
Description:
|
||||
Initialises datastores module.
|
||||
"""
|
||||
BIN
datastores/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
datastores/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
datastores/__pycache__/datastore_store.cpython-311.pyc
Normal file
BIN
datastores/__pycache__/datastore_store.cpython-311.pyc
Normal file
Binary file not shown.
738
datastores/datastore_store.py
Normal file
738
datastores/datastore_store.py
Normal file
@@ -0,0 +1,738 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: DataStores
|
||||
Feature: Store DataStore
|
||||
|
||||
Description:
|
||||
Datastore for Store
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
# from routes import bp_home
|
||||
import lib.argument_validation as av
|
||||
from business_objects.category import Category_List, Category
|
||||
from business_objects.product import Product, Product_Permutation, Price, Product_Filters # Permutation_Variation_Link
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.currency import Currency
|
||||
from business_objects.delivery_region import Delivery_Region
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.basket import Basket, Basket_Item
|
||||
from business_objects.order import Order
|
||||
from business_objects.sql_error import SQL_Error
|
||||
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
|
||||
# external
|
||||
# from abc import ABC, abstractmethod, abstractproperty
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy import text
|
||||
import stripe
|
||||
import os
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class DataStore_Store():
|
||||
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
|
||||
KEY_ID_CURRENCY = 'id_currency'
|
||||
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
|
||||
# Attributes
|
||||
db: SQLAlchemy
|
||||
# Global constants
|
||||
|
||||
def __new__(cls, db, info_user, app):
|
||||
# Initialiser - validation
|
||||
_m = 'DataStore_Store.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
|
||||
return super(DataStore_Store, cls).__new__(cls)
|
||||
|
||||
def __init__(self, db, info_user, app):
|
||||
# Constructor
|
||||
self.db = db
|
||||
self.info_user = info_user
|
||||
self.app = app
|
||||
self.key_secret_stripe = os.environ.get("KEY_SECRET_STRIPE")
|
||||
self.key_public_stripe = os.environ.get("KEY_PUBLIC_STRIPE")
|
||||
|
||||
# For sample support and debugging, not required for production:
|
||||
stripe.set_app_info(
|
||||
'stripe-samples/checkout-one-time-payments',
|
||||
version='0.0.1',
|
||||
url='https://github.com/stripe-samples/checkout-one-time-payments')
|
||||
|
||||
stripe.api_key = self.key_secret_stripe
|
||||
|
||||
|
||||
def get_many_product_category(self, product_filters):
|
||||
# redundant argument validation?
|
||||
_m = 'DataStore_Store.get_many_product_category'
|
||||
av.val_instance(product_filters, 'product_filters', _m, Product_Filters)
|
||||
"""
|
||||
av.val_str(category_ids, 'category_ids', _m)
|
||||
av.val_str(product_ids, 'product_ids', _m)
|
||||
av.val_bool(get_all_category, 'get_all_category', _m)
|
||||
av.val_bool(get_all_product, 'get_all_product', _m)
|
||||
av.val_int(max_products_per_category, 'max_products_per_category', _m)
|
||||
argument_dict_list = {
|
||||
'a_id_user': self.info_user.get('sub'),
|
||||
'a_get_all_categories': 0,
|
||||
'a_ids_category': category_ids,
|
||||
'a_get_inactive_categories': 0,
|
||||
'a_ids_product': product_ids,
|
||||
'a_get_inactive_products': 0,
|
||||
'a_get_first_product_only': 1 if (max_products_per_category == 1) else 0,
|
||||
'a_get_all_products': 1 if get_all_product else 0,
|
||||
'a_ids_image': '',
|
||||
'a_get_inactive_images': 0,
|
||||
'a_get_first_image_only': 0,
|
||||
'a_get_all_images': 1
|
||||
}
|
||||
"""
|
||||
argument_dict = product_filters.to_json()
|
||||
print(f'argument_dict: {argument_dict}')
|
||||
print('executing p_shop_get_many_product')
|
||||
result = self.db_procedure_execute('p_shop_get_many_product', argument_dict)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
# categories, category_index = DataStore_Store.input_many_product(cursor)
|
||||
category_list, errors = DataStore_Store.input_many_product(cursor)
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return category_list, errors # categories, category_index
|
||||
|
||||
|
||||
def edit_basket(self, ids_permutation_basket, quantities_permutation_basket, id_permutation_edit, quantity_permutation_edit, sum_not_edit, id_currency, id_region_delivery):
|
||||
# redundant argument validation?
|
||||
_m = 'DataStore_Store.edit_basket'
|
||||
print(f'{_m}\nstarting...')
|
||||
# av.val_instance(filters, 'filters', _m, Product_Category_Filters)
|
||||
# av.val_str(ids_product_basket, 'ids_product_basket', _m)
|
||||
av.val_str(ids_permutation_basket, 'ids_permutation_basket', _m)
|
||||
# av.val_str(quantities_product_basket, 'quantities_product_basket', _m)
|
||||
av.val_str(quantities_permutation_basket, 'quantities_permutation_basket', _m)
|
||||
"""
|
||||
if id_product_edit == 'None':
|
||||
id_product_edit = None
|
||||
else:
|
||||
print(f'id_product_edit: {id_product_edit}')
|
||||
av.val_int(id_product_edit, 'id_product_edit', _m)
|
||||
"""
|
||||
if id_permutation_edit == 'None' or str(type(id_permutation_edit)) =="<class 'NoneType'>":
|
||||
id_permutation_edit = None
|
||||
else:
|
||||
print(f'id_permutation_edit: {id_permutation_edit}')
|
||||
print(str(type(id_permutation_edit)))
|
||||
av.val_int(id_permutation_edit, 'id_permutation_edit', _m)
|
||||
if quantity_permutation_edit == 'None' or str(type(quantity_permutation_edit)) =="<class 'NoneType'>":
|
||||
quantity_permutation_edit = None
|
||||
else:
|
||||
print(f'quantity_permutation_edit: {quantity_permutation_edit}')
|
||||
av.val_int(quantity_permutation_edit, 'quantity_permutation_edit', _m)
|
||||
if sum_not_edit == 'None':
|
||||
sum_not_edit = None
|
||||
else:
|
||||
print(f'sum_not_edit: {sum_not_edit}')
|
||||
av.val_bool(sum_not_edit, 'sum_not_edit', _m)
|
||||
|
||||
argument_dict_list = {
|
||||
'a_id_user': self.info_user.get('sub'),
|
||||
# 'a_ids_product_basket': ids_product_basket,
|
||||
'a_ids_permutation_basket': ids_permutation_basket,
|
||||
# 'a_quantities_product_basket': quantities_product_basket,
|
||||
'a_quantities_permutation_basket': quantities_permutation_basket,
|
||||
# 'a_id_product_edit': id_product_edit if id_permutation_edit is None else None,
|
||||
'a_id_permutation_edit': id_permutation_edit,
|
||||
'a_quantity_permutation_edit': quantity_permutation_edit,
|
||||
'a_sum_not_edit': 1 if sum_not_edit else 0,
|
||||
'a_id_currency': id_currency,
|
||||
'a_id_region_purchase': id_region_delivery
|
||||
}
|
||||
|
||||
result = self.db_procedure_execute('p_shop_edit_user_basket', argument_dict_list)
|
||||
print('data received')
|
||||
|
||||
cursor = result.cursor
|
||||
|
||||
# categories, category_index = DataStore_Store.input_many_product(cursor)
|
||||
category_list, errors = DataStore_Store.input_many_product(cursor)
|
||||
|
||||
print(f'cursor: {str(cursor)}')
|
||||
|
||||
# Basket
|
||||
if not cursor.nextset():
|
||||
raise Exception("No more query results! Cannot open basket contents")
|
||||
result_set = cursor.fetchall()
|
||||
print(f'raw basket: {result_set}')
|
||||
# print(f'variations: {result_set_3}')
|
||||
# variations = [Variation(**row) for row in result_set_3]
|
||||
basket = Basket()
|
||||
for row in result_set:
|
||||
index_category = category_list.get_index_category_from_id(row[0])
|
||||
category = category_list.categories[index_category]
|
||||
index_product = category.get_index_product_from_id(row[1])
|
||||
product = category.products[index_product]
|
||||
basket_item = Basket_Item.make_from_product_and_quantity_and_VAT_included(product, row[7], self.app.is_included_VAT)
|
||||
print(f'adding basket item: {row}')
|
||||
print(f'basket item: {basket_item}')
|
||||
basket.add_item(basket_item) # basket.append(basket_item) # Basket_Item(category.name, product, row[4]))
|
||||
|
||||
print(f'basket: {basket}')
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_2]
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return basket
|
||||
|
||||
def edit_user(self):
|
||||
# redundant argument validation?
|
||||
_m = 'DataStore_Store.edit_user'
|
||||
# av.val_instance(filters, 'filters', _m, Product_Category_Filters)
|
||||
|
||||
argument_dict_list = {
|
||||
'a_id_user': self.info_user.get('sub'),
|
||||
'a_name': self.info_user.get('name'),
|
||||
'a_email': self.info_user.get('email'),
|
||||
'a_email_verified': 1 if self.info_user.get('email_verified') == 'True' else 0
|
||||
}
|
||||
|
||||
result = self.db_procedure_execute('p_shop_edit_user', argument_dict_list)
|
||||
cursor = result.cursor
|
||||
|
||||
result_set_1 = cursor.fetchall()
|
||||
print(f'raw user data: {result_set_1}')
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_2]
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return (result_set_1[0][1] == b'\x01')
|
||||
|
||||
|
||||
def db_procedure_execute(self, proc_name, argument_dict_list = None):
|
||||
# Argument validation
|
||||
_m = 'DataStore_Store.db_procedure_execute'
|
||||
av.val_str(proc_name, 'proc_name', _m)
|
||||
has_arguments = not str(type(argument_dict_list)) == "<class 'NoneType'>"
|
||||
if has_arguments:
|
||||
# av.val_list_instances(argument_dict_list, 'argument_dict_list', _m, dict)
|
||||
pass
|
||||
# Methods
|
||||
proc_string = f'CALL {proc_name}('
|
||||
if has_arguments:
|
||||
arg_keys = list(argument_dict_list.keys())
|
||||
for i in range(len(arg_keys)):
|
||||
proc_string += f'{"" if i == 0 else ", "}:{arg_keys[i]}'
|
||||
proc_string += ')'
|
||||
proc_string = text(proc_string)
|
||||
print(f'{_m}\nproc_string: {proc_string}\nargs: {argument_dict_list}')
|
||||
# with self.db.session.begin() as session:
|
||||
if has_arguments:
|
||||
result = self.db.session.execute(proc_string, argument_dict_list)
|
||||
else:
|
||||
result = self.db.session.execute(proc_string)
|
||||
print(f'result: {result}')
|
||||
return result
|
||||
cursor = result.cursor
|
||||
result_set_1 = cursor.fetchall()
|
||||
print(f'categories: {result_set_1}')
|
||||
cursor.nextset()
|
||||
result_set_2 = cursor.fetchall()
|
||||
print(f'products: {result_set_2}')
|
||||
|
||||
"""
|
||||
def get_many_id_price(self, product_ids):
|
||||
_m = 'DataStore_Store.get_many_id_price'
|
||||
av.val_str(product_ids, 'product_ids', _m)
|
||||
price_ids = []
|
||||
for product_id in product_ids.split(','):
|
||||
if product_id == 'prod_PB0NUOSEs06ymG':
|
||||
price_ids.append() # get price id
|
||||
return price_ids
|
||||
"""
|
||||
|
||||
def input_many_product(cursor):
|
||||
_m = 'DataStore_Store.input_many_product'
|
||||
category_list = Category_List()
|
||||
# Categories
|
||||
result_set_1 = cursor.fetchall()
|
||||
print(f'raw categories: {result_set_1}')
|
||||
# categories = [Category(row[0], row[1], row[2], row[3]) for row in result_set_1]
|
||||
# categories = []
|
||||
# category_index = {}
|
||||
for row in result_set_1:
|
||||
new_category = Category.make_from_DB_product(row) # Category(row[0], row[1], row[2], row[3])
|
||||
# category_index[new_category.id_category] = len(categories)
|
||||
# categories.append(new_category)
|
||||
category_list.add_category(new_category)
|
||||
# print(f'categories: {[c.id_category for c in categories]}')
|
||||
|
||||
# Products
|
||||
cursor.nextset()
|
||||
result_set_2 = cursor.fetchall()
|
||||
# print(f'products: {result_set_2}')
|
||||
products = [] # [Product(**row) for row in result_set_2]
|
||||
product_index = {}
|
||||
for row in result_set_2:
|
||||
new_permutation = Product_Permutation.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
|
||||
index_category = category_list.get_index_category_from_id(new_permutation.id_category)
|
||||
category = category_list.categories[index_category]
|
||||
try:
|
||||
index_product = category.get_index_product_from_id(new_permutation.id_product)
|
||||
category_list.add_permutation(new_permutation)
|
||||
# product = products[index_product]
|
||||
# product.add_permutation(new_permutation)
|
||||
except KeyError:
|
||||
product_index[new_permutation.id_product] = len(products)
|
||||
product = Product.make_from_DB_product(row)
|
||||
product.add_permutation(new_permutation)
|
||||
products.append(product)
|
||||
# categories[category_index[new_product.id_category]].add_product(new_product)
|
||||
category_list.add_product(product)
|
||||
# category_list.add_permutation(new_permutation)
|
||||
# print(f'products: {[p.id_product for p in products]}') # {products}')
|
||||
print(f'category_list: {category_list}')
|
||||
|
||||
# Variations
|
||||
cursor.nextset()
|
||||
result_set_3 = cursor.fetchall()
|
||||
# print(f'variations: {result_set_3}')
|
||||
# variations = [Variation(**row) for row in result_set_3]
|
||||
variations = []
|
||||
for row in result_set_3:
|
||||
new_variation = Variation.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
|
||||
variations.append(new_variation)
|
||||
# products[product_index[new_variation.id_product]].variations.append(new_variation)
|
||||
# index_category = category_index[new_variation.id_category]
|
||||
# index_product = categories[index_category].index_product_from_ids_product_permutation(new_variation.id_product, new_variation.id_permutation)
|
||||
# categories[index_category].products[index_product].variations.append(new_variation)
|
||||
category_list.add_variation(new_variation)
|
||||
# print(f'variations: {variations}')
|
||||
# print(f'products: {[p.id_product for p in products]}')
|
||||
|
||||
"""
|
||||
# Permutation Variation Links
|
||||
cursor.nextset()
|
||||
result_set_4 = cursor.fetchall()
|
||||
# print(f'variations: {result_set_3}')
|
||||
# variations = [Variation(**row) for row in result_set_3]
|
||||
permutation_variation_links = []
|
||||
for row in result_set_3:
|
||||
permutation_variation_link = Permutation_Variation_Link.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
|
||||
permutation_variation_links.append(permutation_variation_link)
|
||||
# categories[category_index[new_variation.id_category]].products[categories[category_index[new_variation.id_category]].product_index[new_variation.id_product]].variations.append(new_variation)
|
||||
categories[category_index[new_variation.id_category]].products[categories[category_index[new_variation.id_category]].product_index[new_variation.id_product]].variations.append(new_variation)
|
||||
print(f'variations: {variations}')
|
||||
print(f'products: {[p.id_product for p in products]}')
|
||||
"""
|
||||
|
||||
# Prices
|
||||
cursor.nextset()
|
||||
result_set_4 = cursor.fetchall()
|
||||
# print(f'variations: {result_set_3}')
|
||||
# variations = [Variation(**row) for row in result_set_3]
|
||||
prices = []
|
||||
for row in result_set_4:
|
||||
price = Price.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
|
||||
prices.append(price)
|
||||
"""
|
||||
index_category = category_index[price.id_category]
|
||||
index_product = categories[index_category].index_product_from_ids_product_permutation(price.id_product, price.id_permutation)
|
||||
categories[index_category].products[index_product].prices.append(price)
|
||||
"""
|
||||
category_list.add_price(price)
|
||||
# print(f'prices: {prices}')
|
||||
# print(f'products: {[p.id_product for p in products]}')
|
||||
|
||||
"""
|
||||
# Currencies
|
||||
cursor.nextset()
|
||||
result_set_5 = cursor.fetchall()
|
||||
# print(f'variations: {result_set_3}')
|
||||
# variations = [Variation(**row) for row in result_set_3]
|
||||
prices = []
|
||||
for row in result_set_4:
|
||||
price = Price.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
|
||||
prices.append(price)
|
||||
index_category = category_index[price.id_category]
|
||||
categories[index_category].products[index_category].product_index[price.id_product].prices.append(price)
|
||||
print(f'prices: {prices}')
|
||||
print(f'products: {[p.id_product for p in products]}')
|
||||
"""
|
||||
|
||||
# Images
|
||||
cursor.nextset()
|
||||
result_set_5 = cursor.fetchall()
|
||||
# print(f'images: {result_set_4}')
|
||||
# images = [Image(**row) for row in result_set_4]
|
||||
images = []
|
||||
for row in result_set_5:
|
||||
new_image = Image.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4])
|
||||
images.append(new_image)
|
||||
# products[product_index[new_image.id_product]].images.append(new_image)
|
||||
"""
|
||||
index_category = category_index[new_image.id_category]
|
||||
index_product = categories[index_category].index_product_from_ids_product_permutation(new_image.id_product, new_image.id_permutation)
|
||||
categories[index_category].products[index_product].images.append(new_image)
|
||||
"""
|
||||
category_list.add_image(new_image)
|
||||
# print(f'images: {images}')
|
||||
# print(f'products: {[p.id_product for p in products]}')
|
||||
|
||||
# Delivery options
|
||||
cursor.nextset()
|
||||
result_set_7 = cursor.fetchall()
|
||||
delivery_options = []
|
||||
for row in result_set_7:
|
||||
new_delivery_option = Delivery_Option.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12])
|
||||
delivery_options.append(new_delivery_option)
|
||||
# products[product_index[new_delivery_option.id_product]].delivery_options.append(new_delivery_option)
|
||||
"""
|
||||
index_category = category_index[new_delivery_option.id_category]
|
||||
index_product = categories[index_category].index_product_from_ids_product_permutation(new_delivery_option.id_product, new_delivery_option.id_permutation)
|
||||
categories[index_category].products[index_product].delivery_options.append(new_delivery_option)
|
||||
"""
|
||||
category_list.add_delivery_option(new_delivery_option)
|
||||
# print(f'delivery_options: {delivery_options}')
|
||||
# print(f'products: {products}')
|
||||
|
||||
# Discounts
|
||||
cursor.nextset()
|
||||
result_set_8 = cursor.fetchall()
|
||||
discounts = []
|
||||
for row in result_set_8:
|
||||
new_discount = Discount.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10])
|
||||
discounts.append(new_discount)
|
||||
# i_cat = category_index[new_discount.id_category]
|
||||
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts.append(new_delivery_option)
|
||||
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discount_index[new_discount.id_discount] = len(categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts)
|
||||
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts.append(new_discount)
|
||||
"""
|
||||
index_category = category_index[new_discount.id_category]
|
||||
index_product = categories[index_category].index_product_from_ids_product_permutation(new_discount.id_product, new_discount.id_permutation)
|
||||
categories[index_category].products[index_product].add_discount(new_discount)
|
||||
"""
|
||||
category_list.add_discount(new_discount)
|
||||
# print(f'discounts: {discounts}')
|
||||
# print(f'products: {products}')
|
||||
|
||||
"""
|
||||
# Delivery Regions
|
||||
cursor.nextset()
|
||||
result_set_6 = cursor.fetchall()
|
||||
delivery_regions = []
|
||||
for row in result_set_6:
|
||||
new_delivery_region = Delivery_Region.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6])
|
||||
delivery_regions.append(new_delivery_region)
|
||||
# products[product_index[new_delivery_option.id_product]].delivery_regions.append(new_delivery_region)
|
||||
i_cat = category_index[new_delivery_region.id_category]
|
||||
i_prod = categories[i_cat].product_index[new_delivery_region.id_product]
|
||||
categories[i_cat].products[i_prod].discounts[categories[i_cat].products[i_prod].
|
||||
categories[i_cat].products[i_prod].discounts[categories[i_cat].products[i_prod].discount_index[new_delivery_region.id_discount]].delivery_regions.append(new_delivery_region)
|
||||
print(f'delivery_regions: {delivery_regions}')
|
||||
print(f'products: {products}')
|
||||
"""
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
errors = []
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # (row[0], row[1])
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
category_list.get_all_variation_trees()
|
||||
"""
|
||||
for category in category_list.categories:
|
||||
print(f'category: {category.name}')
|
||||
for product in category.products:
|
||||
permutation = product.get_permutation_selected()
|
||||
print(f'product: {product.name}\nselected permutation: {permutation}')
|
||||
"""
|
||||
|
||||
if len(errors) > 0:
|
||||
for error in errors:
|
||||
if error.code == 'PRODUCT_AVAILABILITY':
|
||||
ids_permutation_unavailable = DataStore_Store.get_ids_permutation_from_error_availability(error.msg)
|
||||
for id_permutation in ids_permutation_unavailable:
|
||||
index_category = category_list.get_index_category_from_id_permutation(id_permutation)
|
||||
category = category_list.categories[index_category]
|
||||
index_product = category.get_index_product_from_id_permutation(id_permutation)
|
||||
product = category.products[index_product]
|
||||
index_permutation = product.get_index_permutation_from_id(id_permutation)
|
||||
permutation = product.permutations[index_permutation]
|
||||
permutation.is_available = False
|
||||
if 'region' in error.msg or 'currency' in error.msg:
|
||||
permutation.is_unavailable_in_currency_or_region = True
|
||||
|
||||
return category_list, errors # categories, category_index
|
||||
|
||||
def db_cursor_clear(cursor):
|
||||
while cursor.nextset():
|
||||
print(f'new result set: {cursor.fetchall()}')
|
||||
|
||||
def get_ids_permutation_from_error_availability(msg_error_availability):
|
||||
ids_permutation = []
|
||||
index_colon = msg_error_availability.find(':', msg_error_availability.find(':'))
|
||||
msg_error_availability = msg_error_availability[index_colon + 1:]
|
||||
index_comma = 0
|
||||
while index_comma > -1:
|
||||
msg_error_availability = msg_error_availability[index_comma:]
|
||||
index_comma = msg_error_availability.find(',')
|
||||
ids_permutation.append(msg_error_availability[:index_comma])
|
||||
return ids_permutation
|
||||
|
||||
def get_many_user_order(self, id_user, ids_order, n_order_max, id_checkout_session):
|
||||
_m = 'Model_View_Store.get_many_user_order'
|
||||
# av.val_str(id_user)
|
||||
# validation conducted by server
|
||||
|
||||
argument_dict_list = {
|
||||
'a_id_user': id_user,
|
||||
'a_ids_order': ids_order,
|
||||
'a_n_order_max': n_order_max,
|
||||
'a_id_checkout_session': id_checkout_session
|
||||
}
|
||||
|
||||
print('executing p_shop_get_many_user_order')
|
||||
result = self.db_procedure_execute('p_shop_get_many_user_order', argument_dict_list)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
|
||||
|
||||
# Discount Delivery Regions
|
||||
cursor.nextset()
|
||||
result_set_1 = cursor.fetchall()
|
||||
orders = []
|
||||
for row in result_set_1:
|
||||
new_order = Order(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
|
||||
orders.append(new_order)
|
||||
print(f'orders: {orders}')
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return orders
|
||||
|
||||
def get_many_stripe_product_new(self):
|
||||
_m = 'Model_View_Store.get_many_stripe_product_new'
|
||||
_m_db = 'p_shop_get_many_stripe_product_new'
|
||||
# av.val_str(id_user)
|
||||
# validation conducted by server
|
||||
|
||||
argument_dict_list = {
|
||||
'a_id_user': self.info_user
|
||||
}
|
||||
|
||||
print(f'executing {_m_db}')
|
||||
result = self.db_procedure_execute(_m_db, argument_dict_list)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
|
||||
|
||||
# Products
|
||||
cursor.nextset()
|
||||
result_set_1 = cursor.fetchall()
|
||||
products = []
|
||||
for row in result_set_1:
|
||||
new_product = Product.make_from_DB_Stripe_product(row) # Product(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
|
||||
products.append(new_product)
|
||||
print(f'products: {products}')
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return products
|
||||
|
||||
def get_many_stripe_price_new(self):
|
||||
_m = 'Model_View_Store.get_many_stripe_price_new'
|
||||
_m_db = 'p_shop_get_many_stripe_price_new'
|
||||
# av.val_str(id_user)
|
||||
# validation conducted by server
|
||||
|
||||
argument_dict_list = {
|
||||
'a_id_user': self.info_user
|
||||
}
|
||||
|
||||
print(f'executing {_m_db}')
|
||||
result = self.db_procedure_execute(_m_db, argument_dict_list)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
|
||||
|
||||
# Products
|
||||
cursor.nextset()
|
||||
result_set_1 = cursor.fetchall()
|
||||
products = []
|
||||
for row in result_set_1:
|
||||
new_product = Product.make_from_DB_Stripe_price(row) # Product(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
|
||||
products.append(new_product)
|
||||
print(f'products: {products}')
|
||||
|
||||
# Errors
|
||||
cursor.nextset()
|
||||
result_set_e = cursor.fetchall()
|
||||
print(f'raw errors: {result_set_e}')
|
||||
if len(result_set_e) > 0:
|
||||
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
|
||||
for error in errors:
|
||||
print(f"Error [{error.code}]: {error.msg}")
|
||||
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return products
|
||||
|
||||
def get_many_product_new(self):
|
||||
_m = 'Model_View_Store.get_many_product_new'
|
||||
# Stripe
|
||||
new_products = self.get_many_stripe_product_new()
|
||||
for product in new_products:
|
||||
product.id_stripe_product = self.create_stripe_product(product)
|
||||
return new_products
|
||||
|
||||
def get_many_price_new(self):
|
||||
_m = 'Model_View_Store.get_many_product_new'
|
||||
# Stripe
|
||||
new_products = self.get_many_stripe_price_new()
|
||||
for product in new_products:
|
||||
product.id_stripe_price = self.create_stripe_price(product)
|
||||
return new_products
|
||||
|
||||
# Stripe
|
||||
def create_stripe_product(self, product): # _name, product_description):
|
||||
_m = 'Model_View_Store_Checkout.create_stripe_product'
|
||||
# av.val_str(product_name, 'product_name', _m)
|
||||
# av.val_str(product_description, 'product_description', _m)
|
||||
av.val_instance(product, 'product', _m, Product)
|
||||
|
||||
print(f'stripe.api_key = {stripe.api_key}')
|
||||
new_product = stripe.Product.create(
|
||||
name = product.name,
|
||||
description = product.description,
|
||||
)
|
||||
|
||||
# Save these identifiers
|
||||
print(f"Success! Here is your new Stripe product id: {new_product.id}")
|
||||
|
||||
return new_product.id
|
||||
|
||||
def create_stripe_price(self, product, currency): # product_id, product_price, product_currency, product_is_subscription, product_recurring_interval = '', product_interval_count = 0):
|
||||
_m = 'Model_View_Store_Checkout.create_stripe_price'
|
||||
"""
|
||||
av.val_str(p_id, 'p_id', _m)
|
||||
av.full_val_float(p_price, 'p_price', _m, 0.01)
|
||||
p_price = round(p_price, 2)
|
||||
av.val_str(p_currency, 'p_currency', _m)
|
||||
av.full_val_bool(p_is_subscription, 'p_is_subscription', _m)
|
||||
p_is_subscription = bool(p_is_subscription)
|
||||
av.val_str(p_recurring_interval, 'p_recurring_interval', _m)
|
||||
av.full_val_int(p_interval_count, 'p_interval_count', _m, 1 if p_is_subscription else 0)
|
||||
p_interval_count = int(p_interval_count)
|
||||
"""
|
||||
av.val_instance(product, 'product', _m, Product)
|
||||
av.val_str(currency, 'currency', _m)
|
||||
|
||||
print(f'stripe.api_key = {stripe.api_key}')
|
||||
|
||||
new_product_price = stripe.Price.create(
|
||||
unit_amount = product.unit_price,
|
||||
currency = currency,
|
||||
recurring = { "interval": product.name_recurring_interval, "interval_count": product.count_recurring_interval } if product.is_subscription else None,
|
||||
product = product.id_stripe_product
|
||||
)
|
||||
|
||||
# Save these identifiers
|
||||
print(f"Success! Here is your Stripe product price id: {new_product_price.id} for {product.name}")
|
||||
|
||||
return new_product_price.id
|
||||
|
||||
def get_regions_and_currencies(self):
|
||||
_m = 'Model_View_Store.get_regions_and_currencies'
|
||||
_m_db_currency = 'p_shop_get_many_currency'
|
||||
_m_db_region = 'p_shop_get_many_region'
|
||||
|
||||
argument_dict_list_currency = {
|
||||
'a_get_inactive_currency': 0
|
||||
}
|
||||
argument_dict_list_region = {
|
||||
'a_get_inactive_currency': 0
|
||||
}
|
||||
|
||||
print(f'executing {_m_db_currency}')
|
||||
result = self.db_procedure_execute(_m_db_currency, argument_dict_list_currency)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
|
||||
# cursor.nextset()
|
||||
result_set_1 = cursor.fetchall()
|
||||
currencies = []
|
||||
for row in result_set_1:
|
||||
currency = Currency.make_from_DB_currency(row)
|
||||
currencies.append(currency)
|
||||
print(f'currencies: {currencies}')
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
print(f'executing {_m_db_region}')
|
||||
result = self.db_procedure_execute(_m_db_region, argument_dict_list_region)
|
||||
cursor = result.cursor
|
||||
print('data received')
|
||||
|
||||
# cursor.nextset()
|
||||
result_set_1 = cursor.fetchall()
|
||||
regions = []
|
||||
for row in result_set_1:
|
||||
region = Delivery_Region.make_from_DB_region(row)
|
||||
regions.append(region)
|
||||
print(f'regions: {regions}')
|
||||
DataStore_Store.db_cursor_clear(cursor)
|
||||
|
||||
return regions, currencies
|
||||
|
||||
def get_metadata_basket(json_request):
|
||||
is_included_VAT = json_request[DataStore_Store.KEY_IS_INCLUDED_VAT]
|
||||
id_currency = json_request[DataStore_Store.KEY_ID_CURRENCY]
|
||||
id_region_delivery = json_request[DataStore_Store.KEY_ID_REGION_DELIVERY]
|
||||
return id_currency, id_region_delivery, is_included_VAT
|
||||
102
forms.py
Normal file
102
forms.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Forms - User data input
|
||||
|
||||
Description:
|
||||
Defines Flask-WTF forms for handling user input.
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
# external
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, TextAreaField, SubmitField, BooleanField, IntegerField, SelectField
|
||||
from wtforms.validators import InputRequired, NumberRange, Regexp, DataRequired
|
||||
|
||||
|
||||
|
||||
class Form_Contact(FlaskForm):
|
||||
email = StringField('Email address')
|
||||
CC = BooleanField('Would you like to receive a copy of this email request?') # not in use
|
||||
name = StringField('Name')
|
||||
msg = TextAreaField('Message')
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
class Form_Register(FlaskForm):
|
||||
email = StringField('Email address')
|
||||
CC = BooleanField('Would you like to receive a copy of this email request?') # not in use
|
||||
name = StringField('Name')
|
||||
msg = TextAreaField('Message')
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
|
||||
"""
|
||||
class Form_Product(FlaskForm): # for basket, product tiles, product add
|
||||
# PositiveIntegerField with validation constraints
|
||||
quantity = IntegerField(
|
||||
'Quantity',
|
||||
validators=[
|
||||
# InputRequired(message='Quantity'),
|
||||
NumberRange(min=1, message='Please enter a positive integer')
|
||||
],
|
||||
default=1
|
||||
)
|
||||
"""
|
||||
|
||||
class Form_Basket_Add(FlaskForm): # for basket, product tiles, product add
|
||||
# PositiveIntegerField with validation constraints
|
||||
quantity = IntegerField(
|
||||
'Quantity',
|
||||
validators=[
|
||||
# InputRequired(message='Quantity'),
|
||||
NumberRange(min=1, message='Please enter a positive integer')
|
||||
],
|
||||
default=1
|
||||
# render_kw={'id-product': ''} # {Model_View_Store.attr_id_product: ''}
|
||||
)
|
||||
submit = SubmitField('Add')
|
||||
form_type = 'Form_Basket_Add'
|
||||
|
||||
class Form_Basket_Edit(FlaskForm): # for basket, product tiles, product add
|
||||
# PositiveIntegerField with validation constraints
|
||||
quantity = IntegerField(
|
||||
'Quantity',
|
||||
validators=[
|
||||
# InputRequired(message='Quantity'),
|
||||
NumberRange(min=1, message='Please enter a positive integer')
|
||||
],
|
||||
default=1
|
||||
# render_kw={'id-product': ''} # {Model_View_Store.attr_id_product: ''}
|
||||
)
|
||||
submit = SubmitField('Update')
|
||||
form_type = 'Form_Basket_Edit'
|
||||
|
||||
class Form_Billing(FlaskForm):
|
||||
identical = BooleanField('Use delivery address')
|
||||
region = SelectField('Country / region', choices=[('uk', 'UK'), ('international', 'International')], validators=[DataRequired()])
|
||||
name_full = StringField('Full name')
|
||||
phone_number = StringField('Phone number', validators=[Regexp(r'^\+?[0-9\s]{5,20}$', message='Only numbers, plus symbol, and space are allowed.'), DataRequired()])
|
||||
postcode = StringField('Post code', validators=[DataRequired()])
|
||||
address_1 = StringField('Address line 1', validators=[DataRequired()])
|
||||
address_2 = StringField('Address line 2 (optional)')
|
||||
city = StringField('City', validators=[DataRequired()])
|
||||
county = StringField('County', validators=[DataRequired()])
|
||||
submit = SubmitField('Submit')
|
||||
form_type_billing_not_delivery = False
|
||||
|
||||
def output_id(self):
|
||||
return 'formBilling' if self.form_type_billing_not_delivery else 'formDeliver'
|
||||
|
||||
class Form_Is_Included_VAT(FlaskForm):
|
||||
is_included = BooleanField('Include VAT')
|
||||
|
||||
class Form_Delivery_Region(FlaskForm):
|
||||
id_region_delivery = SelectField('Region')
|
||||
|
||||
class Form_Currency(FlaskForm):
|
||||
id_currency = SelectField('Currency')
|
||||
11
helpers/__init__.py
Normal file
11
helpers/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Module Initialisation
|
||||
Feature: Helpers
|
||||
|
||||
Description:
|
||||
Initialises helpers module.
|
||||
"""
|
||||
11
lib/__init__.py
Normal file
11
lib/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Module Initialisation
|
||||
Feature: Library
|
||||
|
||||
Description:
|
||||
Initialises library module.
|
||||
"""
|
||||
BIN
lib/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
lib/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/argument_validation.cpython-311.pyc
Normal file
BIN
lib/__pycache__/argument_validation.cpython-311.pyc
Normal file
Binary file not shown.
BIN
lib/__pycache__/data_types.cpython-311.pyc
Normal file
BIN
lib/__pycache__/data_types.cpython-311.pyc
Normal file
Binary file not shown.
1315
lib/argument_validation.py
Normal file
1315
lib/argument_validation.py
Normal file
File diff suppressed because it is too large
Load Diff
37
lib/data_types.py
Normal file
37
lib/data_types.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on Thu Apr 27 12:33:59 2023
|
||||
|
||||
@author: Edward Middleton-Smith
|
||||
|
||||
Argument Validation
|
||||
"""
|
||||
|
||||
# CLASSES
|
||||
# ATTRIBUTE DECLARATION
|
||||
# METHODS
|
||||
# FUNCTION
|
||||
# ARGUMENTS
|
||||
# ARGUMENT VALIDATION
|
||||
# ATTRIBUTE + VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
# RETURNS
|
||||
|
||||
# NORMAL METHODS
|
||||
# FUNCTION
|
||||
# ARGUMENTS
|
||||
# ARGUMENT VALIDATION
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
# RETURNS
|
||||
|
||||
# IMPORTS
|
||||
|
||||
# CLASSES
|
||||
|
||||
# METHODS
|
||||
def get_enum_member_by_text(enum_class, text):
|
||||
for member in enum_class.__members__.values():
|
||||
if member.name == text:
|
||||
return member
|
||||
raise ValueError(f'{text} is not in {enum_class}')
|
||||
11
models/__init__.py
Normal file
11
models/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Module Initialisation
|
||||
Feature: Models
|
||||
|
||||
Description:
|
||||
Initialises view data models module.
|
||||
"""
|
||||
BIN
models/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_base.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_contact.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_contact.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_home.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_home.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_store.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_store.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_store_basket.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_store_basket.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_store_checkout.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_store_checkout.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
models/__pycache__/model_view_store_home.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_store_home.cpython-311.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/model_view_store_product.cpython-311.pyc
Normal file
BIN
models/__pycache__/model_view_store_product.cpython-311.pyc
Normal file
Binary file not shown.
104
models/model_view_base.py
Normal file
104
models/model_view_base.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Base View Model
|
||||
|
||||
Description:
|
||||
Base data model for views
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
# from routes import bp_home
|
||||
import lib.argument_validation as av
|
||||
from forms import Form_Is_Included_VAT, Form_Delivery_Region, Form_Currency
|
||||
# external
|
||||
from abc import ABC, abstractmethod, abstractproperty
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask import Flask
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Base(ABC):
|
||||
# Attributes
|
||||
is_user_logged_in: bool
|
||||
id_user: str
|
||||
form_is_included_VAT: Form_Is_Included_VAT
|
||||
form_delivery_region: Form_Delivery_Region
|
||||
form_currency: Form_Currency
|
||||
# app: Flask
|
||||
is_page_store: bool
|
||||
# Global constants
|
||||
FLAG_BUTTON_MODAL_CLOSE = 'btn-overlay-close'
|
||||
FLAG_BUTTON_SUBMIT = 'btn-submit'
|
||||
FLAG_CARD = 'card'
|
||||
FLAG_COLLAPSIBLE = 'collapsible'
|
||||
FLAG_COLUMN = 'column'
|
||||
FLAG_CONTAINER = 'container'
|
||||
FLAG_CONTAINER_INPUT = FLAG_CONTAINER + '-input'
|
||||
FLAG_INITIALISED = 'initialised'
|
||||
FLAG_ROW = 'column'
|
||||
FLAG_SCROLLABLE = 'scrollable'
|
||||
FLAG_SUBMITTED = 'submitted'
|
||||
# flagIsDatePicker = 'is-date-picker'
|
||||
HASH_PAGE_CONTACT = '/contact'
|
||||
HASH_PAGE_ERROR_NO_PERMISSION = '/error'
|
||||
HASH_PAGE_HOME = '/'
|
||||
HASH_PAGE_STORE_HOME = '/store'
|
||||
HASH_PAGE_STORE_PRODUCT = '/store/product'
|
||||
ID_FORM_CURRENCY = 'formCurrency'
|
||||
ID_FORM_DELIVERY_REGION = 'formDeliveryRegion'
|
||||
ID_FORM_IS_INCLUDED_VAT = 'formIsIncludedVAT'
|
||||
ID_MODAL_SERVICES = 'modalServices'
|
||||
ID_MODAL_TECHNOLOGIES = 'modalTechnologies'
|
||||
ID_NAV_CONTACT = 'navContact'
|
||||
ID_NAV_HOME = 'navHome'
|
||||
ID_NAV_STORE_HOME = 'navStoreHome'
|
||||
ID_NAV_STORE_PRODUCT = 'navStoreProduct'
|
||||
ID_PAGE_BODY = 'pageBody'
|
||||
URL_HOST = 'http://127.0.0.1:5000'
|
||||
URL_GITHUB = 'https://github.com/Teddy-1024'
|
||||
URL_LINKEDIN = 'https://uk.linkedin.com/in/lordteddyms'
|
||||
|
||||
@abstractproperty
|
||||
def title(self):
|
||||
pass
|
||||
|
||||
def __new__(cls, db, info_user, app): # , *args, **kwargs
|
||||
# Initialiser - validation
|
||||
_m = 'Model_View_Base.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'{_m}\nstarting')
|
||||
# return super().__new__(cls, *args, **kwargs)
|
||||
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
|
||||
return super(Model_View_Base, cls).__new__(cls)
|
||||
|
||||
def __init__(self, db, info_user, app):
|
||||
# Constructor
|
||||
_m = 'Model_View_Base.__init__'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'{_m}\nstarting')
|
||||
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
|
||||
self.db = db
|
||||
self.info_user = info_user
|
||||
print(f'info_user: {info_user}\ntype: {str(type(info_user))}')
|
||||
self.is_user_logged_in = ('sub' in list(info_user.keys()) and not info_user['sub'] == '' and not str(type(info_user['sub'])) == "<class 'NoneType'?")
|
||||
print(f'is_user_logged_in: {self.is_user_logged_in}')
|
||||
self.id_user = info_user['sub'] if self.is_user_logged_in else ''
|
||||
self.form_is_included_VAT = Form_Is_Included_VAT()
|
||||
self.form_delivery_region = Form_Delivery_Region()
|
||||
self.form_currency = Form_Currency()
|
||||
self.app = app
|
||||
self.is_page_store = False
|
||||
|
||||
def output_bool(self, boolean):
|
||||
return str(boolean).lower()
|
||||
46
models/model_view_contact.py
Normal file
46
models/model_view_contact.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Contact View Model
|
||||
|
||||
Description:
|
||||
Data model for contact view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_base import Model_View_Base
|
||||
# from routes import bp_home
|
||||
from lib import argument_validation as av
|
||||
# external
|
||||
from flask_wtf import FlaskForm
|
||||
from abc import abstractproperty
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Contact(Model_View_Base):
|
||||
# Attributes
|
||||
@property
|
||||
def title(self):
|
||||
return 'Contact'
|
||||
|
||||
def __new__(cls, db, info_user, app, form):
|
||||
# Initialiser - validation
|
||||
_m = 'Model_View_Contact.__new__'
|
||||
av.val_instance(form, 'form', _m, FlaskForm)
|
||||
return super(Model_View_Contact, cls).__new__(cls, db, info_user, app)
|
||||
|
||||
def __init__(self, db, info_user, app, form):
|
||||
# Constructor
|
||||
super().__init__(db, info_user, app)
|
||||
self.form = form
|
||||
41
models/model_view_home.py
Normal file
41
models/model_view_home.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Home View Model
|
||||
|
||||
Description:
|
||||
Data model for home view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_base import Model_View_Base
|
||||
# from routes import bp_home
|
||||
# external
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Home(Model_View_Base):
|
||||
# Attributes
|
||||
@property
|
||||
def title(self):
|
||||
return 'Home'
|
||||
|
||||
def __new__(cls, db, info_user, app):
|
||||
# Initialiser - validation
|
||||
print(f'info_user: {info_user}')
|
||||
return super(Model_View_Home, cls).__new__(cls, db, info_user, app)
|
||||
|
||||
def __init__(self, db, info_user, app):
|
||||
# Constructor
|
||||
super().__init__(db, info_user, app)
|
||||
360
models/model_view_store.py
Normal file
360
models/model_view_store.py
Normal file
@@ -0,0 +1,360 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Parent View Model
|
||||
|
||||
Description:
|
||||
Parent data model for store views
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
# from context import models
|
||||
from models.model_view_base import Model_View_Base
|
||||
from business_objects.product import Product, Product_Filters # Product_Image_Filters,
|
||||
from business_objects.image import Resolution_Level_Enum
|
||||
import lib.argument_validation as av
|
||||
from datastores.datastore_store import DataStore_Store
|
||||
from forms import Form_Basket_Edit, Form_Is_Included_VAT, Form_Delivery_Region, Form_Currency
|
||||
from business_objects.basket import Basket_Item, Basket
|
||||
from business_objects.category import Category
|
||||
# external
|
||||
from flask import send_file, jsonify
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
import locale
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store(Model_View_Base):
|
||||
# Attributes
|
||||
# id_user: str
|
||||
db: SQLAlchemy
|
||||
basket: Basket # list # dict
|
||||
# basket_total: float
|
||||
id_currency: bool
|
||||
id_region_delivery: bool
|
||||
is_included_VAT: bool
|
||||
show_delivery_option: bool # for checkout page
|
||||
# Global constants
|
||||
ATTR_FORM_TYPE = 'form-type'
|
||||
ATTR_ID_PRODUCT_CATEGORY = 'id-product-category'
|
||||
ATTR_ID_PRODUCT = 'id-product'
|
||||
ATTR_ID_PERMUTATION = 'id-permutation'
|
||||
FLAG_BASKET_ITEM_DELETE = 'basket-item-delete'
|
||||
FLAG_BUTTON_BASKET_ADD = Model_View_Base.FLAG_BUTTON_SUBMIT + '.btnAdd2Basket'
|
||||
FLAG_BUTTON_BUY_NOW = 'btnBuyNow'
|
||||
HASH_PAGE_STORE_BASKET = '/store/basket'
|
||||
HASH_STORE_BASKET_ADD = '/store/basket_add'
|
||||
HASH_STORE_BASKET_DELETE = '/store/basket_delete'
|
||||
HASH_STORE_BASKET_EDIT = '/store/basket_edit'
|
||||
HASH_STORE_BASKET_LOAD = '/store/basket_load'
|
||||
HASH_STORE_SET_CURRENCY = '/store/set_currency'
|
||||
HASH_STORE_SET_DELIVERY_REGION = '/store/set_delivery_region'
|
||||
HASH_STORE_SET_IS_INCLUDED_VAT = '/store/set_is_included_VAT'
|
||||
ID_BASKET = 'basket'
|
||||
ID_BASKET_CONTAINER = 'basketContainer'
|
||||
ID_BASKET_TOTAL = 'basketTotal'
|
||||
ID_BUTTON_CHECKOUT = 'btnCheckout'
|
||||
ID_BUTTON_BASKET_ADD = 'btnBasketAdd'
|
||||
ID_BUTTON_BUY_NOW = 'btnBuyNow'
|
||||
ID_CURRENCY_DEFAULT = 1
|
||||
ID_LABEL_BASKET_EMPTY = 'basketEmpty'
|
||||
ID_REGION_DELIVERY_DEFAULT = 1
|
||||
IS_INCLUDED_VAT_DEFAULT = True
|
||||
KEY_BASKET = 'basket'
|
||||
# KEY_CODE_CURRENCY = 'code_currency'
|
||||
KEY_FORM = 'form'
|
||||
KEY_ID_CURRENCY = 'id_currency'
|
||||
KEY_ID_PRODUCT = 'product_id'
|
||||
KEY_ID_PERMUTATION = 'permutation_id'
|
||||
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
|
||||
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
|
||||
KEY_PRICE = 'price'
|
||||
KEY_QUANTITY = 'quantity'
|
||||
TYPE_FORM_BASKET_ADD = 'Form_Basket_Add'
|
||||
TYPE_FORM_BASKET_EDIT = 'Form_Basket_Edit'
|
||||
# development variables
|
||||
# valid_product_id_list = ['prod_PB0NUOSEs06ymG']
|
||||
|
||||
def __new__(cls, db, info_user, app, id_currency, id_region_delivery): # , *args, **kwargs``
|
||||
# Initialiser - validation
|
||||
_m = 'Model_View_Store.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'{_m}\nstarting')
|
||||
# av.val_str(id_user, 'id_user', _m)
|
||||
# return super().__new__(cls, *args, **kwargs)
|
||||
return super().__new__(cls, db, info_user, app) # Model_View_Store, cls
|
||||
|
||||
def __init__(self, db, info_user, app, id_currency, id_region_delivery):
|
||||
# Constructor
|
||||
_m = 'Model_View_Store.__init__'
|
||||
print(f'{_m}\nstarting')
|
||||
super().__init__(db, info_user, app)
|
||||
self.is_page_store = True
|
||||
self.basket = Basket()
|
||||
# self.basket_total = 0
|
||||
# self.db = db
|
||||
# if logged in:
|
||||
# else:
|
||||
self.id_currency = id_currency
|
||||
self.id_region_delivery = id_region_delivery
|
||||
self.show_delivery_option = True
|
||||
self.form_is_included_VAT = Form_Is_Included_VAT()
|
||||
regions, currencies = self.get_regions_and_currencies()
|
||||
self.form_currency = Form_Currency()
|
||||
self.form_currency.id_currency.choices = [(currency.id_currency, f'{currency.code} - {currency.name}') for currency in currencies]
|
||||
self.form_currency.id_currency.data = str(self.id_currency) if len(currencies) > 0 else None
|
||||
self.form_delivery_region = Form_Delivery_Region()
|
||||
self.form_delivery_region.id_region_delivery.choices = [(region.id_region, f'{region.code} - {region.name}') for region in regions]
|
||||
self.form_delivery_region.id_region_delivery.data = str(self.id_region_delivery) if len(regions) > 0 else None
|
||||
|
||||
def get_many_product_category(self, product_filters): # category_ids = '', product_ids = '', get_all_category = True, get_all_product = True, max_products_per_category = -1):
|
||||
_m = 'Model_View_Store.get_many_product_category'
|
||||
av.val_instance(product_filters, 'product_filters', _m, Product_Filters)
|
||||
"""
|
||||
av.val_str(category_ids, 'category_ids', _m)
|
||||
av.val_str(product_ids, 'product_ids', _m)
|
||||
av.val_bool(get_all_category, 'get_all_category', _m)
|
||||
av.val_bool(get_all_product, 'get_all_product', _m)
|
||||
av.val_int(max_products_per_category, 'max_products_per_category', _m)
|
||||
"""
|
||||
# get products from database
|
||||
# call datastore method
|
||||
# return [Product.template()]
|
||||
self.category_list, errors = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(product_filters) # category_ids, product_ids, get_all_category, get_all_product, max_products_per_category)
|
||||
# self.categories = categories
|
||||
# self.category_index = category_index
|
||||
|
||||
return
|
||||
if get_all_category or get_all_product:
|
||||
prod = Product.template()
|
||||
prod_list = [ prod, prod ]
|
||||
return { 'MISCELLANEOUS': prod_list if max_products_per_category < 0 else prod_list[:max_products_per_category] }
|
||||
if product_ids == 'panties123':
|
||||
prod = Product.template()
|
||||
return { 'MISCELLANEOUS': [ prod] }
|
||||
|
||||
# def product_category_getMany(self, category_ids = '', product_ids = '', get_all_category = True, get_all_product = True):
|
||||
# return Model_View_Store.product_category_getMany(category_ids, product_ids, get_all_category, get_all_product)
|
||||
|
||||
def get_many_product_image_src(self, product_id, image_ids = '', get_primary_only = True, resolution_level = ''):
|
||||
_m = 'Model_View_Store.get_many_product_image'
|
||||
# print(f'{_m}\n')
|
||||
# av.val_instance(filters, 'filters', _m, Product_Image_Filters)
|
||||
av.val_int(product_id, 'product_id', _m)
|
||||
# av.full_val_int(product_id, 'product_id', _m)
|
||||
# product_id = int(product_id)
|
||||
av.val_str(image_ids, 'image_ids', _m)
|
||||
av.full_val_bool(get_primary_only, 'get_primary_only', _m)
|
||||
get_primary_only = bool(get_primary_only)
|
||||
resolution_level = Resolution_Level_Enum.get_member_by_text(resolution_level)
|
||||
av.val_instance(resolution_level, 'resolution_level', _m, Resolution_Level_Enum)
|
||||
# if (filters.product_id < 0 or filters.product_id not in self.valid_product_id_list):
|
||||
if (product_id not in Model_View_Store.valid_product_id_list): # product_id < 0 or
|
||||
return ''
|
||||
path_suffix = 'jpg' # get_suffix_from_product_id(product_id)
|
||||
path_file = f'/static/images/{product_id}.{path_suffix}'
|
||||
return path_file
|
||||
# return send_file(path_file, mimetype=f'image/{path_suffix}')
|
||||
|
||||
"""
|
||||
def get_product_category_text(self, category):
|
||||
return Enum_Product_Category.get_member_by_text(category).text()
|
||||
|
||||
def add_2_basket(product_id, quantity, basket_local):
|
||||
_m = 'Model_View_Store.add_2_basket'
|
||||
av.full_val_int(product_id, 'product_id', _m)
|
||||
product_id = str(product_id)
|
||||
av.full_val_int(quantity, 'quantity', _m)
|
||||
quantity = int(quantity)
|
||||
av.val_instance(basket_local, 'basket_local', _m, dict)
|
||||
# send to database
|
||||
|
||||
# update basket on webpage with new database status
|
||||
if product_id in basket_local:
|
||||
basket_local[product_id] += quantity
|
||||
else:
|
||||
basket_local[product_id] = quantity
|
||||
return basket_local // jsonify(basket_local)
|
||||
"""
|
||||
|
||||
|
||||
def basket_item_edit(self, permutation_id, quantity, quantity_sum_not_replace):
|
||||
_m = 'Model_View_Store.basket_item_edit'
|
||||
# av.full_val_int(product_id, 'product_id', _m)
|
||||
# product_id = int(product_id)
|
||||
# av.val_instance(db, 'db', _m, SQLAlchemy)
|
||||
print(f'{_m} starting')
|
||||
# print(f'product_id: {product_id}\npermutation_id: {permutation_id}\nquantity = {quantity}')
|
||||
# av.full_val_int(product_id, 'product_id', _m)
|
||||
# print('valid product id')
|
||||
av.full_val_int(quantity, 'quantity', _m)
|
||||
quantity = int(quantity)
|
||||
# item_added = False
|
||||
print(f'basket: {self.basket}')
|
||||
ids_permutation, quantities_permutation = self.basket.to_csv()
|
||||
self.basket = DataStore_Store(self.db, self.info_user, self.app).edit_basket(ids_permutation, quantities_permutation, permutation_id, quantity, quantity_sum_not_replace, self.app.id_currency, self.app.id_region_delivery)
|
||||
return True
|
||||
|
||||
def get_basket(self, json_data):
|
||||
self.import_JSON_basket(json_data)
|
||||
if self.is_user_logged_in:
|
||||
ids_permutation, quantities_permutation = self.basket.to_csv()
|
||||
self.basket = DataStore_Store(self.db, self.info_user, self.app).edit_basket(ids_permutation, quantities_permutation, None, None, None, self.app.id_currency, self.app.id_region_delivery)
|
||||
# return self.basket
|
||||
|
||||
def _get_json_basket_id_CSVs_product_permutation(self, basket):
|
||||
product_ids = ''
|
||||
permutation_ids = ''
|
||||
item_index_dict = {}
|
||||
if len(basket) > 0:
|
||||
for index_item in range(len(basket)):
|
||||
if index_item > 0:
|
||||
product_ids += ','
|
||||
permutation_ids += ','
|
||||
basket_item = basket[index_item]
|
||||
id_product = basket_item[self.key_id_product]
|
||||
id_permutation = basket_item[self.key_id_permutation]
|
||||
id_permutation = '' if (id_permutation is None or id_permutation == 'None') else str(id_permutation)
|
||||
product_ids += str(id_product) # str(basket[b].product.id)
|
||||
permutation_ids += id_permutation # str(basket[b].product.id)
|
||||
# item_index_dict[Basket.get_key_product_index_from_ids_product_permutation(id_product, id_permutation)] = index_item
|
||||
item_index_dict[id_permutation] = index_item
|
||||
print(f'product_ids = {product_ids}')
|
||||
print(f'permutation_ids = {permutation_ids}')
|
||||
return product_ids, permutation_ids, item_index_dict
|
||||
|
||||
def _get_basket_from_json(self, json_data):
|
||||
basket = json_data[self.key_basket]['items']
|
||||
av.val_instance(basket, 'basket', 'Model_View_Store._get_basket_from_json', list)
|
||||
print(f'basket = {basket}')
|
||||
return basket
|
||||
|
||||
def import_JSON_basket(self, json_data):
|
||||
_m = 'Model_View_Store.import_JSON_basket'
|
||||
# av.val_instance(db, 'db', _m, SQLAlchemy)
|
||||
items = self._get_basket_from_json(json_data)
|
||||
print(f'json basket items: {items}')
|
||||
product_ids, permutation_ids, item_index_dict = self._get_json_basket_id_CSVs_product_permutation(items)
|
||||
category_list, errors = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(Product_Filters(
|
||||
self.id_user, # :a_id_user
|
||||
True, '', False, # :a_get_all_category, :a_ids_category, :a_get_inactive_category
|
||||
False, product_ids, False, False, # :a_get_all_product, :a_ids_product, :a_get_inactive_product, :a_get_first_product_only
|
||||
False, permutation_ids, False, # :a_get_all_permutation, :a_ids_permutation, :a_get_inactive_permutation
|
||||
False, '', False, True, # :a_get_all_image, :a_ids_image, :a_get_inactive_image, :a_get_first_image_only
|
||||
False, str(self.app.id_region_delivery), False, # :a_get_all_delivery_region, :a_ids_delivery_region, :a_get_inactive_delivery_region
|
||||
False, str(self.app.id_currency), False, # :a_get_all_currency, :a_ids_currency, :a_get_inactive_currency
|
||||
True, '', False # :a_get_all_discount, :a_ids_discount, :a_get_inactive_discount
|
||||
)) # product_ids=product_ids, get_all_category=False, get_all_product=False)
|
||||
# print(f'categories = {categories}')
|
||||
self.basket = Basket()
|
||||
if len(category_list.categories) > 0: # not (categories is None):
|
||||
for category in category_list.categories:
|
||||
for product in category.products:
|
||||
# product = Product.make_from_json(items[index_item])
|
||||
product.form_basket_edit = Form_Basket_Edit()
|
||||
# key_index_product = Basket.get_key_product_index_from_ids_product_permutation(product.id_product, product.get_id_permutation())
|
||||
permutation = product.get_permutation_selected()
|
||||
self.basket.add_item(Basket_Item.make_from_product_and_quantity_and_VAT_included(product, items[item_index_dict[str(permutation.id_permutation)]][self.key_quantity], self.app.is_included_VAT))
|
||||
"""
|
||||
if len(items) > 0:
|
||||
for index_item in range(len(items)):
|
||||
"""
|
||||
print(f'basket data: {json_data}')
|
||||
print(f'basket: {self.basket}')
|
||||
|
||||
# ids_permutation_unavailable_region_or_currency = []
|
||||
# id_permutation_unavailable_otherwise = []
|
||||
if len(errors) > 0:
|
||||
for error in errors:
|
||||
if error[1] == 'PRODUCT_AVAILABILITY':
|
||||
ids_permutation = DataStore_Store.get_ids_permutation_from_error_availability(error[2])
|
||||
for id_permutation in ids_permutation:
|
||||
for item in self.basket.items:
|
||||
permutation = item.product.get_permutation_selected()
|
||||
if id_permutation == permutation.id_permutation:
|
||||
item.is_available = False
|
||||
if 'region' in error[2] or 'currency' in error[2]:
|
||||
item.is_unavailable_in_currency_or_region = True
|
||||
# ids_permutation_unavailable_region_or_currency.append(id_permutation)
|
||||
# else:
|
||||
# for id_permutation in ids_permutation:
|
||||
# id_permutation_unavailable_otherwise.append(id_permutation)
|
||||
"""
|
||||
ids_permutation_unavailable = self.basket.get_ids_permutation_unavailable()
|
||||
if len(ids_permutation_unavailable) > 0:
|
||||
category_list_unavailable, errors_unavailable = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(Product_Filters(
|
||||
self.id_user, # :a_id_user
|
||||
True, '', False, # :a_get_all_category, :a_ids_category, :a_get_inactive_category
|
||||
False, '', False, False, # :a_get_all_product, :a_ids_product, :a_get_inactive_product, :a_get_first_product_only
|
||||
False, ','.join(ids_permutation_unavailable), False, # :a_get_all_permutation, :a_ids_permutation, :a_get_inactive_permutation
|
||||
False, '', False, True, # :a_get_all_image, :a_ids_image, :a_get_inactive_image, :a_get_first_image_only
|
||||
True, '', False, # :a_get_all_delivery_region, :a_ids_delivery_region, :a_get_inactive_delivery_region
|
||||
True, '', False, # :a_get_all_currency, :a_ids_currency, :a_get_inactive_currency
|
||||
True, '', False # :a_get_all_discount, :a_ids_discount, :a_get_inactive_discount
|
||||
)) # product_ids=product_ids, get_all_category=False, get_all_product=False)
|
||||
else:
|
||||
category_list_unavailable = None
|
||||
errors_unavailable = []
|
||||
"""
|
||||
|
||||
|
||||
def import_JSON_basket_item(self, json_data, form_basket = None):
|
||||
_m = 'Model_View_Store.import_JSON_basket_item'
|
||||
print(f'starting {_m}')
|
||||
# print('getting product id')
|
||||
# product_id = av.input_int(json_data[self.key_id_product], self.key_id_product, _m)
|
||||
|
||||
# print(f'product id: {product_id}, type: {str(type(product_id))}')
|
||||
try:
|
||||
permutation_id = json_data[self.key_id_permutation]
|
||||
av.full_val_int(permutation_id, self.key_id_permutation, _m)
|
||||
permutation_id = int(permutation_id)
|
||||
except:
|
||||
permutation_id = None
|
||||
if not permutation_id == 'None':
|
||||
print(f'permutation_id invalid: {permutation_id}')
|
||||
raise ValueError("Invalid permutation id")
|
||||
print(f'permutation_id: {permutation_id}')
|
||||
|
||||
try:
|
||||
print(f'form_basket: {form_basket}')
|
||||
print('getting quantity')
|
||||
print(f'form_basket.quantity: {form_basket.quantity}')
|
||||
print(f'form_basket.quantity.data: {form_basket.quantity.data}')
|
||||
quantity = int(form_basket.quantity.data)
|
||||
except:
|
||||
quantity = 0
|
||||
print(f'quantity: {quantity}')
|
||||
|
||||
print(f'permutation_id: {permutation_id}\nquantity: {quantity}')
|
||||
|
||||
return permutation_id, quantity
|
||||
|
||||
def output_basket_total(self):
|
||||
return self.basket.output_total()
|
||||
|
||||
|
||||
def init_forms_basket_add(self):
|
||||
for cat in self.categories:
|
||||
c = self.category_index
|
||||
|
||||
def get_many_user_order(self, ids_order, n_order_max, id_checkout_session):
|
||||
# _m = 'Model_View_Store.get_many_user_order'
|
||||
# av.val_str(id_user)
|
||||
# validation conducted by server
|
||||
return DataStore_Store(self.db, self.info_user, self.app).get_many_user_order(self.info_user['sub'], ids_order, n_order_max, id_checkout_session)
|
||||
|
||||
def get_regions_and_currencies(self):
|
||||
regions, currencies = DataStore_Store(self.db, self.info_user, self.app).get_regions_and_currencies()
|
||||
return regions, currencies
|
||||
93
models/model_view_store_basket.py
Normal file
93
models/model_view_store_basket.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Basket View Model
|
||||
|
||||
Description:
|
||||
Data model for store basket view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_store import Model_View_Store
|
||||
# from routes import bp_home
|
||||
from business_objects.product import Product
|
||||
from forms import Form_Billing # Form_Product
|
||||
# external
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store_Basket(Model_View_Store):
|
||||
# Attributes
|
||||
# product_categories: list # (str)
|
||||
form_delivery: Form_Billing
|
||||
form_billing: Form_Billing
|
||||
forms_delivery_method: list # []
|
||||
is_collapsed_info_billing: bool
|
||||
is_collapsed_info_delivery: bool
|
||||
# Global constants
|
||||
# category_products: dict { category_enum_id: List[Product] }
|
||||
hash_page_store_checkout = '/store/checkout'
|
||||
hash_page_store_checkout_session = '/store/checkout_session'
|
||||
hash_store_basket_info = '/store/basket_info'
|
||||
id_container_info_billing = 'containerInfoBilling'
|
||||
id_container_info_delivery = 'containerInfoDelivery'
|
||||
id_overlay_info_delivery = 'overlayInfoDelivery'
|
||||
id_overlay_info_billing = 'overlayInfoBilling'
|
||||
key_address1 = 'address_1'
|
||||
key_address2 = 'address_2'
|
||||
key_city = 'city'
|
||||
key_county = 'county'
|
||||
key_id_checkout = 'checkout-session-id'
|
||||
key_info_billing = 'billing-info'
|
||||
key_info_delivery = 'delivery-info'
|
||||
key_info_identical = 'identical'
|
||||
key_info_type = 'type-info'
|
||||
key_is_subscription = 'is-subscription'
|
||||
key_name_full = 'name_full'
|
||||
key_phone_number = 'phone_number'
|
||||
key_postcode = 'postcode'
|
||||
key_region = 'region'
|
||||
key_url_checkout = 'checkout-url'
|
||||
|
||||
# Attributes
|
||||
@property
|
||||
def title(self):
|
||||
return 'Store Basket'
|
||||
|
||||
def __new__(cls, db, id_user, app, id_currency, id_region_delivery):
|
||||
# Initialiser - validation
|
||||
return super(Model_View_Store_Basket, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
|
||||
|
||||
def __init__(self, db, id_user, app, id_currency, id_region_delivery):
|
||||
# Constructor
|
||||
super().__init__(db, id_user, app, id_currency, id_region_delivery)
|
||||
# self.product_categories = Model_View_Store_Basket.get_many_product_category(get_all_category = True, get_all_product = True)
|
||||
self.form_billing = Form_Billing()
|
||||
self.form_billing.form_type_billing_not_delivery = True
|
||||
self.form_delivery = Form_Billing()
|
||||
# if logged in:
|
||||
# else:
|
||||
self.is_collapsed_info_billing = False
|
||||
self.is_collapsed_info_delivery = False
|
||||
"""
|
||||
self.forms_product = {}
|
||||
for cat in self.product_categories:
|
||||
for product in cat:
|
||||
if len(list(product.variations.keys())) == 0:
|
||||
new_form = Form_Product()
|
||||
if new_form.validate_on_submit():
|
||||
# Handle form submission
|
||||
self.add_2_basket(product.id, )
|
||||
self.forms[str(product.id)] = new_form
|
||||
"""
|
||||
75
models/model_view_store_checkout.py
Normal file
75
models/model_view_store_checkout.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Checkout View Model
|
||||
|
||||
Description:
|
||||
Data model for store checkout view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_store import Model_View_Store
|
||||
from models.model_view_store_basket import Model_View_Store_Basket
|
||||
# from routes import bp_home
|
||||
from business_objects.product import Product
|
||||
from forms import Form_Billing # Form_Product
|
||||
import lib.argument_validation as av
|
||||
from datastores.datastore_store import DataStore_Store
|
||||
# external
|
||||
import os
|
||||
import stripe
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store_Checkout(Model_View_Store_Basket):
|
||||
# Attributes
|
||||
key_secret_stripe: str
|
||||
key_public_stripe: str
|
||||
# Global constants
|
||||
key_id_price = 'price_id'
|
||||
@property
|
||||
def title(self):
|
||||
return 'Store Checkout'
|
||||
|
||||
def __new__(cls, db, id_user, app):
|
||||
# Initialiser - validation
|
||||
return super(Model_View_Store_Checkout, cls).__new__(cls, db, id_user, app)
|
||||
|
||||
def __init__(self, db, id_user, app):
|
||||
# Constructor
|
||||
super().__init__(db, id_user, app)
|
||||
self.key_secret_stripe = os.environ.get("KEY_SECRET_STRIPE")
|
||||
self.key_public_stripe = os.environ.get("KEY_PUBLIC_STRIPE")
|
||||
|
||||
# For sample support and debugging, not required for production:
|
||||
stripe.set_app_info(
|
||||
'stripe-samples/checkout-one-time-payments',
|
||||
version='0.0.1',
|
||||
url='https://github.com/stripe-samples/checkout-one-time-payments')
|
||||
|
||||
stripe.api_key = self.key_secret_stripe
|
||||
|
||||
|
||||
def create_product(self, product): # _name, product_description):
|
||||
return DataStore_Store(self.db, self.info_user).create_product(product) # _name, product_description)
|
||||
|
||||
def create_price(self, product, currency):
|
||||
return DataStore_Store(self.db, self.info_user).create_price(product, currency)
|
||||
|
||||
def get_many_product_new(self):
|
||||
return DataStore_Store(self.db, self.info_user).get_many_product_new()
|
||||
|
||||
"""
|
||||
def get_price_id(product_ids):
|
||||
return DataStore_Store().get_many_id_price(product_ids)
|
||||
"""
|
||||
55
models/model_view_store_checkout_success.py
Normal file
55
models/model_view_store_checkout_success.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Checkout Success View Model
|
||||
|
||||
Description:
|
||||
Data model for store checkout success view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_store import Model_View_Store
|
||||
from models.model_view_store_checkout import Model_View_Store_Checkout
|
||||
# from routes import bp_home
|
||||
from business_objects.product import Product
|
||||
from forms import Form_Billing # Form_Product
|
||||
import lib.argument_validation as av
|
||||
from datastores.datastore_store import DataStore_Store
|
||||
# external
|
||||
import os
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store_Checkout_Success(Model_View_Store_Checkout):
|
||||
# Attributes
|
||||
key_secret_stripe: str
|
||||
key_public_stripe: str
|
||||
# Global constants
|
||||
key_id_price = 'price_id'
|
||||
@property
|
||||
def title(self):
|
||||
return 'Store Checkout Success'
|
||||
|
||||
def __new__(cls, db, id_user, app, id_checkout_session, checkout_items = None):
|
||||
# Initialiser - validation
|
||||
_m = 'Model_View_Store_Checkout_Success.__new__'
|
||||
# av.val_list(checkout_items, 'checkout_items', _m)
|
||||
av.val_str(id_checkout_session, 'id_checkout_session', _m)
|
||||
return super(Model_View_Store_Checkout_Success, cls).__new__(cls, db, id_user, app)
|
||||
|
||||
def __init__(self, db, id_user, app, id_checkout_session, checkout_items = None):
|
||||
# Constructor
|
||||
super().__init__(db, id_user, app)
|
||||
self.checkout_items = checkout_items
|
||||
self.id_checkout_session = id_checkout_session
|
||||
self.order = self.get_many_user_order('', 1, id_checkout_session)
|
||||
63
models/model_view_store_home.py
Normal file
63
models/model_view_store_home.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Home View Model
|
||||
|
||||
Description:
|
||||
Data model for store home view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_store import Model_View_Store
|
||||
# from routes import bp_home
|
||||
from business_objects.product import Product
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store_Home(Model_View_Store):
|
||||
# Attributes
|
||||
product_categories: list # (str)
|
||||
forms_product: dict
|
||||
forms_basket: dict
|
||||
# Global constants
|
||||
# category_products: dict { category_enum_id: List[Product] }
|
||||
# Attributes
|
||||
@property
|
||||
def title(self):
|
||||
return 'Store Home'
|
||||
max_products_per_category = -1
|
||||
|
||||
def __new__(cls, db, id_user, app, id_currency, id_region_delivery):
|
||||
# Initialiser - validation
|
||||
return super(Model_View_Store_Home, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
|
||||
|
||||
def __init__(self, db, id_user, app, id_currency, id_region_delivery):
|
||||
# Constructor
|
||||
super().__init__(db, id_user, app, id_currency, id_region_delivery)
|
||||
# self.categories = Model_View_Store_Home.get_many_product_category(self.db, get_all_category = True, get_all_product = True)
|
||||
# self.get_many_product_category(get_all_category = True, get_all_product = True)
|
||||
"""
|
||||
self.forms_product = {}
|
||||
for cat in self.product_categories:
|
||||
for product in cat:
|
||||
if len(list(product.variations.keys())) == 0:
|
||||
new_form = Form_Product()
|
||||
if new_form.validate_on_submit():
|
||||
# Handle form submission
|
||||
self.add_2_basket(product.id, )
|
||||
self.forms[str(product.id)] = new_form
|
||||
"""
|
||||
|
||||
82
models/model_view_store_product.py
Normal file
82
models/model_view_store_product.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: View Models
|
||||
Feature: Store Product View Model
|
||||
|
||||
Description:
|
||||
Data model for store product view
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
from models.model_view_store import Model_View_Store
|
||||
from datastores.datastore_store import DataStore_Store
|
||||
# from routes import bp_home
|
||||
from business_objects.product import Product, Product_Filters
|
||||
import lib.argument_validation as av
|
||||
# external
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Model_View_Store_Product(Model_View_Store):
|
||||
# categories: list # (str)
|
||||
# category_products: dict { category_enum_id: List[Product] }
|
||||
|
||||
# Attributes
|
||||
@property
|
||||
def title(self):
|
||||
return 'Store Home'
|
||||
|
||||
def __new__(cls, db, id_user, app, id_permutation, id_currency, id_region_delivery): # *args, **kwargs
|
||||
# Initialiser - validation
|
||||
_m = 'Model_View_Store_Product.__new__'
|
||||
print(f'{_m}\nstarting...')
|
||||
v_arg_type = 'class attribute'
|
||||
|
||||
# av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
|
||||
# av.val_int(id_product, 'id_product', _m, v_arg_type=v_arg_type)
|
||||
# av.val_int(id_permutation, 'id_permutation', _m, v_arg_type=v_arg_type)
|
||||
|
||||
print(f'user id: {id_user.get("sub")}')
|
||||
print(f'ending')
|
||||
|
||||
# return super().__new__(cls, *args, **kwargs) # Model_View_Store_Product, cls # , db, id_user, id_product) # , db, id_user)
|
||||
return super(Model_View_Store_Product, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
|
||||
|
||||
def __init__(self, db, id_user, app, id_permutation, id_currency, id_region_delivery):
|
||||
# Constructor
|
||||
_m = 'Model_View_Store_Product.__init__'
|
||||
print(f'{_m}\nstarting...')
|
||||
super().__init__(db, id_user, app, id_currency, id_region_delivery)
|
||||
print('supered')
|
||||
print(f'user info: {self.info_user}')
|
||||
# print(f'user id: {self.info_user.get("sub")}')
|
||||
|
||||
category_list = DataStore_Store(self.db, self.info_user).get_many_product_category(Product_Filters(
|
||||
self.info_user['sub'],
|
||||
True, '', False,
|
||||
True, '', False, False,
|
||||
False, str(id_permutation), False,
|
||||
True, '', False, False,
|
||||
False, str(id_region_delivery), False,
|
||||
False, str(id_currency), False,
|
||||
True, '', False
|
||||
)) # product_ids=str(id_product), permutation_ids=str(id_permutation))
|
||||
print('connection to db successful')
|
||||
# self.categories = categories
|
||||
# self.category_index = category_index
|
||||
if (category_list.get_count_categories() > 0):
|
||||
self.product = category_list.get_permutation_first()
|
||||
else:
|
||||
self.product = None
|
||||
print('selected permutation selected')
|
||||
182
pay_stripe.py
Normal file
182
pay_stripe.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: App General
|
||||
Feature: App
|
||||
|
||||
Description:
|
||||
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
import os
|
||||
import stripe
|
||||
import json
|
||||
from flask import Flask, render_template, render_template_string, jsonify, request, send_from_directory, redirect
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
from config import app_config
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
key_secret = os.environ.get("KEY_SECRET_STRIPE")
|
||||
key_public = os.environ.get("KEY_PUBLIC_STRIPE") # 'pk_test_51OGrxlL7BuLKjoMpfpfw7bSmZZK1MhqMoQ5VhW2jUj7YtoEejO4vqnxKPiqTHHuh9U4qqkywbPCSI9TpFKtr4SYH007KHMWs68'
|
||||
|
||||
# METHODS
|
||||
def create_product_price():
|
||||
print(f'stripe.api_key = {stripe.api_key}')
|
||||
starter_subscription = stripe.Product.create(
|
||||
name="Starter Subscription",
|
||||
description="$12/Month subscription",
|
||||
)
|
||||
|
||||
starter_subscription_price = stripe.Price.create(
|
||||
unit_amount=1200,
|
||||
currency="usd",
|
||||
recurring={"interval": "month"},
|
||||
product=starter_subscription['id'],
|
||||
)
|
||||
|
||||
# Save these identifiers
|
||||
print(f"Success! Here is your starter subscription product id: {starter_subscription.id}")
|
||||
print(f"Success! Here is your starter subscription price id: {starter_subscription_price.id}")
|
||||
|
||||
return starter_subscription_price.id
|
||||
|
||||
def get_file_str(f_address):
|
||||
f = open(f_address)
|
||||
return f.read()
|
||||
|
||||
# Ensure environment variables are set.
|
||||
price = os.getenv('PRICE')
|
||||
if price is None or price == 'price_12345' or price == '':
|
||||
print('You must set a Price ID in .env. Please see the README.')
|
||||
exit(0)
|
||||
|
||||
# For sample support and debugging, not required for production:
|
||||
stripe.set_app_info(
|
||||
'stripe-samples/checkout-one-time-payments',
|
||||
version='0.0.1',
|
||||
url='https://github.com/stripe-samples/checkout-one-time-payments')
|
||||
|
||||
# stripe.api_version = '2020-08-27'
|
||||
stripe.api_key = key_secret # os.getenv('KEY_SECRET_STRIPE')
|
||||
|
||||
# app_dir = str(os.path.abspath(os.path.join(
|
||||
# __file__, "..", "..")))
|
||||
# static_dir = str(os.path.abspath(os.path.join(
|
||||
# app_dir, os.getenv("STATIC_DIR"))))
|
||||
# template_dir = str(os.path.abspath(os.path.join(
|
||||
# app_dir, os.getenv("TEMPLATE_DIR"))))
|
||||
app = Flask(__name__) # , static_folder=static_dir,
|
||||
# static_url_path="", template_folder=template_dir)
|
||||
app.config.from_object(app_config)
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
# return render_template(f'{app_dir}\\templates\\_home.html') # f'{app_dir}\\templates\\layout.html')
|
||||
# return render_template_string(get_file_str(f'{app_dir}\\templates\\_home.html')) # f'{app_dir}\\templates\\layout.html')
|
||||
return render_template('_home.html', title='Home')
|
||||
|
||||
@app.route('/store', methods=['GET'])
|
||||
def store_home():
|
||||
return render_template('_store_home.html', title='Store Home')
|
||||
|
||||
@app.route('/contact')
|
||||
def contact():
|
||||
return render_template('_contact.html', title='Contact Us')
|
||||
|
||||
|
||||
@app.route('/config', methods=['GET'])
|
||||
def get_publishable_key():
|
||||
price = stripe.Price.retrieve(os.getenv('PRICE'))
|
||||
return jsonify({
|
||||
'publicKey': key_public, # os.getenv('KEY_PUBLIC_STRIPE'),
|
||||
'unitAmount': price['unit_amount'],
|
||||
'currency': price['currency']
|
||||
})
|
||||
|
||||
# Fetch the Checkout Session to display the JSON result on the success page
|
||||
@app.route('/checkout-session', methods=['GET'])
|
||||
def get_checkout_session():
|
||||
id = request.args.get('sessionId')
|
||||
print(f'checkout session id: {id}')
|
||||
checkout_session = stripe.checkout.Session.retrieve(id)
|
||||
return jsonify(checkout_session)
|
||||
|
||||
|
||||
@app.route('/create-checkout-session', methods=['POST'])
|
||||
def create_checkout_session():
|
||||
quantity = request.form.get('quantity', 1)
|
||||
domain_url = os.getenv('DOMAIN')
|
||||
|
||||
try:
|
||||
# Create new Checkout Session for the order
|
||||
# Other optional params include:
|
||||
# [billing_address_collection] - to display billing address details on the page
|
||||
# [customer] - if you have an existing Stripe Customer ID
|
||||
# [payment_intent_data] - lets capture the payment later
|
||||
# [customer_email] - lets you prefill the email input in the form
|
||||
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
|
||||
# For full details see https://stripe.com/docs/api/checkout/sessions/create
|
||||
|
||||
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
|
||||
checkout_session = stripe.checkout.Session.create(
|
||||
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
|
||||
cancel_url=domain_url + '/canceled.html',
|
||||
mode='subscription', # 'payment',
|
||||
# automatic_tax={'enabled': True},
|
||||
line_items=[{
|
||||
'price': os.getenv('PRICE'),
|
||||
'quantity': quantity,
|
||||
}]
|
||||
)
|
||||
return redirect(checkout_session.url, code=303)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
|
||||
@app.route('/webhook', methods=['POST'])
|
||||
def webhook_received():
|
||||
# You can use webhooks to receive information about asynchronous payment events.
|
||||
# For more about our webhook events check out https://stripe.com/docs/webhooks.
|
||||
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
|
||||
request_data = json.loads(request.data)
|
||||
|
||||
if webhook_secret:
|
||||
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
|
||||
signature = request.headers.get('stripe-signature')
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload=request.data, sig_header=signature, secret=webhook_secret)
|
||||
data = event['data']
|
||||
except Exception as e:
|
||||
return e
|
||||
# Get the type of webhook event sent - used to check the status of PaymentIntents.
|
||||
event_type = event['type']
|
||||
else:
|
||||
data = request_data['data']
|
||||
event_type = request_data['type']
|
||||
data_object = data['object']
|
||||
|
||||
print('event ' + event_type)
|
||||
|
||||
if event_type == 'checkout.session.completed':
|
||||
print('🔔 Payment succeeded!')
|
||||
|
||||
return jsonify({'status': 'success'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# stripe.api_key = key_secret
|
||||
|
||||
# create_product_price()
|
||||
|
||||
# Setup Stripe python client library.
|
||||
load_dotenv(find_dotenv())
|
||||
app.run(port=4242, debug=True)
|
||||
BIN
payments/__pycache__/pay_stripe.cpython-311.pyc
Normal file
BIN
payments/__pycache__/pay_stripe.cpython-311.pyc
Normal file
Binary file not shown.
170
payments/pay_stripe.py
Normal file
170
payments/pay_stripe.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: App General
|
||||
Feature: App
|
||||
|
||||
Description:
|
||||
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
import os
|
||||
import stripe
|
||||
import json
|
||||
from flask import Flask, render_template, render_template_string, jsonify, request, send_from_directory, redirect
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
key_secret = os.environ.get("KEY_SECRET_STRIPE")
|
||||
key_public = os.environ.get("KEY_PUBLIC_STRIPE") # 'pk_test_51OGrxlL7BuLKjoMpfpfw7bSmZZK1MhqMoQ5VhW2jUj7YtoEejO4vqnxKPiqTHHuh9U4qqkywbPCSI9TpFKtr4SYH007KHMWs68'
|
||||
|
||||
# METHODS
|
||||
def create_product_price():
|
||||
print(f'stripe.api_key = {stripe.api_key}')
|
||||
starter_subscription = stripe.Product.create(
|
||||
name="Starter Subscription",
|
||||
description="$12/Month subscription",
|
||||
)
|
||||
|
||||
starter_subscription_price = stripe.Price.create(
|
||||
unit_amount=1200,
|
||||
currency="usd",
|
||||
recurring={"interval": "month"},
|
||||
product=starter_subscription['id'],
|
||||
)
|
||||
|
||||
# Save these identifiers
|
||||
print(f"Success! Here is your starter subscription product id: {starter_subscription.id}")
|
||||
print(f"Success! Here is your starter subscription price id: {starter_subscription_price.id}")
|
||||
|
||||
return starter_subscription_price.id
|
||||
|
||||
def get_file_str(f_address):
|
||||
f = open(f_address)
|
||||
return f.read()
|
||||
|
||||
# Ensure environment variables are set.
|
||||
price = os.getenv('PRICE')
|
||||
if price is None or price == 'price_12345' or price == '':
|
||||
print('You must set a Price ID in .env. Please see the README.')
|
||||
exit(0)
|
||||
|
||||
# For sample support and debugging, not required for production:
|
||||
stripe.set_app_info(
|
||||
'stripe-samples/checkout-one-time-payments',
|
||||
version='0.0.1',
|
||||
url='https://github.com/stripe-samples/checkout-one-time-payments')
|
||||
|
||||
# stripe.api_version = '2020-08-27'
|
||||
stripe.api_key = key_secret # os.getenv('KEY_SECRET_STRIPE')
|
||||
|
||||
app_dir = str(os.path.abspath(os.path.join(
|
||||
__file__, "..", "..")))
|
||||
static_dir = str(os.path.abspath(os.path.join(
|
||||
app_dir, os.getenv("STATIC_DIR"))))
|
||||
app = Flask(__name__, static_folder=static_dir,
|
||||
static_url_path="", template_folder=static_dir)
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_example():
|
||||
# return render_template(f'{app_dir}\\templates\\_home.html') # f'{app_dir}\\templates\\layout.html')
|
||||
# return render_template_string(get_file_str(f'{app_dir}\\templates\\_home.html')) # f'{app_dir}\\templates\\layout.html')
|
||||
return render_template('../templates/_home.html')
|
||||
|
||||
|
||||
@app.route('/config', methods=['GET'])
|
||||
def get_publishable_key():
|
||||
price = stripe.Price.retrieve(os.getenv('PRICE'))
|
||||
return jsonify({
|
||||
'publicKey': key_public, # os.getenv('KEY_PUBLIC_STRIPE'),
|
||||
'unitAmount': price['unit_amount'],
|
||||
'currency': price['currency']
|
||||
})
|
||||
|
||||
# Fetch the Checkout Session to display the JSON result on the success page
|
||||
@app.route('/checkout-session', methods=['GET'])
|
||||
def get_checkout_session():
|
||||
id = request.args.get('sessionId')
|
||||
checkout_session = stripe.checkout.Session.retrieve(id)
|
||||
return jsonify(checkout_session)
|
||||
|
||||
|
||||
@app.route('/create-checkout-session', methods=['POST'])
|
||||
def create_checkout_session():
|
||||
quantity = request.form.get('quantity', 1)
|
||||
domain_url = os.getenv('DOMAIN')
|
||||
|
||||
try:
|
||||
# Create new Checkout Session for the order
|
||||
# Other optional params include:
|
||||
# [billing_address_collection] - to display billing address details on the page
|
||||
# [customer] - if you have an existing Stripe Customer ID
|
||||
# [payment_intent_data] - lets capture the payment later
|
||||
# [customer_email] - lets you prefill the email input in the form
|
||||
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
|
||||
# For full details see https://stripe.com/docs/api/checkout/sessions/create
|
||||
|
||||
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
|
||||
checkout_session = stripe.checkout.Session.create(
|
||||
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
|
||||
cancel_url=domain_url + '/canceled.html',
|
||||
mode='payment',
|
||||
# automatic_tax={'enabled': True},
|
||||
line_items=[{
|
||||
'price': os.getenv('PRICE'),
|
||||
'quantity': quantity,
|
||||
}]
|
||||
)
|
||||
return redirect(checkout_session.url, code=303)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
|
||||
@app.route('/webhook', methods=['POST'])
|
||||
def webhook_received():
|
||||
# You can use webhooks to receive information about asynchronous payment events.
|
||||
# For more about our webhook events check out https://stripe.com/docs/webhooks.
|
||||
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
|
||||
request_data = json.loads(request.data)
|
||||
|
||||
if webhook_secret:
|
||||
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
|
||||
signature = request.headers.get('stripe-signature')
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload=request.data, sig_header=signature, secret=webhook_secret)
|
||||
data = event['data']
|
||||
except Exception as e:
|
||||
return e
|
||||
# Get the type of webhook event sent - used to check the status of PaymentIntents.
|
||||
event_type = event['type']
|
||||
else:
|
||||
data = request_data['data']
|
||||
event_type = request_data['type']
|
||||
data_object = data['object']
|
||||
|
||||
print('event ' + event_type)
|
||||
|
||||
if event_type == 'checkout.session.completed':
|
||||
print('🔔 Payment succeeded!')
|
||||
|
||||
return jsonify({'status': 'success'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# stripe.api_key = key_secret
|
||||
|
||||
# create_product_price()
|
||||
|
||||
# Setup Stripe python client library.
|
||||
load_dotenv(find_dotenv())
|
||||
app.run(port=4242, debug=True)
|
||||
24
requirements.txt
Normal file
24
requirements.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
flask
|
||||
flask_wtf
|
||||
flask_sqlalchemy
|
||||
flask_cors
|
||||
authlib
|
||||
jwt
|
||||
mysqlclient
|
||||
|
||||
|
||||
# environment variables
|
||||
# KEY_SECRET_FLASK = 'random secret key'
|
||||
# # KEY_PUBLIC_FLASK = ''
|
||||
# ID_AUTH0_CLIENT = ''
|
||||
# ID_AUTH0_CLIENT_SECRET = ''
|
||||
# DOMAIN_AUTH0=dev-nwak2066ef6h8ixn.us.auth0.com
|
||||
# SQLALCHEMY_DATABASE_URI = 'mysql://username:password@localhost/dbname'
|
||||
# Replace 'username', 'password', 'localhost', and 'dbname' with your actual database credentials
|
||||
|
||||
# run with:
|
||||
# python -m flask run
|
||||
|
||||
# not env var:
|
||||
# auth0 recovery key: S92HY59VU5J9YDVYWCRHSKJ8
|
||||
54
routes.py
Normal file
54
routes.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Controller - Webpage routing
|
||||
|
||||
Description:
|
||||
Defines the routes and view functions for each page.
|
||||
Manages the interaction between the frontend and backend.
|
||||
"""
|
||||
|
||||
from flask import render_template, url_for, Blueprint
|
||||
from app import app
|
||||
from app.forms import Form_Contact
|
||||
# from forms import MyForm
|
||||
# from app import MyForm
|
||||
from model_view_contact import Model_View_Contact
|
||||
|
||||
"""
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
return render_template('_home.html', title='Home')
|
||||
|
||||
@app.route('/store', methods=['GET'])
|
||||
def store_home():
|
||||
return render_template('_store_home.html', title='Store Home')
|
||||
|
||||
@app.route('/contact', methods=['GET', 'POST'])
|
||||
def contact():
|
||||
form = Form_Contact()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
email = form.sender_email.data
|
||||
CC = form.sender_CC.data
|
||||
name = form.sender_name.data
|
||||
msg = form.sender_message.data
|
||||
# return render_template('contact.html', form=form)
|
||||
# return render_template('_contact.html', title='Contact Us')
|
||||
return render_template('contact.html', model=Model_View_Contact(form))
|
||||
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return render_template('about.html')
|
||||
|
||||
@app.route('/contact', methods=['GET', 'POST'])
|
||||
def contact():
|
||||
form = MyForm()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
pass
|
||||
return render_template('contact.html', form=form)
|
||||
"""
|
||||
16
run.py
Normal file
16
run.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Backend
|
||||
Feature: Launcher
|
||||
|
||||
Description:
|
||||
Runs project.
|
||||
"""
|
||||
|
||||
from app import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
96
static/batch/sql_combine.bat
Normal file
96
static/batch/sql_combine.bat
Normal file
@@ -0,0 +1,96 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: set "test=C:\C:\ \"
|
||||
set dir_parent=C:\Users\edwar\OneDrive\Documents\Programming\Visual Studio 2022\PARTS_Web\app\static\sql
|
||||
set "f_list=file_list.txt"
|
||||
set dir_current=%cd%
|
||||
set "f_tmp=temp.txt"
|
||||
set "f_combine=000_combine.sql"
|
||||
set verbose=0
|
||||
set "strs_delete_0=920_edit_permissions.sql"
|
||||
set "strs_delete_1=910_anal.sql"
|
||||
set "strs_delete_2=deprecated"
|
||||
set "strs_delete_3=%f_list%"
|
||||
set "strs_delete_4=%f_tmp%"
|
||||
set "strs_delete_5=701_p_shop_get_many_role_permission.sql"
|
||||
set "strs_delete_6=600_p_shop_save_product.sql"
|
||||
set "strs_delete_7=170_ish_tbl_ERP_Order.sql"
|
||||
set strs_n_max=7
|
||||
set strs_list =%strs_delete_0% %strs_delete_1% %strs_delete_2% %strs_delete_3% %strs_delete_4%
|
||||
set "str_true=true"
|
||||
set "str_replace="
|
||||
|
||||
set "str_list="
|
||||
|
||||
|
||||
:: report constants
|
||||
echo temp = %f_tmp%
|
||||
echo dir_parent = !%dir_parent%!
|
||||
echo dir_current = !%dir_current%!
|
||||
echo file_list = %f_list%
|
||||
echo file = %f_combine%
|
||||
echo n strings = !strs_n_max!
|
||||
|
||||
:: begin
|
||||
cd %dir_parent%
|
||||
::echo current directory: %cd%
|
||||
del %f_tmp%
|
||||
del %f_list%
|
||||
del %f_combine%
|
||||
|
||||
:: colate dir files
|
||||
dir /b > %f_list%
|
||||
|
||||
::type %f_list%
|
||||
|
||||
echo loopy
|
||||
:: Remove blacklist files
|
||||
:: grep -v '920_edit_permissions.sql' %f_list% > %f_list%
|
||||
:: grep -v '910_anal.sql' %f_list% > %f_list%
|
||||
|
||||
|
||||
(for /f "delims=" %%a in (%f_list%) do (
|
||||
::if %verbose% gtr 0 ( echo new line )
|
||||
set "line=%%a"
|
||||
::if %verbose% gtr 0 ( echo new line = !line! )
|
||||
::if !line! neq %strs_delete_0% if !line! neq %strs_delete_1% if !line! neq %strs_delete_2% if !line! neq %strs_delete_3% if !line! neq %strs_delete_4% if !line! neq %strs_delete_5% if !line! neq %strs_delete_6% (
|
||||
:: echo !line!
|
||||
:: set "str_list=!str_list! !line!"
|
||||
::)
|
||||
set include_line=1
|
||||
for /L %%i in (0, 1, %strs_n_max%) do (
|
||||
::set "str_delete=!strs_delete_%%i!"
|
||||
if !line! equ !strs_delete_%%i! (
|
||||
set include_line=0
|
||||
)
|
||||
::set "include_line=!include_line!"
|
||||
set "line=!line!"
|
||||
)
|
||||
if !include_line! gtr 0 (
|
||||
echo !line!
|
||||
set "str_list=!str_list! !line!"
|
||||
)
|
||||
set "line=!line!"
|
||||
)) > %f_tmp%
|
||||
|
||||
:: Combine files
|
||||
echo output list:
|
||||
type %f_tmp%
|
||||
|
||||
del %f_list%
|
||||
echo file_tmp: %f_tmp%
|
||||
echo file_list: %f_list%
|
||||
echo combining files
|
||||
::type %f_tmp% | type > %f_list%
|
||||
::
|
||||
::type %f_list%
|
||||
echo cmd:
|
||||
echo "type !str_list! > %f_combine%"
|
||||
type !str_list! > %f_combine%
|
||||
|
||||
cd %dir_current%
|
||||
|
||||
echo Current Time: %TIME%
|
||||
|
||||
endlocal
|
||||
15
static/css/contact.css
Normal file
15
static/css/contact.css
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
.content > a {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content > a > img, .content > a > h4 {
|
||||
flex: content;
|
||||
margin: 0px;
|
||||
}
|
||||
*/
|
||||
|
||||
14
static/css/home.css
Normal file
14
static/css/home.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.img-demo {
|
||||
max-width: 50%;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.img-featured {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
img {
|
||||
background-image: url("/static/images/Tag_Molly1.png");
|
||||
}
|
||||
*/
|
||||
302
static/css/shared.css
Normal file
302
static/css/shared.css
Normal file
@@ -0,0 +1,302 @@
|
||||
:root {
|
||||
/* Declare global variables */
|
||||
--c_purple: #5B29FF;
|
||||
--c_purple_pastel: #D1D1FF;
|
||||
--c_purple_light: #C6BDFF;
|
||||
--c_purple_dark: #4700B3;
|
||||
--c_blue: #0044FF;
|
||||
--c_blue_pastel: #B8E0FF;
|
||||
--c_blue_light: #73E8FF;
|
||||
--c_blue_dark: #003ADB;
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial;
|
||||
padding: 10px;
|
||||
background: var(--c_purple_pastel);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.4vh;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 2vh;
|
||||
margin: 1vh;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.5vh;
|
||||
margin: 1vh;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25vh;
|
||||
margin: 1vh;
|
||||
}
|
||||
|
||||
/* Header/Blog Title */
|
||||
.header {
|
||||
padding: 1vh;
|
||||
text-align: center;
|
||||
background-color: var(--c_purple_light);
|
||||
}
|
||||
|
||||
/* Style the top navigation bar */
|
||||
.topnav {
|
||||
overflow: hidden;
|
||||
background-color: var(--c_purple);
|
||||
border-bottom-left-radius: 2.5vh;
|
||||
border-bottom-right-radius: 2.5vh;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Style the topnav links */
|
||||
.topnav a, .topnav label {
|
||||
float: left;
|
||||
display: block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
/* padding: 14px 16px; */
|
||||
text-decoration: none;
|
||||
}
|
||||
.topnav a {
|
||||
padding: 3vh 2vw;
|
||||
}
|
||||
/* Change color on hover */
|
||||
.topnav a:hover {
|
||||
background-color: var(--c_purple_light);
|
||||
color: var(--c_purple_dark);
|
||||
}
|
||||
|
||||
.topnav .container {
|
||||
max-width: 20%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Create two unequal columns that floats next to each other */
|
||||
/* Left column */
|
||||
.leftcolumn {
|
||||
float: left;
|
||||
width: 70%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* min-width: fit-content; */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Right column */
|
||||
.rightcolumn {
|
||||
float: left;
|
||||
width: 30%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* min-width: fit-content; only on store?? */
|
||||
/* background-color: #f1f1f1; */
|
||||
padding-left: 20px;
|
||||
height: fit-content;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Fake image */
|
||||
.fakeimg {
|
||||
background-color: var(--c_purple_light);
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
img, video {
|
||||
border-radius: 3vh;
|
||||
}
|
||||
|
||||
/* header image */
|
||||
img.header-logo {
|
||||
max-height: 15vh;
|
||||
}
|
||||
|
||||
/* icon images */
|
||||
.img-icon {
|
||||
max-width: 5vh;
|
||||
max-height: 5vh;
|
||||
border-radius: 1vh;
|
||||
}
|
||||
|
||||
.container-icon-label {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container-icon-label > * {
|
||||
display: inline-flex;
|
||||
margin-left: 1vh;
|
||||
margin-right: 1vh;
|
||||
}
|
||||
|
||||
.header > .container:first-of-type {
|
||||
max-width: 25%;
|
||||
justify-self: left;
|
||||
}
|
||||
.header > .container:last-of-type {
|
||||
max-width: 75%;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
/* Add a card effect for articles */
|
||||
.card {
|
||||
background-color: white;
|
||||
padding: 1vh;
|
||||
margin-top: 3vh;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border-radius: 4vh;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.card.subcard {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.header.card {
|
||||
border-radius: 2.5vh;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
margin: 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.container > .card:first-of-type {
|
||||
margin-top: none;
|
||||
}
|
||||
|
||||
/* Clear floats after the columns
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
*/
|
||||
/* Footer */
|
||||
.footer {
|
||||
padding: 1vh;
|
||||
text-align: center;
|
||||
background: var(--c_purple_light);
|
||||
margin-top: 20px;
|
||||
border-radius: 2.5vh;
|
||||
}
|
||||
|
||||
.footer > h4, h5 {
|
||||
padding: 0;
|
||||
margin: 1vh;
|
||||
}
|
||||
|
||||
/* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */
|
||||
@media screen and (max-width: 800px) {
|
||||
.leftcolumn, .rightcolumn {
|
||||
width: 100%;
|
||||
/* padding: 0; */
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive layout - when the screen is less than 400px wide, make the navigation links stack on top of each other instead of next to each other */
|
||||
@media screen and (max-width: 400px) {
|
||||
.topnav a {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* input container
|
||||
margin-top: 3vh;
|
||||
*/
|
||||
.container-input {
|
||||
padding: 1vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container-input > label {
|
||||
width: 100%;
|
||||
margin-bottom: 1vh;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.container-input:not(:nth-child(3)) > label {
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.container-input > input, .container-input > textarea {
|
||||
border: 2px solid var(--c_purple);
|
||||
max-width: 50%;
|
||||
min-width: 40%;
|
||||
padding: 1vh;
|
||||
}
|
||||
|
||||
.label-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button, .btn-submit, input[type="submit"] {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
border: 4px solid;
|
||||
border-radius: 2vh;
|
||||
padding: 1vh 2vh 1vh 2vh;
|
||||
margin: 0.5vh;
|
||||
background-color: var(--c_blue_pastel);
|
||||
color: var(--c_blue_dark);
|
||||
border-color: var(--c_blue_dark);
|
||||
}
|
||||
|
||||
/* Overlay modal */
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
0
static/css/store_home.css
Normal file
0
static/css/store_home.css
Normal file
0
static/css/store_product.css
Normal file
0
static/css/store_product.css
Normal file
55
static/css/store_shared.css
Normal file
55
static/css/store_shared.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.img-product {
|
||||
max-width: 20vh;
|
||||
max-height: 20vh;
|
||||
border-radius: 3vh;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.img-thumbnail {
|
||||
max-width: 10vh;
|
||||
max-height: 10vh;
|
||||
border-radius: 3vh;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.btnAdd2Basket {
|
||||
background-color: var(--c_blue_pastel);
|
||||
color: var(--c_blue_dark);
|
||||
border-color: var(--c_blue_dark);
|
||||
}
|
||||
|
||||
#btnCheckout, .btnBuyNow {
|
||||
background-color: var(--c_purple_pastel);
|
||||
color: var(--c_purple_dark);
|
||||
border-color: var(--c_purple_dark);
|
||||
}
|
||||
|
||||
.btn-increment, .btn-decrement {
|
||||
border: 2px solid darkgrey;
|
||||
background-color: lightgray;
|
||||
margin: 1vh 1vh;
|
||||
width: 2.5vh;
|
||||
height: 2.5vh;
|
||||
border-radius: 1.25vh;
|
||||
font-size: 2vh;
|
||||
}
|
||||
|
||||
.container-input > input {
|
||||
padding: 0vh 1vh;
|
||||
border-radius: 0.5vh;
|
||||
max-width: 7vh;
|
||||
}
|
||||
|
||||
#basket {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.basket-item-delete {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* Right column */
|
||||
.rightcolumn {
|
||||
min-width: fit-content;
|
||||
}
|
||||
86
static/css/stylesheet.css
Normal file
86
static/css/stylesheet.css
Normal file
@@ -0,0 +1,86 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.banner {
|
||||
background-color: black;
|
||||
color: white;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
padding-top: 5vh;
|
||||
padding-bottom: 10vh;
|
||||
}
|
||||
|
||||
.banner.top {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.banner.bottom {
|
||||
background-color: black;
|
||||
color: white;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.column {
|
||||
float: left;
|
||||
padding: 5vw;
|
||||
}
|
||||
|
||||
.column.side {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.column.middle {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.midbod {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.panel {
|
||||
float: left;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.panel.labelcontainer {
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.label.bodytext {
|
||||
background-color: black;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-style: normal;
|
||||
font-size: 12;
|
||||
}
|
||||
|
||||
.label.title {
|
||||
font-style: bold;
|
||||
font-size: 18;
|
||||
}
|
||||
BIN
static/docs/Logo.png
Normal file
BIN
static/docs/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
static/docs/__pycache__/server.cpython-311.pyc
Normal file
BIN
static/docs/__pycache__/server.cpython-311.pyc
Normal file
Binary file not shown.
124
static/docs/server.py
Normal file
124
static/docs/server.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#! /usr/bin/env python3.6
|
||||
|
||||
"""
|
||||
server.py
|
||||
Stripe Sample.
|
||||
Python 3.6 or newer required.
|
||||
"""
|
||||
|
||||
import stripe
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, render_template, jsonify, request, send_from_directory, redirect
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
|
||||
# Setup Stripe python client library.
|
||||
load_dotenv(find_dotenv())
|
||||
|
||||
# Ensure environment variables are set.
|
||||
price = os.getenv('PRICE')
|
||||
if price is None or price == 'price_12345' or price == '':
|
||||
print('You must set a Price ID in .env. Please see the README.')
|
||||
exit(0)
|
||||
|
||||
# For sample support and debugging, not required for production:
|
||||
stripe.set_app_info(
|
||||
'stripe-samples/checkout-one-time-payments',
|
||||
version='0.0.1',
|
||||
url='https://github.com/stripe-samples/checkout-one-time-payments')
|
||||
|
||||
stripe.api_version = '2020-08-27'
|
||||
stripe.api_key = os.getenv('STRIPE_KEY_SECRET')
|
||||
|
||||
static_dir = str(os.path.abspath(os.path.join(
|
||||
__file__, "..", os.getenv("STATIC_DIR"))))
|
||||
app = Flask(__name__, static_folder=static_dir,
|
||||
static_url_path="", template_folder=static_dir)
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def get_example():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/config', methods=['GET'])
|
||||
def get_publishable_key():
|
||||
price = stripe.Price.retrieve(os.getenv('PRICE'))
|
||||
return jsonify({
|
||||
'publicKey': os.getenv('STRIPE_PUBLISHABLE_KEY'),
|
||||
'unitAmount': price['unit_amount'],
|
||||
'currency': price['currency']
|
||||
})
|
||||
|
||||
# Fetch the Checkout Session to display the JSON result on the success page
|
||||
@app.route('/checkout-session', methods=['GET'])
|
||||
def get_checkout_session():
|
||||
id = request.args.get('sessionId')
|
||||
checkout_session = stripe.checkout.Session.retrieve(id)
|
||||
return jsonify(checkout_session)
|
||||
|
||||
|
||||
@app.route('/create-checkout-session', methods=['POST'])
|
||||
def create_checkout_session():
|
||||
quantity = request.form.get('quantity', 1)
|
||||
domain_url = os.getenv('DOMAIN')
|
||||
|
||||
try:
|
||||
# Create new Checkout Session for the order
|
||||
# Other optional params include:
|
||||
# [billing_address_collection] - to display billing address details on the page
|
||||
# [customer] - if you have an existing Stripe Customer ID
|
||||
# [payment_intent_data] - lets capture the payment later
|
||||
# [customer_email] - lets you prefill the email input in the form
|
||||
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
|
||||
# For full details see https://stripe.com/docs/api/checkout/sessions/create
|
||||
|
||||
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
|
||||
checkout_session = stripe.checkout.Session.create(
|
||||
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
|
||||
cancel_url=domain_url + '/canceled.html',
|
||||
mode='payment',
|
||||
# automatic_tax={'enabled': True},
|
||||
line_items=[{
|
||||
'price': os.getenv('PRICE'),
|
||||
'quantity': quantity,
|
||||
}]
|
||||
)
|
||||
return redirect(checkout_session.url, code=303)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
|
||||
@app.route('/webhook', methods=['POST'])
|
||||
def webhook_received():
|
||||
# You can use webhooks to receive information about asynchronous payment events.
|
||||
# For more about our webhook events check out https://stripe.com/docs/webhooks.
|
||||
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
|
||||
request_data = json.loads(request.data)
|
||||
|
||||
if webhook_secret:
|
||||
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
|
||||
signature = request.headers.get('stripe-signature')
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload=request.data, sig_header=signature, secret=webhook_secret)
|
||||
data = event['data']
|
||||
except Exception as e:
|
||||
return e
|
||||
# Get the type of webhook event sent - used to check the status of PaymentIntents.
|
||||
event_type = event['type']
|
||||
else:
|
||||
data = request_data['data']
|
||||
event_type = request_data['type']
|
||||
data_object = data['object']
|
||||
|
||||
print('event ' + event_type)
|
||||
|
||||
if event_type == 'checkout.session.completed':
|
||||
print('🔔 Payment succeeded!')
|
||||
|
||||
return jsonify({'status': 'success'})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=4242, debug=True)
|
||||
63
static/docs/template webpage.html
Normal file
63
static/docs/template webpage.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="../css/copiedstyles.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h1>My Website</h1>
|
||||
<p>Resize the browser window to see the effect.</p>
|
||||
</div>
|
||||
|
||||
<div class="topnav">
|
||||
<a href="#">Link</a>
|
||||
<a href="#">Link</a>
|
||||
<a href="#">Link</a>
|
||||
<a href="#" style="float:right">Link</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="leftcolumn">
|
||||
<div class="card">
|
||||
<h2>TITLE HEADING</h2>
|
||||
<h5>Title description, Dec 7, 2017</h5>
|
||||
<div class="fakeimg" style="height:200px;">Image</div>
|
||||
<p>Some text..</p>
|
||||
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>TITLE HEADING</h2>
|
||||
<h5>Title description, Sep 2, 2017</h5>
|
||||
<div class="fakeimg" style="height:200px;">Image</div>
|
||||
<p>Some text..</p>
|
||||
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightcolumn">
|
||||
<div class="card">
|
||||
<h2>About Me</h2>
|
||||
<div class="fakeimg" style="height:100px;">Image</div>
|
||||
<p>Some text about me in culpa qui officia deserunt mollit anim..</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Popular Post</h3>
|
||||
<div class="fakeimg"><p>Image</p></div>
|
||||
<div class="fakeimg"><p>Image</p></div>
|
||||
<div class="fakeimg"><p>Image</p></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Follow Me</h3>
|
||||
<p>Some text..</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<h2>Footer</h2>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
1
static/docs/test.py
Normal file
1
static/docs/test.py
Normal file
@@ -0,0 +1 @@
|
||||
print(not None)
|
||||
BIN
static/images/Braille_Translator_Demo.png
Normal file
BIN
static/images/Braille_Translator_Demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
static/images/CAD.gif
Normal file
BIN
static/images/CAD.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
BIN
static/images/Fractal Fortune Teller.mp4
Normal file
BIN
static/images/Fractal Fortune Teller.mp4
Normal file
Binary file not shown.
BIN
static/images/Invoice Generator.mp4
Normal file
BIN
static/images/Invoice Generator.mp4
Normal file
Binary file not shown.
BIN
static/images/Logo.png
Normal file
BIN
static/images/Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
static/images/Logo_GitHub.png
Normal file
BIN
static/images/Logo_GitHub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user