1.started removal of CDNs.\n 2. Improved modular structure for all parts of project including database.

This commit is contained in:
2024-08-30 23:27:28 +01:00
parent 9de1ccce16
commit ba50aec9c9
2895 changed files with 490579 additions and 7561 deletions

View File

@@ -1,54 +0,0 @@
"""
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)
"""

View File

@@ -28,11 +28,11 @@ 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
from business_objects.store.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
from business_objects.store.basket import Basket_Item
from datastores.datastore_store_base import DataStore_Store
from business_objects.store.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
@@ -163,9 +163,9 @@ def store_home():
print(f"id_region_delivery = {id_region_delivery}")
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
# model.get_regions_and_currencies()
# model.categories = Model_View_Store_Home.get_many_product_category(db)
# model.categories = Model_View_Store_Home.get_many_product(db)
# product = categories[list(categories.keys())[0]][0]
model.get_many_product_category(Product_Filters(
model.get_many_product(Product_Filters(
model.id_user,
True, '', False,
True, '', False, False,
@@ -423,7 +423,7 @@ def store_product(permutation_id, region_id, currency_id, is_included_VAT):
model = Model_View_Store_Product(db, get_info_user(), app, permutation_id, currency_id, region_id, is_included_VAT)
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))
# model.get_many_product(product_ids = str(product_id))
# print('categories reached')
# product = model.categories[0].products[0]# [list(categories.keys())[0]][0]
# print('product reached')
@@ -654,7 +654,7 @@ def login_callback():
# 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
# DataStore_Store().add_new_user(id_user) # this is part of get basket - should occur on page load
return redirect("/")

Binary file not shown.

Binary file not shown.

Binary file not shown.

571
app.py
View File

@@ -18,549 +18,78 @@ Initializes the Flask application, sets the configuration based on the environme
# internal
from config import app_config, Config
# from routes import bp_home
from extensions import db, csrf, cors, mail, oauth
"""
from forms import Form_Contact, Form_Supplier, Form_Filters_Permutation, Form_Filters_Stock_Item
from models.model_view_base import Model_View_Base
from models.model_view_admin import Model_View_Admin
from models.model_view_home import Model_View_Home
from models.model_view_contact import Model_View_Contact
from models.model_view_services import Model_View_Services
from models.model_view_store_stock_item import Model_View_Store_Stock_Item
from models.model_view_supplier import Model_View_Supplier
from models.model_view_store_permutation import Model_View_Store_Permutation
from models.model_view_store_stock_items import Model_View_Store_Stock_Items
from models.model_view_store_supplier import Model_View_Store_Supplier
from models.model_view_store_product_permutation import Model_View_Store_Product_Permutations
from models.model_view_user import Model_View_User
from business_objects.product import Product, Product_Filters, Product_Permutation # , Product_Image_Filters, Resolution_Level_Enum
from business_objects.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.store.product import Product, Product_Filters, Product_Permutation # , Product_Image_Filters, Resolution_Level_Enum
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters
from datastores.datastore_store import DataStore_Store
from datastores.datastore_store_base import DataStore_Store
from helpers.helper_app import Helper_App
import lib.argument_validation as av
"""
from routing.core import routes_core
from routing.legal import routes_legal
from routing.store.product_category import routes_store_product_category
from routing.store.product_permutation import routes_store_product_permutation
from routing.store.stock_item import routes_store_stock_item
from routing.store.supplier import routes_store_supplier
from routing.user import routes_user
# 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
from flask_mail import Mail, Message
from flask_wtf.csrf import CSRFProtect
import stripe
import json
from dotenv import load_dotenv, find_dotenv
# from flask_appconfig import AppConfig
import os
import sys
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client import OAuthError
import jwt
from urllib.parse import quote, urlparse, parse_qs
# VARIABLE INSTANTIATION
app = Flask(__name__)
csrf = CSRFProtect(app)
CORS(app)
# app.register_blueprint(bp_home, url_prefix=''
# app.config.from_object(app_config)
# app.app_config = app_config
app.DEBUG = Config.DEBUG
app.TESTING = Config.TESTING
app.SECRET_KEY = "007cfbdaaf6d1eb27209720e8a5fc8ba0a334ae0be6fcac132b0a471549cde7c" # Config.SECRET_KEY
app.config['SECRET_KEY'] = app.SECRET_KEY
app.DB_NAME = Config.DB_NAME
app.DB_USER = Config.DB_USER
app.DB_PASSWORD = Config.DB_PASSWORD
app.DB_HOST = Config.DB_HOST
# app.DB_PORT = Config.DB_PORT
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.URL_HOST = Config.URL_HOST
app.config.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_DATABASE_URI
app.config.SQLALCHEMY_TRACK_MODIFICATIONS = Config.SQLALCHEMY_TRACK_MODIFICATIONS
app.config.ID_AUTH0_CLIENT = Config.ID_AUTH0_CLIENT
app.config.ID_AUTH0_CLIENT_SECRET = Config.ID_AUTH0_CLIENT_SECRET
app.config.DOMAIN_AUTH0 = Config.DOMAIN_AUTH0
app.config.ID_TOKEN_USER = Config.ID_TOKEN_USER
app.config.URL_HOST = Config.URL_HOST
app.config['SQLALCHEMY_DATABASE_URI'] = Config.SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Config.SQLALCHEMY_TRACK_MODIFICATIONS
app.config['ID_AUTH0_CLIENT'] = Config.ID_AUTH0_CLIENT
app.config['ID_AUTH0_CLIENT_SECRET'] = Config.ID_AUTH0_CLIENT_SECRET
app.config['DOMAIN_AUTH0'] = Config.DOMAIN_AUTH0
app.config['ID_TOKEN_USER'] = Config.ID_TOKEN_USER
app.MAIL_DEBUG = True
app.MAIL_SERVER = Config.MAIL_SERVER
app.MAIL_PORT = Config.MAIL_PORT
app.MAIL_USE_TLS = Config.MAIL_USE_TLS
app.MAIL_USE_SSL = Config.MAIL_USE_SSL
app.MAIL_USERNAME = Config.MAIL_USERNAME
app.MAIL_PASSWORD = Config.MAIL_PASSWORD
app.MAIL_DEFAULT_SENDER = Config.MAIL_DEFAULT_SENDER
app.config.MAIL_DEBUG = True
app.config.MAIL_SERVER = Config.MAIL_SERVER
app.config.MAIL_PORT = Config.MAIL_PORT
app.config.MAIL_USE_TLS = Config.MAIL_USE_TLS
app.config.MAIL_USE_SSL = Config.MAIL_USE_SSL
app.config.MAIL_USERNAME = Config.MAIL_USERNAME
app.config.MAIL_PASSWORD = Config.MAIL_PASSWORD
app.config.MAIL_DEFAULT_SENDER = Config.MAIL_DEFAULT_SENDER
app.config['MAIL_DEBUG'] = True
app.config['MAIL_SERVER'] = Config.MAIL_SERVER
app.config['MAIL_PORT'] = Config.MAIL_PORT
app.config['MAIL_USE_TLS'] = Config.MAIL_USE_TLS
app.config['MAIL_USE_SSL'] = Config.MAIL_USE_SSL
app.config['MAIL_USERNAME'] = Config.MAIL_USERNAME
app.config['MAIL_PASSWORD'] = Config.MAIL_PASSWORD
app.config['MAIL_DEFAULT_SENDER'] = Config.MAIL_DEFAULT_SENDER
app.config['MAIL_CONTACT_PUBLIC'] = Config.MAIL_CONTACT_PUBLIC
# app.RECAPTCHA_PUBLIC_KEY = Config.RECAPTCHA_PUBLIC_KEY
app.config['RECAPTCHA_PUBLIC_KEY'] = Config.RECAPTCHA_PUBLIC_KEY
# app.RECAPTCHA_PRIVATE_KEY = Config.RECAPTCHA_PRIVATE_KEY
app.config['RECAPTCHA_PRIVATE_KEY'] = Config.RECAPTCHA_PRIVATE_KEY
# db = SQLAlchemy(app)
app.config['SQLALCHEMY_DATABASE_URI'] = Config.SQLALCHEMY_DATABASE_URI
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',
api_base_url = f'https://{app.DOMAIN_AUTH0}',
authorize_url = f'https://{app.DOMAIN_AUTH0}/authorize',
access_token_url = f'https://{app.DOMAIN_AUTH0}/oauth/token',
)
# session[app.ID_TOKEN_USER] = {'userinfo': {'sub': ''}}
db = SQLAlchemy()
db.init_app(app)
with app.app_context():
db.create_all()
db.engine.url = app.config.SQLALCHEMY_DATABASE_URI
mail = Mail(app) # ToDo: move to helper
# METHODS
sys.path.insert(0, os.path.dirname(__file__)) # Todo: why?
@app.route('/hello')
def hello():
return "Hello, World!" + f"{app.errors}\n{app.config.SQLALCHEMY_DATABASE_URI == app.SQLALCHEMY_DATABASE_URI}"
@app.route('/goodbye')
def goodbye():
return "Goodbye, cruel World!"
"""
@app.route('/public_html/403.shtml', methods=['GET'])
def forbidden():
return send_from_directory('/home/partsltd/public_html', '403.shtml')
"""
app = Flask(__name__)
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
message = 'It works!\n'
version = 'Python %s\n' % sys.version.split()[0]
response = '\n'.join([message, version])
return [response.encode()]
# AppConfig(app)
app.config.from_object(app_config) # for db init with required keys
# app.config["config"] = app_config()
csrf.init_app(app)
cors.init_app(app)
db.init_app(app)
mail.init_app(app)
oauth.init_app(app)
# ROUTING
@app.route('/', methods=['GET'])
def home():
try:
model = Model_View_Home(app, db)
print('nips')
html_body = render_template('_page_home.html', model = model)
except Exception as e:
return jsonify(error=str(e)), 403
return html_body
@app.route('/contact', methods=['GET'])
def contact():
try:
form = Form_Contact()
model = Model_View_Contact(app, db, form)
html_body = render_template('_page_contact.html', model = model)
except Exception as e:
return jsonify(error=str(e)), 403
return html_body
with app.app_context():
# config = app.config["config"]
db.create_all()
db.engine.url = app.config['SQLALCHEMY_DATABASE_URI']
@app.route('/contact', methods=['POST'])
def contact_post():
try:
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
message = form.message.data
# send email
mailItem = Message("PARTS Website Contact Us Message", recipients=[app.config['MAIL_CONTACT_PUBLIC']])
mailItem.body = f"Dear Lord Edward Middleton-Smith,\n\n{message}\n\nKind regards,\n{name}\n{email}"
mail.send(mailItem)
return "Submitted."
return "Invalid. Failed to submit."
# html_body = render_template('_page_contact.html', model = model)
except Exception as e:
return jsonify(error=str(e)), 403
@app.route('/services', methods=['GET', 'POST'])
# @app.route('/public_html/services', methods=['GET', 'POST'])
def services():
try:
model = Model_View_Services(app, db)
html_body = render_template('_page_services.html', model = model)
except Exception as e:
return jsonify(error=str(e)), 403
return html_body
# shop management
@app.route('/supplier', methods=['GET'])
def supplier():
try:
data = request.json
except:
data = {}
print(f'data={data}')
form_data = data[Model_View_Supplier.key_form]
print(f'form_data: {form_data}')
form = Form_Supplier(**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:
model = Model_View_Supplier(app, db, form)
# 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 Exception as e:
return jsonify({'status': 'failure', 'Message': f'Bad data received by controller.\n{e}'})
# 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 render_template('_page_supplier.html', model = model)
return jsonify({'status': 'failure', 'Message': f'Invalid supplier details.\n{form.errors}'})
# product permutations
@app.route('/store/permutation', methods=['GET'])
def permutation():
filters = Product_Filters.get_default()
model = Model_View_Store_Permutation(app=app, db=db, filters_product=filters)
return render_template('_page_store_product_permutation.html', model = model)
@app.route('/store/permutation_filter', methods=['POST'])
def permutation_filter():
data = Helper_App.get_request_data(request)
form_filters = None
try:
form_filters = get_Form_Filters_Permutation(data)
if not form_filters.validate_on_submit():
return jsonify({'status': 'failure', 'Message': f'Form invalid.\n{form_filters.errors}'})
# ToDo: manually validate category, product
filters_form = Product_Filters.from_form(form_filters)
model = Model_View_Store_Permutation(app=app, db=db, filters_product=filters_form)
return jsonify({'status': 'success', 'Success': True, 'data': model.category_list.to_list_rows_permutation()})
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Bad data received by controller.\n{e}'})
def get_Form_Filters_Permutation(data_request):
data_form = data_request[Model_View_Store_Permutation.KEY_FORM]
form_filters = Form_Filters_Permutation(**data_form)
form_filters.is_out_of_stock.data = av.input_bool(data_form['is_out_of_stock'], 'is_out_of_stock', 'permutations_post')
return form_filters
@app.route('/store/permutation_save', methods=['POST'])
def permutation_save():
data = Helper_App.get_request_data(request)
form_filters = None
try:
form_filters = get_Form_Filters_Permutation(data)
if not form_filters.validate_on_submit():
return jsonify({'status': 'failure', 'Message': f'Filters form invalid.\n{form_filters.errors}'})
permutations = data[Model_View_Store_Permutation.KEY_PERMUTATIONS]
if len(permutations) == 0:
return jsonify({'status': 'failure', 'Message': f'No permutations.'})
objsPermutation = []
for permutation in permutations:
objsPermutation.append(Product_Permutation.from_json(permutation))
# ToDo: manually validate category, product
filters_form = Product_Filters.from_form(form_filters)
model_save = Model_View_Store_Permutation(app=app, db=db, filters_product=filters_form)
model_save.save_permutations(data.comment, objsPermutation)
model_return = Model_View_Store_Permutation(app=app, db=db, filters_product=filters_form)
return jsonify({'status': 'success', 'Success': True, 'data': model_return.category_list.to_list_rows_permutation()})
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Bad data received by controller.\n{e}'})
# stock
@app.route('/store/stock_item', methods=['GET'])
def stock():
filters = Stock_Item_Filters.get_default()
model = Model_View_Store_Stock_Item(app=app, db=db, filters_stock_item=filters)
return render_template('_page_store_stock_item.html', model = model)
@app.route('/store/stock_item_filter', methods=['POST'])
def stock_filter():
data = Helper_App.get_request_data(request)
form_filters = None
try:
form_filters = get_form_filters_stock_items(data)
if not form_filters.validate_on_submit():
return jsonify({'status': 'failure', 'Message': f'Form invalid.\n{form_filters.errors}'})
# ToDo: manually validate category, product
filters_form = Stock_Item_Filters.from_form(form_filters)
model = Model_View_Store_Stock_Item(app=app, db=db, filters_stock_item=filters_form)
return jsonify({'status': 'success', 'Success': True, 'data': model.category_list.to_list_rows_permutation()})
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Bad data received by controller.\n{e}'})
def get_form_filters_stock_items(data_request):
data_form = data_request[Model_View_Store_Stock_Item.KEY_FORM]
form_filters = Form_Filters_Stock_Item(**data_form)
form_filters.is_out_of_stock.data = av.input_bool(data_form['is_out_of_stock'], 'is_out_of_stock', 'permutations_post')
return form_filters
@app.route('/store/stock_item_save', methods=['POST'])
def stock_save():
data = Helper_App.get_request_data(request)
"""
form_filters = None
try:
form_filters = get_form_filters_stock_items(data)
if not form_filters.validate_on_submit():
return jsonify({'status': 'failure', 'Message': f'Filters form invalid.\n{form_filters.errors}'})
stock_items = data[Model_View_Store_Stock.KEY_PERMUTATIONS]
print(f'stock_items: {stock_items}')
if len(stock_items) == 0:
return jsonify({'status': 'failure', 'Message': f'No stock items.'})
objsStockItem = []
for stock_item in stock_items:
objsStockItem.append(Product_Permutation.from_json(stock_item))
print(f'objsStockItem: {objsStockItem}')
# ToDo: manually validate category, product
filters_form = Stock_Filters.from_form(form_filters)
model_save = Model_View_Store_Stock(app=app, db=db, filters_product=filters_form)
model_save.save_stock_items(data.comment, objsPermutation)
model_return = Model_View_Store_Stock(app=app, db=db, filters_product=filters_form)
return jsonify({'status': 'success', 'Success': True, 'data': model_return.category_list.to_list_rows_permutation()})
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Bad data received by controller.\n{e}'})
"""
# User authentication
@app.route("/login", methods=['POST'])
def login():
try:
data = request.json
except:
data = {}
print(f'data={data}')
# callback_login = F'{Model_View_Base.HASH_CALLBACK_LOGIN}{data.get(Model_View_Base.KEY_CALLBACK, Model_View_Base.HASH_PAGE_HOME)}'
# encoded_path = quote(data.get(Model_View_Base.KEY_CALLBACK, Model_View_Base.HASH_PAGE_HOME))
uri_redirect = url_for('login_callback', _external=True) # , subpath=encoded_path
# uri_redirect = f'{app.URL_HOST}/login_callback?subpath={data.get(Model_View_Base.KEY_CALLBACK, Model_View_Base.HASH_PAGE_HOME)}'
print(f'redirect uri: {uri_redirect}')
hash_callback = data.get(Model_View_Base.KEY_CALLBACK, Model_View_Base.HASH_PAGE_HOME)
print(f'hash_callback: {hash_callback}')
red = oauth.auth0.authorize_redirect(
redirect_uri = uri_redirect,
state = quote(hash_callback)
oauth.register(
"auth0",
client_id = app.config['ID_AUTH0_CLIENT'],
client_secret = app.config['ID_AUTH0_CLIENT_SECRET'],
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=f'https://{app.config['DOMAIN_AUTH0']}/.well-known/openid-configuration',
api_base_url = f'https://{app.config['DOMAIN_AUTH0']}',
authorize_url = f'https://{app.config['DOMAIN_AUTH0']}/authorize',
access_token_url = f'https://{app.config['DOMAIN_AUTH0']}/oauth/token',
)
print(f'redirect: {red}')
headers = red.headers['Location']
print(f'headers: {headers}')
parsed_url = urlparse(headers)
query_params = parse_qs(parsed_url.query)
print(f"""
OAuth Authorize Redirect URL:
Base URL: {parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}
{parsed_url}
Query Parameters: {query_params}
""")
return jsonify({'Success': True, 'status': 'success', f'{Model_View_Base.KEY_CALLBACK}': headers})
@app.route("/login_callback") # <path:subpath>/<code>
def login_callback():
try:
# print(f'code: {code}')
token = None
try:
token = oauth.auth0.authorize_access_token()
except Exception as e:
# Log the error for debugging
print(f"Error: {str(e)}")
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}')
datastore_store = DataStore_Store(app, db)
user = datastore_store.get_user_auth0()
user_filters = User_Filters.from_user(user)
users, errors = datastore_store.get_many_user(user_filters, user)
try:
user = users[0]
print('User logged in')
print(f'user ({str(type(user))}): {user}')
print(f'user key: {Model_View_Base.KEY_USER}')
user_json = user.to_json()
session[Model_View_Base.KEY_USER] = user_json
print(f'user stored on session')
except:
print(f'User not found: {user_filters}')
try:
hash_callback = token.get('hash_callback')
if hash_callback is None:
print('hash is none')
state = request.args.get('state')
print(f'state: {state}')
hash_callback = state # .get('hash_callback')
print(f'hash_callback: {hash_callback}')
except:
print("get hash callback failed")
# 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
print(f'user session: {session[Model_View_Base.KEY_USER]}')
return redirect(f'{app.URL_HOST}{hash_callback}')
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Controller error.\n{e}'})
@app.route("/logout")
def logout():
session.clear()
url_logout = "https://" + app.DOMAIN_AUTH0 + "/v2/logout?" + urlencode(
{
"returnTo": url_for("logout_callback", _external=True),
"client_id": app.ID_AUTH0_CLIENT,
}# ,
# quote_via=quote_plus,
)
app.logger.debug(f"Redirecting to {url_logout}")
print(f"Redirecting to {url_logout}")
return redirect(url_logout)
@app.route("/logout_callback") # <path:subpath>/<code>
def logout_callback():
return home()
try:
session[app.ID_TOKEN_USER] = None
user = User()
try:
hash_callback = token.get('hash_callback')
if hash_callback is None:
print('hash is none')
state = request.args.get('state')
print(f'state: {state}')
hash_callback = state # .get('hash_callback')
print(f'hash_callback: {hash_callback}')
except:
print("get hash callback failed")
# 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
print(f'user session: {session[Model_View_Base.KEY_USER]}')
return redirect(f'{app.URL_HOST}{hash_callback}')
except Exception as e:
return jsonify({'status': 'failure', 'Message': f'Controller error.\n{e}'})
@app.route("/user")
def user():
try:
model = Model_View_User(app, db)
html_body = render_template('_page_user.html', model = model)
except Exception as e:
return str(e)
return html_body
# snore
@app.route('/license', methods=['GET'])
def license():
try:
model = Model_View_Home(app, db)
html_body = render_template('_page_license.html', model = model)
except Exception as e:
return str(e)
return html_body
@app.route('/accessibility-statement', methods=['GET'])
def accessibility_statement():
try:
model = Model_View_Home(app, db)
html_body = render_template('_page_accessibility_statement.html', model = model)
except Exception as e:
return str(e)
return html_body
@app.route('/accessibility-report', methods=['GET'])
def accessibility_report():
try:
model = Model_View_Home(app, db)
html_body = render_template('_page_accessibility_report.html', model = model)
except Exception as e:
return str(e)
return html_body
@app.route('/retention-schedule', methods=['GET'])
def retention_schedule():
try:
model = Model_View_Home(app, db)
html_body = render_template('_page_retention_schedule.html', model = model)
except Exception as e:
return str(e)
return html_body
@app.route('/privacy-notice', methods=['GET'])
def privacy_notice():
try:
model = Model_View_Home(app, db)
html_body = render_template('_page_privacy_notice.html', model = model)
except Exception as e:
return str(e)
return html_body
# Onload
if __name__ == '__main__':
app.run()
# app.run(debug=True, host="0.0.0.0", port=5000)
app.register_blueprint(routes_core)
app.register_blueprint(routes_legal)
# app.register_blueprint(routes_store_product)
# app.register_blueprint(routes_store_product_category)
app.register_blueprint(routes_store_product_permutation)
app.register_blueprint(routes_store_stock_item)
app.register_blueprint(routes_store_supplier)
app.register_blueprint(routes_user)

View File

@@ -1,873 +0,0 @@
"""
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_Filters_Permutation
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
from business_objects.stock_item import Stock_Item
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from dataclasses import dataclass
from typing import ClassVar
# 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):
ATTR_ID_CATEGORY: ClassVar[str] = Variation.ATTR_ID_CATEGORY # 'id-category'
ATTR_ID_PRODUCT: ClassVar[str] = Variation.ATTR_ID_PRODUCT # 'id-product'
ATTR_ID_PERMUTATION: ClassVar[str] = Variation.ATTR_ID_PERMUTATION # 'id-permutation'
FLAG_VARIATIONS: ClassVar[str] = 'variations'
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[22]
product.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
product.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
product.can_admin = av.input_bool(query_row[26], "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)
def add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Product.add_stock_item', Stock_Item)
index_permutation = self.permutation_index[stock_item.id_permutation]
self.permutations[index_permutation].add_stock_item(stock_item)
def has_permutations(self):
return len(self.permutations) > 0
def is_available(self):
if len(self.permutations) == 0:
return False
for permutation in self.permutations:
if permutation.is_available():
return True
return False
def to_list_rows_permutation(self):
list_rows = []
for permutation in self.permutations:
list_rows.append(permutation.to_row_permutation())
return list_rows
class Product_Permutation(db.Model):
FLAG_QUANTITY_STOCK = 'quantity-stock'
FLAG_QUANTITY_MIN = 'quantity-min'
FLAG_QUANTITY_MAX = 'quantity-max'
FLAG_COST_LOCAL = 'cost-local'
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)
id_currency_cost = db.Column(db.Integer)
code_currency_cost = db.Column(db.String(3))
symbol_currency_cost = db.Column(db.String(3))
cost_local = 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 = {}
self.stock_items = []
self.stock_item_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 = False
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.id_currency_cost = query_row[7]
permutation.code_currency_cost = query_row[8]
permutation.symbol_currency_cost = query_row[9]
permutation.cost_local = query_row[6]
permutation.has_variations = query_row[4]
permutation.id_category = query_row[5]
permutation.latency_manufacture = query_row[11]
permutation.quantity_min = query_row[12]
permutation.quantity_max = query_row[13]
permutation.quantity_step = query_row[14]
permutation.quantity_stock = query_row[15]
permutation.id_stripe_product = query_row[16]
permutation.is_subscription = av.input_bool(query_row[17], "is_subscription", _m, v_arg_type=v_arg_type)
permutation.name_recurrence_interval = query_row[18]
permutation.name_plural_recurrence_interval = query_row[19]
permutation.count_recurrence_interval = query_row[20]
permutation.display_order = query_row[23]
permutation.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
permutation.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
permutation.can_admin = av.input_bool(query_row[26], "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 from_json(jsonPermutation):
permutation = Product_Permutation()
permutation.id_category = jsonPermutation[Product.ATTR_ID_CATEGORY]
permutation.id_product = jsonPermutation[Product.ATTR_ID_PRODUCT]
permutation.id_permutation = jsonPermutation[Product.ATTR_ID_PERMUTATION]
permutation.has_variations = len(jsonPermutation[Product.FLAG_VARIATIONS]) > 0
if permutation.has_variations:
for jsonVariation in jsonPermutation[Product.FLAG_VARIATIONS]:
variation = Variation.from_json(jsonVariation)
permutation.add_variation(variation)
permutation.quantity_stock = jsonPermutation[Product_Permutation.FLAG_QUANTITY_STOCK]
permutation.quantity_min = jsonPermutation[Product_Permutation.FLAG_QUANTITY_MIN]
permutation.quantity_max = jsonPermutation[Product_Permutation.FLAG_QUANTITY_MAX]
return permutation
def is_available(self):
return len(self.prices) > 0
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
"""
return self.code_currency_cost if self.symbol_currency_cost == '' else self.symbol_currency_cost
def output_variations(self):
if not self.has_variations: return ''
return '\n'.join([f'{variation.name_variation_type}: {variation.name_variation}' for variation in self.variations])
def output_variations_jsonify(self):
if not self.has_variations: return ''
return ','.join([f'{variation.id_type}: {variation.id_variation}' for variation in self.variations])
"""
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 add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Product_Permutation.add_stock_item', Stock_Item)
self.stock_items.append(stock_item)
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
def to_row_permutation(self):
a = {
Product.ATTR_ID_CATEGORY: self.id_category,
Product.ATTR_ID_PRODUCT: self.id_product,
Product.FLAG_VARIATIONS: self.output_variations(),
Product_Permutation.FLAG_QUANTITY_STOCK: self.quantity_stock,
Product_Permutation.FLAG_QUANTITY_MIN: self.quantity_min,
Product_Permutation.FLAG_QUANTITY_MAX: self.quantity_max,
Product_Permutation.FLAG_COST_LOCAL: f"<strong>{self.symbol_currency_cost}</strong>{self.cost_local}"
}
print('permutation row: ', a)
return a
"""
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
get_inactive_category: bool
get_first_category_only: bool
ids_category: str
get_all_product: bool
get_inactive_product: bool
get_first_product_only: bool
ids_product: str
get_all_permutation: bool
get_inactive_permutation: bool
get_first_permutation_only: bool
ids_permutation: str
get_all_image: bool
get_inactive_image: bool
get_first_image_only: bool
ids_image: str
get_all_region: bool
get_inactive_region: bool
get_first_region_only: bool
ids_region: str
get_all_currency: bool
get_inactive_currency: bool
get_first_currency_only: bool
ids_currency: str
get_all_discount: bool
get_inactive_discount: bool
ids_discount: str
get_products_quantity_stock_below_min: bool
def to_json(self):
return {
'a_id_user': None,
'a_get_all_category': self.get_all_category,
'a_get_inactive_category': self.get_inactive_category,
'a_get_first_category_only': self.get_first_category_only,
'a_ids_category': self.ids_category,
'a_get_all_product': self.get_all_product,
'a_get_inactive_product': self.get_inactive_product,
'a_get_first_product_only': self.get_first_product_only,
'a_ids_product': self.ids_product,
'a_get_all_permutation': self.get_all_permutation,
'a_get_inactive_permutation': self.get_inactive_permutation,
'a_get_first_permutation_only': self.get_first_permutation_only,
'a_ids_permutation': self.ids_permutation,
'a_get_all_image': self.get_all_image,
'a_get_inactive_image': self.get_inactive_image,
'a_get_first_image_only': self.get_first_image_only,
'a_ids_image': self.ids_image,
'a_get_all_delivery_region': self.get_all_region,
'a_get_inactive_delivery_region': self.get_inactive_region,
'a_get_first_delivery_region_only': self.get_first_region_only,
'a_ids_delivery_region': self.ids_region,
'a_get_all_currency': self.get_all_currency,
'a_get_inactive_currency': self.get_inactive_currency,
'a_get_first_currency_only': self.get_first_currency_only,
'a_ids_currency': self.ids_currency,
'a_get_all_discount': self.get_all_discount,
'a_get_inactive_discount': self.get_inactive_discount,
'a_ids_discount': self.ids_discount,
'a_get_products_quantity_stock_below_min': self.get_products_quantity_stock_below_min
}
@staticmethod
def from_form(form):
# if not (form is Form_Filters_Permutation): raise ValueError(f'Invalid form type: {type(form)}')
av.val_instance(form, 'form', 'Product_Filters.from_form', Form_Filters_Permutation)
has_category_filter = not (form.id_category.data == '0' or form.id_category.data == '')
has_product_filter = not (form.id_product.data == '0' or form.id_product.data == '')
get_permutations_stock_below_min = av.input_bool(form.is_out_of_stock.data, "is_out_of_stock", "Product_Filters.from_form")
print(f'form question: {type(form.is_out_of_stock)}\nbool interpretted: {get_permutations_stock_below_min}\type form: {type(form)}')
return Product_Filters(
get_all_category = not has_category_filter,
get_inactive_category = False,
get_first_category_only = False,
ids_category = form.id_category.data,
get_all_product = not has_product_filter,
get_inactive_product = False,
get_first_product_only = False,
ids_product = form.id_product.data,
get_all_permutation = not get_permutations_stock_below_min,
get_inactive_permutation = False,
get_first_permutation_only = False,
ids_permutation = '',
get_all_image = False,
get_inactive_image = False,
get_first_image_only = False,
ids_image = '',
get_all_region = False,
get_inactive_region = False,
get_first_region_only = False,
ids_region = '',
get_all_currency = False,
get_inactive_currency = False,
get_first_currency_only = False,
ids_currency = '',
get_all_discount = False,
get_inactive_discount = False,
ids_discount = '',
get_products_quantity_stock_below_min = get_permutations_stock_below_min
)
@staticmethod
def get_default():
return Product_Filters(
get_all_category = True,
get_inactive_category = False,
get_first_category_only = False,
ids_category = '',
get_all_product = True,
get_inactive_product = False,
get_first_product_only = False,
ids_product = '',
get_all_permutation = True,
get_inactive_permutation = False,
get_first_permutation_only = False,
ids_permutation = '',
get_all_image = True,
get_inactive_image = False,
get_first_image_only = False,
ids_image = '',
get_all_region = True,
get_inactive_region = False,
get_first_region_only = False,
ids_region = '',
get_all_currency = True,
get_inactive_currency = False,
get_first_currency_only = False,
ids_currency = '',
get_all_discount = True,
get_inactive_discount = False,
ids_discount = '',
get_products_quantity_stock_below_min = True
)

View File

@@ -55,7 +55,7 @@ class SQL_Error(db.Model):
super().__init__()
"""
def make_from_DB_record(record):
def from_DB_record(record):
error = SQL_Error()
error.display_order = record[0]
error.code = record[1]

View File

@@ -19,9 +19,9 @@ Business object for basket
# 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 business_objects.store.product import Product #, Product_Filters
from business_objects.store.discount import Discount
from business_objects.store.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
@@ -49,9 +49,9 @@ class Basket_Item():
self.is_available = True
"""
def make_from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
def from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
# Initialiser - validation
_m = 'Basket_Item.make_from_product_and_quantity'
_m = 'Basket_Item.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)

View File

@@ -10,26 +10,10 @@ 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 extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
from typing import ClassVar
# CLASSES
"""
@@ -56,15 +40,22 @@ class Currency_Enum(Enum):
"""
class Currency(db.Model):
ATTR_ID_CURRENCY: ClassVar[str] = 'id-currency'
FLAG_CODE: ClassVar[str] = 'code-currency'
FLAG_NAME: ClassVar[str] = 'name-currency'
FLAG_SYMBOL: ClassVar[str] = 'symbol-currency'
FLAG_FACTOR_FROM_GBP: ClassVar[str] = 'factor-from-GBP-currency'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-currency'
id_currency = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String)
name = db.Column(db.String)
symbol = db.Column(db.String)
code = db.Column(db.String(50))
name = db.Column(db.String(255))
symbol = db.Column(db.String(50))
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'
def from_DB_currency(query_row):
# _m = 'Currency.from_DB_currency'
# v_arg_type = 'class attribute'
currency = Currency()
currency.id_currency = query_row[0]
@@ -75,8 +66,8 @@ class Currency(db.Model):
currency.display_order = query_row[5]
return currency
"""
def make_from_DB_product(query_row):
_m = 'Currency.make_from_DB_product'
def from_DB_product(query_row):
_m = 'Currency.from_DB_product'
v_arg_type = 'class attribute'
currency = Currency()
currency.id_permutation = query_row[0]
@@ -93,4 +84,23 @@ class Currency(db.Model):
symbol: {self.symbol}
factor from GBP: {self.factor_from_GBP}
display_order: {self.display_order}
'''
'''
def to_json(self):
return {
Currency.ATTR_ID_CURRENCY: self.id_currency,
Currency.FLAG_CODE: self.code,
Currency.FLAG_NAME: self.name,
Currency.FLAG_SYMBOL: self.symbol,
Currency.FLAG_FACTOR_FROM_GBP: self.factor_from_GBP,
Currency.FLAG_DISPLAY_ORDER: self.display_order
}
@staticmethod
def from_json(json_currency):
currency = Currency()
currency.id_currency = json_currency[Currency.ATTR_ID_CURRENCY]
currency.code = json_currency[Currency.FLAG_CODE]
currency.name = json_currency[Currency.FLAG_NAME]
currency.symbol = json_currency[Currency.FLAG_SYMBOL]
currency.factor_from_GBP = json_currency[Currency.FLAG_FACTOR_FROM_GBP]
currency.display_order = json_currency[Currency.FLAG_DISPLAY_ORDER]
return currency

View File

@@ -10,25 +10,8 @@ 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()
from extensions import db
# CLASSES
@@ -80,7 +63,7 @@ class Delivery_Option(db.Model):
display_order = db.Column(db.Integer)
def __init__(self):
self.delivery_regions = []
def make_from_DB_product(query_row):
def from_DB_product(query_row):
option = Delivery_Option()
option.id_option = query_row[0]
option.id_product = query_row[1]

View File

@@ -10,32 +10,23 @@ 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
from extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from typing import ClassVar
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Enum_Delivery_Region(Enum):
UK = 0
class Delivery_Region(db.Model):
ATTR_ID_REGION: ClassVar[str] = 'id-region'
FLAG_CODE: ClassVar[str] = 'code-region'
FLAG_NAME: ClassVar[str] = 'name-region'
FLAG_ACTIVE: ClassVar[str] = 'active-region'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-region'
id_region = db.Column(db.Integer, primary_key=True)
"""
id_category = db.Column(db.Integer)
@@ -68,14 +59,14 @@ class Delivery_Region(db.Model):
self.code = code
self.display_order = display_order
"""
def make_from_DB_product(query_row):
def 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):
def from_DB_region(query_row):
region = Delivery_Region()
region.id_region = query_row[0]
region.code = query_row[1]
@@ -91,6 +82,20 @@ class Delivery_Region(db.Model):
active: {self.active}
display_order: {self.display_order}
'''
def to_json(self):
return {
Delivery_Region.ATTR_ID_REGION: self.id_region,
Delivery_Region.FLAG_CODE: self.code,
Delivery_Region.FLAG_NAME: self.name,
Delivery_Region.FLAG_ACTIVE: self.active,
Delivery_Region.FLAG_DISPLAY_ORDER: self.display_order
}
@staticmethod
def from_json(json_region):
region = Delivery_Region()
region.id_region = json_region[Delivery_Region.ATTR_ID_REGION]
region.code = json_region[Delivery_Region.FLAG_CODE]
region.name = json_region[Delivery_Region.FLAG_NAME]
region.active = json_region[Delivery_Region.FLAG_ACTIVE]
region.display_order = json_region[Delivery_Region.FLAG_DISPLAY_ORDER]
return region

View File

@@ -10,25 +10,8 @@ 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()
from extensions import db
# CLASSES
@@ -54,7 +37,7 @@ class Discount(db.Model):
def __init__(self):
self.delivery_regions = []
def make_from_DB_product(query_row):
def from_DB_product(query_row):
discount = Discount()
discount.id_discount = query_row[0]
discount.id_category = query_row[1]

View File

@@ -10,28 +10,13 @@ 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
from extensions import db
# 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
@@ -88,8 +73,8 @@ class Image(db.Model):
self.display_order = display_order
super().__init__()
"""
def make_from_DB_product(query_row):
_m = 'Image.make_from_DB_product'
def from_DB_product(query_row):
_m = 'Image.from_DB_product'
# print(f'image: {query_row}')
image = Image()
image.id_image = query_row[0]

View File

@@ -10,17 +10,11 @@ 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 business_objects.store.product import Product
from business_objects.store.delivery_option import Delivery_Option
# from forms import Form_Product
# from models.model_view_store import Model_View_Store # circular
# external

View File

@@ -0,0 +1,454 @@
"""
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
"""
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit, Form_Filters_Permutation
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.discount import Discount
from business_objects.store.image import Image
from business_objects.store.product_permutation import Product_Permutation
from business_objects.store.product_price import Product_Price
from business_objects.store.store_base import Store_Base
from business_objects.store.stock_item import Stock_Item
from business_objects.store.product_variation import Product_Variation
from business_objects.store.product_variation_tree import Product_Variation_Tree
from extensions import db
# external
from dataclasses import dataclass
from typing import ClassVar, List
"""
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 Product(db.Model, Store_Base):
FLAG_NAME: ClassVar[str] = 'name-product'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-product'
FLAG_CAN_VIEW: ClassVar[str] = 'can-view-product'
FLAG_CAN_EDIT: ClassVar[str] = 'can-edit-product'
FLAG_CAN_ADMIN: ClassVar[str] = 'can-admin-product'
FLAG_HAS_VARIATIONS: ClassVar[str] = 'has-variations-product'
FLAG_INDEX_PERMUTATION_SELECTED: ClassVar[str] = 'index-permutation-selected'
FLAG_VARIATION_TREES: ClassVar[str] = 'variation-trees'
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__()
Store_Base.__init__(self)
self.form_basket_add = Form_Basket_Add()
self.form_basket_edit = Form_Basket_Edit()
def from_DB_product(query_row):
_m = 'Product.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[22]
product.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
product.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
product.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
return product
"""
def from_permutation(permutation, has_variations = False):
_m = 'Product.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 from_permutations(permutations):
_m = 'Product.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):
self.variation_trees = []
for index_permutation in range(len(self.permutations)):
variation_tree = Product_Variation_Tree.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 from_DB_Stripe_product(query_row):
permutation = Product_Permutation.from_DB_Stripe_product(query_row)
product = Product.from_permutation(permutation)
return product
def from_DB_Stripe_price(query_row):
permutation = Product_Permutation.from_DB_Stripe_price(query_row)
product = Product.from_permutation(permutation)
return product
def from_json(json_basket_item, key_id_product, key_id_permutation):
permutation = Product_Permutation.from_json(json_basket_item, key_id_product, key_id_permutation)
product = Product.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', Product_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', Product_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)
def add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Product.add_stock_item', Stock_Item)
index_permutation = self.permutation_index[stock_item.id_permutation]
self.permutations[index_permutation].add_stock_item(stock_item)
def has_permutations(self):
return len(self.permutations) > 0
def is_available(self):
if len(self.permutations) == 0:
return False
for permutation in self.permutations:
if permutation.is_available():
return True
return False
def to_list_rows_permutation(self):
list_rows = []
for permutation in self.permutations:
list_rows.append(permutation.to_row_permutation())
return list_rows
@classmethod
def from_json(cls, json):
product = cls()
product.id_product = json[cls.ATTR_ID_PRODUCT]
product.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
product.name = json[cls.FLAG_NAME]
product.display_order = json[cls.FLAG_DISPLAY_ORDER]
product.can_view = json[cls.FLAG_CAN_VIEW]
product.can_edit = json[cls.FLAG_CAN_EDIT]
product.can_admin = json[cls.FLAG_CAN_ADMIN]
product.has_variations = json[cls.FLAG_HAS_VARIATIONS]
product.index_permutation_selected = json[cls.FLAG_INDEX_PERMUTATION_SELECTED]
product.permutations = []
for json_permutation in json[cls.ATTR_ID_PRODUCT_PERMUTATION]:
product.permutations.append(Product_Permutation.from_json(json_permutation))
product.variation_trees = []
for json_tree in json[cls.FLAG_VARIATION_TREES]:
product.variation_trees.append(Product_Variation_Tree.from_json(json_tree))
return product
def to_json(self):
return {
self.ATTR_ID_PRODUCT: self.id_product,
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
self.FLAG_NAME: self.name,
self.FLAG_DISPLAY_ORDER: self.display_order,
self.FLAG_CAN_VIEW: self.can_view,
self.FLAG_CAN_EDIT: self.can_edit,
self.FLAG_CAN_ADMIN: self.can_admin,
self.FLAG_HAS_VARIATIONS: self.has_variations,
self.FLAG_INDEX_PERMUTATION_SELECTED: self.index_permutation_selected,
self.ATTR_ID_PRODUCT_PERMUTATION: [permutation.to_json() for permutation in self.permutations],
self.FLAG_VARIATION_TREES: [tree.to_json() for tree in self.variation_trees]
}
@dataclass
class Product_Filters():
# id_user: str
get_all_category: bool
get_inactive_category: bool
# get_first_category_only: bool
ids_category: str
get_all_product: bool
get_inactive_product: bool
# get_first_product_only: bool
ids_product: str
get_all_permutation: bool
get_inactive_permutation: bool
# get_first_permutation_only: bool
ids_permutation: str
get_all_image: bool
get_inactive_image: bool
# get_first_image_only: bool
ids_image: str
"""
get_all_region: bool
get_inactive_region: bool
get_first_region_only: bool
ids_region: str
get_all_currency: bool
get_inactive_currency: bool
get_first_currency_only: bool
ids_currency: str
get_all_discount: bool
get_inactive_discount: bool
ids_discount: str
"""
get_products_quantity_stock_below_min: bool
def to_json(self):
return {
'a_id_user': None,
'a_get_all_category': self.get_all_category,
'a_get_inactive_category': self.get_inactive_category,
# 'a_get_first_category_only': self.get_first_category_only,
'a_ids_category': self.ids_category,
'a_get_all_product': self.get_all_product,
'a_get_inactive_product': self.get_inactive_product,
# 'a_get_first_product_only': self.get_first_product_only,
'a_ids_product': self.ids_product,
'a_get_all_permutation': self.get_all_permutation,
'a_get_inactive_permutation': self.get_inactive_permutation,
# 'a_get_first_permutation_only': self.get_first_permutation_only,
'a_ids_permutation': self.ids_permutation,
'a_get_all_image': self.get_all_image,
'a_get_inactive_image': self.get_inactive_image,
# 'a_get_first_image_only': self.get_first_image_only,
'a_ids_image': self.ids_image,
# 'a_get_all_delivery_region': self.get_all_region,
# 'a_get_inactive_delivery_region': self.get_inactive_region,
# 'a_get_first_delivery_region_only': self.get_first_region_only,
# 'a_ids_delivery_region': self.ids_region,
# 'a_get_all_currency': self.get_all_currency,
# 'a_get_inactive_currency': self.get_inactive_currency,
# 'a_get_first_currency_only': self.get_first_currency_only,
# 'a_ids_currency': self.ids_currency,
# 'a_get_all_discount': self.get_all_discount,
# 'a_get_inactive_discount': self.get_inactive_discount,
# 'a_ids_discount': self.ids_discount,
'a_get_products_quantity_stock_below_min': self.get_products_quantity_stock_below_min
}
@staticmethod
def from_form(form):
# if not (form is Form_Filters_Permutation): raise ValueError(f'Invalid form type: {type(form)}')
av.val_instance(form, 'form', 'Product_Filters.from_form', Form_Filters_Permutation)
has_category_filter = not (form.id_category.data == '0' or form.id_category.data == '')
has_product_filter = not (form.id_product.data == '0' or form.id_product.data == '')
get_permutations_stock_below_min = av.input_bool(form.is_out_of_stock.data, "is_out_of_stock", "Product_Filters.from_form")
print(f'form question: {type(form.is_out_of_stock)}\nbool interpretted: {get_permutations_stock_below_min}\type form: {type(form)}')
return Product_Filters(
get_all_category = not has_category_filter,
get_inactive_category = False,
# get_first_category_only = False,
ids_category = form.id_category.data,
get_all_product = not has_product_filter,
get_inactive_product = False,
# get_first_product_only = False,
ids_product = form.id_product.data,
get_all_permutation = not get_permutations_stock_below_min,
get_inactive_permutation = False,
# get_first_permutation_only = False,
ids_permutation = '',
get_all_image = False,
get_inactive_image = False,
# get_first_image_only = False,
ids_image = '',
# get_all_region = False,
# get_inactive_region = False,
# get_first_region_only = False,
# ids_region = '',
# get_all_currency = False,
# get_inactive_currency = False,
# get_first_currency_only = False,
# ids_currency = '',
# get_all_discount = False,
# get_inactive_discount = False,
# ids_discount = '',
get_products_quantity_stock_below_min = get_permutations_stock_below_min
)
@staticmethod
def get_default():
return Product_Filters(
get_all_category = True,
get_inactive_category = False,
# get_first_category_only = False,
ids_category = '',
get_all_product = True,
get_inactive_product = False,
# get_first_product_only = False,
ids_product = '',
get_all_permutation = True,
get_inactive_permutation = False,
# get_first_permutation_only = False,
ids_permutation = '',
get_all_image = True,
get_inactive_image = False,
# get_first_image_only = False,
ids_image = '',
# get_all_region = True,
# et_inactive_region = False,
# get_first_region_only = False,
# ids_region = '',
# get_all_currency = True,
# get_inactive_currency = False,
# get_first_currency_only = False,
# ids_currency = '',
# get_all_discount = True,
# get_inactive_discount = False,
# ids_discount = '',
get_products_quantity_stock_below_min = True
)

View File

@@ -18,26 +18,19 @@ Business object for product
# 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
from business_objects.stock_item import Stock_Item
from business_objects.store.product import Product, Product_Permutation, Product_Price
from business_objects.store.product_variation import Product_Variation
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.discount import Discount
from business_objects.store.stock_item import Stock_Item
from business_objects.store.store_base import Store_Base
from extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from pydantic import BaseModel
from typing import ClassVar
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
"""
class Enum_Product_Category(Enum):
ASSISTIVE_DEVICES = 0
HOME_DECOR = 1
@@ -58,10 +51,16 @@ class Enum_Product_Category(Enum):
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 Product_Category(db.Model, Store_Base):
ATTR_CODE_CATEGORY: ClassVar[str] = 'code-category'
ATTR_NAME_CATEGORY: ClassVar[str] = 'name-category'
ATTR_DESCRIPTION_CATEGORY: ClassVar[str] = 'description-category'
ATTR_DISPLAY_ORDER_CATEGORY: ClassVar[str] = 'display-order-category'
class Category(db.Model):
id_category = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(50))
name = db.Column(db.String(255))
description = db.Column(db.String(4000))
display_order = db.Column(db.Integer)
@@ -85,9 +84,9 @@ class Category(db.Model):
self.products = []
self.product_index = {}
super().__init__()
def make_from_DB_product(query_row):
category = Category()
Store_Base.__init__(self)
def from_DB_product(query_row):
category = Product_Category()
category.id_category = query_row[0]
category.name = query_row[1]
category.description = query_row[2]
@@ -115,7 +114,6 @@ class Category(db.Model):
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)
@@ -136,11 +134,11 @@ class Category(db.Model):
# 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)
av.val_instance(variation, 'variation', 'Category.add_variation', Product_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)
av.val_instance(price, 'price', 'Category.add_price', Product_Price)
index_product = self.get_index_product_from_id(price.id_product)
self.products[index_product].add_price(price)
def add_image(self, image):
@@ -155,18 +153,15 @@ class Category(db.Model):
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 add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Category.add_stock_item', Stock_Item)
index_product = self.get_index_product_from_id(stock_item.id_product)
self.products[index_product].add_stock_item(stock_item)
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)
@@ -185,12 +180,10 @@ class Category(db.Model):
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()
def is_available(self):
if len(self.products) == 0:
return False
@@ -198,52 +191,68 @@ class Category(db.Model):
if product.is_available():
return True
return False
def to_list_rows_permutation(self):
list_rows = []
for product in self.products:
list_rows += product.to_list_rows_permutation()
return list_rows
def to_list_products(self):
list_products = []
for product in self.products:
list_products.append({'value': product.id_product, 'text': product.name})
return list_products
def to_json(self):
return {
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
self.ATTR_CODE_CATEGORY: self.code,
self.ATTR_NAME_CATEGORY: self.name,
self.ATTR_DESCRIPTION_CATEGORY: self.description,
self.ATTR_DISPLAY_ORDER_CATEGORY: self.display_order
}
@classmethod
def from_json(cls, json):
category = cls()
category.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY],
category.code = json[cls.ATTR_CODE_CATEGORY],
category.name = json[cls.ATTR_NAME_CATEGORY],
category.description = json[cls.ATTR_DESCRIPTION_CATEGORY],
category.display_order = json[cls.ATTR_DISPLAY_ORDER_CATEGORY]
return category
class Product_Category_Filters():
category_ids: str # csv
product_ids: str # csv
class Filters_Product_Category(BaseModel, Store_Base):
ids_product_category: str
ids_product: str
"""
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):
return super(Filters_Product_Category, cls).__new__(cls)
"""
def __init__(self, ids_product, ids_product_category):
super().__init__(ids_product=ids_product, ids_product_category=ids_product_category)
"""
# Constructor
self.ids = product_ids
self.categories = product_categories
"""
class Category_List():
class Container_Product_Category(Store_Base):
categories: list
def __init__(self):
self.categories = []
def add_category(self, category):
av.val_instance(category, 'category', 'Category_List.add_category', Category)
av.val_instance(category, 'category', 'Container_Product_Categories.add_category', Product_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")
raise ValueError(f"{av.error_msg_str(id_category, 'id_category', 'Container_Product_Categories.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]
@@ -252,77 +261,68 @@ class Category_List():
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")
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Container_Product_Categories.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)
av.val_instance(product, 'product', 'Container_Product_Categories.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)
av.val_instance(permutation, 'permutation', 'Container_Product_Categories.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)
av.val_instance(variation, 'variation', 'Container_Product_Categories.add_variation', Product_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)
av.val_instance(price, 'price', 'Container_Product_Categories.add_price', Product_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)
av.val_instance(image, 'image', 'Container_Product_Categories.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)
av.val_instance(delivery_option, 'delivery_option', 'Container_Product_Categories.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)
av.val_instance(discount, 'discount', 'Container_Product_Categories.add_discount', Discount)
index_category = self.get_index_category_from_id(discount.id_category)
self.categories[index_category].add_discount(discount)
def add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Category.add_stock_item', Stock_Item)
av.val_instance(stock_item, 'stock_item', 'Container_Product_Categories.add_stock_item', Stock_Item)
index_category = self.get_index_category_from_id(stock_item.id_category)
self.categories[index_category].add_stock_item(stock_item)
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)
def to_list_rows_permutation(self):
list_rows = []
for category in self.categories:
list_rows += category.to_list_rows_permutation()
return list_rows
def to_list_categories(self):
list_categories = []
for category in self.categories:
list_categories.append({'value': category.id_category, 'text': category.name})
return list_categories
def to_list_products(self):
list_products = []
for category in self.categories:
# list_products.append(category.to_list_products())
for product in category.products:
list_products.append({'value': product.id_product, 'text': product.name, Product.ATTR_ID_CATEGORY: product.id_category})
list_products.append({'value': product.id_product, 'text': product.name, Product.ATTR_ID_PRODUCT_CATEGORY: product.id_category})
return list_products
def to_dict_lists_products(self):
dict_lists_products = {}
for category in self.categories:

View File

@@ -0,0 +1,365 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Permutation Business Object
Description:
Business object for product permutation
"""
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit, Form_Filters_Permutation
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.discount import Discount
from business_objects.store.image import Image
from business_objects.store.product_price import Product_Price
from business_objects.store.stock_item import Stock_Item
from business_objects.store.store_base import Store_Base
from business_objects.store.product_variation import Product_Variation
from extensions import db
# external
from datetime import datetime, timedelta
import locale
from dataclasses import dataclass
class Product_Permutation(db.Model, Store_Base):
FLAG_QUANTITY_STOCK = 'quantity-stock'
FLAG_QUANTITY_MIN = 'quantity-min'
FLAG_QUANTITY_MAX = 'quantity-max'
FLAG_COST_LOCAL = 'cost-local'
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)
id_currency_cost = db.Column(db.Integer)
code_currency_cost = db.Column(db.String(3))
symbol_currency_cost = db.Column(db.String(3))
cost_local = 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 = {}
self.stock_items = []
self.stock_item_index = {}
super().__init__()
Store_Base.__init__(self)
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 = False
def from_DB_product(query_row):
_m = 'Product_Permutation.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.id_currency_cost = query_row[7]
permutation.code_currency_cost = query_row[8]
permutation.symbol_currency_cost = query_row[9]
permutation.cost_local = query_row[6]
permutation.has_variations = query_row[4]
permutation.id_category = query_row[5]
permutation.latency_manufacture = query_row[11]
permutation.quantity_min = query_row[12]
permutation.quantity_max = query_row[13]
permutation.quantity_step = query_row[14]
permutation.quantity_stock = query_row[15]
permutation.id_stripe_product = query_row[16]
permutation.is_subscription = av.input_bool(query_row[17], "is_subscription", _m, v_arg_type=v_arg_type)
permutation.name_recurrence_interval = query_row[18]
permutation.name_plural_recurrence_interval = query_row[19]
permutation.count_recurrence_interval = query_row[20]
permutation.display_order = query_row[23]
permutation.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
permutation.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
permutation.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
return permutation
def from_DB_Stripe_product(query_row):
_m = 'Product_Permutation.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 from_DB_Stripe_price(query_row):
_m = 'Product_Permutation.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 from_json(json_basket_item, key_id_product, key_id_permutation):
_m = 'Product_Permutation.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
"""
@classmethod
def from_json(cls, json):
permutation = cls()
permutation.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
permutation.id_product = json[cls.ATTR_ID_PRODUCT]
permutation.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
permutation.has_variations = len(json[cls.ATTR_ID_PRODUCT_VARIATION]) > 0
if permutation.has_variations:
for jsonProductVariation in json[cls.ATTR_ID_PRODUCT_VARIATION]:
variation = Product_Variation.from_json(jsonProductVariation)
permutation.add_variation(variation)
permutation.quantity_stock = json[cls.FLAG_QUANTITY_STOCK]
permutation.quantity_min = json[cls.FLAG_QUANTITY_MIN]
permutation.quantity_max = json[cls.FLAG_QUANTITY_MAX]
return permutation
def to_json(self):
return {
'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}
}
def is_available(self):
return len(self.prices) > 0
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
"""
return self.code_currency_cost if self.symbol_currency_cost == '' else self.symbol_currency_cost
def output_variations(self):
if not self.has_variations: return ''
return '\n'.join([f'{variation.name_variation_type}: {variation.name_variation}' for variation in self.variations])
def output_variations_jsonify(self):
if not self.has_variations: return ''
return ','.join([f'{variation.id_type}: {variation.id_variation}' for variation in self.variations])
"""
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, Product_Variation)
try:
self.variation_index[variation.id_variation]
raise ValueError(f"{av.error_msg_str(variation, 'variation', _m, Product_Variation)}\nProduct_Variation 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, Product_Price)
try:
self.price_index[price.display_order]
raise ValueError(f"{av.error_msg_str(price, 'price', _m, Product_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 add_stock_item(self, stock_item):
av.val_instance(stock_item, 'stock_item', 'Product_Permutation.add_stock_item', Stock_Item)
self.stock_items.append(stock_item)
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
def to_row_permutation(self):
a = {
Product_Permutation.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
Product_Permutation.ATTR_ID_PRODUCT: self.id_product,
Product_Permutation.ATTR_ID_PRODUCT_VARIATION: self.output_variations(),
Product_Permutation.FLAG_QUANTITY_STOCK: self.quantity_stock,
Product_Permutation.FLAG_QUANTITY_MIN: self.quantity_min,
Product_Permutation.FLAG_QUANTITY_MAX: self.quantity_max,
Product_Permutation.FLAG_COST_LOCAL: f"<strong>{self.symbol_currency_cost}</strong>{self.cost_local}"
}
print('permutation row: ', a)
return a
"""
class Permutation_Product_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 from_DB_product(query_row):
_m = 'Permutation_Product_Variation_Link.from_DB_product'
v_arg_type = 'class attribute'
link = Permutation_Product_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
"""

View File

@@ -0,0 +1,109 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Price Business Object
Description:
Business object for product price
"""
# internal
from business_objects.store.currency import Currency
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.store_base import Store_Base
from extensions import db
# external
from dataclasses import dataclass
from typing import ClassVar
class Product_Price(db.Model, Store_Base):
ATTR_ID_PRODUCT_PRICE: ClassVar[str] = 'id-price'
FLAG_VALUE_LOCAL_VAT_INCL: ClassVar[str] = 'value-local-vat-incl'
FLAG_VALUE_LOCAL_VAT_EXCL: ClassVar[str] = 'value-local-vat-excl'
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-price'
id_price = db.Column(db.Integer, primary_key=True)
id_permutation = db.Column(db.Integer)
id_product = db.Column(db.Integer)
id_category = db.Column(db.Integer)
id_currency = db.Column(db.Integer)
code_currency = db.Column(db.String(50))
name_currency = db.Column(db.String(255))
symbol_currency = db.Column(db.String(50))
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)
def __init__(self):
super().__init__()
Store_Base.__init__(self)
def from_DB_product(query_row):
# _m = 'Product_Price.from_DB_product'
price = Product_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'''Product_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}
'''
def to_json(self):
return {
self.ATTR_ID_PRODUCT_PRICE: {self.id_price},
self.ATTR_ID_PRODUCT_PERMUTATION: {self.id_permutation},
self.ATTR_ID_PRODUCT: {self.id_product},
self.ATTR_ID_PRODUCT_CATEGORY: {self.id_category},
Currency.ATTR_ID_CURRENCY: {self.id_currency},
Currency.FLAG_CODE: {self.code_currency},
Currency.FLAG_NAME: {self.name_currency},
Currency.FLAG_SYMBOL: {self.symbol_currency},
Delivery_Region.ATTR_ID_REGION: {self.id_region},
self.FLAG_VALUE_LOCAL_VAT_INCL: {self.value_local_VAT_incl},
self.FLAG_VALUE_LOCAL_VAT_EXCL: {self.value_local_VAT_excl},
self.FLAG_DISPLAY_ORDER: {self.display_order}
}
@classmethod
def from_json(cls, json):
price = cls()
price.id_price = json[cls.ATTR_ID_PRODUCT_PRICE]
price.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
price.id_product = json[cls.ATTR_ID_PRODUCT]
price.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
price.id_currency = json[Currency.ATTR_ID_CURRENCY]
price.code_currency = json[Currency.FLAG_CODE]
price.name_currency = json[Currency.FLAG_NAME]
price.symbol_currency = json[Currency.FLAG_SYMBOL]
price.id_region = json[Delivery_Region.ATTR_ID_REGION]
price.value_local_VAT_incl = json[cls.FLAG_VALUE_LOCAL_VAT_INCL]
price.value_local_VAT_excl = json[cls.FLAG_VALUE_LOCAL_VAT_EXCL]
price.display_order = json[cls.FLAG_DISPLAY_ORDER]
return price

View File

@@ -4,7 +4,7 @@ Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Variation Business Object
Feature: Product Product_Variation Business Object
Description:
Business object for product variation
@@ -18,28 +18,16 @@ Business object for product variation
# 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.store.store_base import Store_Base
from extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from dataclasses import dataclass
from typing import ClassVar
from pydantic import BaseModel
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Variation(db.Model):
ATTR_ID_CATEGORY: ClassVar[str] = 'id_category'
ATTR_ID_PERMUTATION: ClassVar[str] = 'id_permutation'
ATTR_ID_PRODUCT: ClassVar[str] = 'id_product'
ATTR_ID_VARIATION: ClassVar[str] = 'id_variation'
ATTR_ID_VARIATION_TYPE: ClassVar[str] = 'id_variation_type'
class Product_Variation(db.Model, Store_Base):
KEY_ACTIVE_VARIATION: ClassVar[str] = 'active_variation'
KEY_ACTIVE_VARIATION_TYPE: ClassVar[str] = 'active_variation_type'
KEY_CODE_VARIATION: ClassVar[str] = 'code_variation'
@@ -64,41 +52,20 @@ class Variation(db.Model):
id_permutation = db.Column(db.Integer)
id_category = 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
def __init__(self):
super().__init__()
"""
def make_from_DB_product(query_row):
variation = Variation.make_from_DB_variation(query_row)
Store_Base.__init__(self)
def from_DB_product(query_row):
variation = Product_Variation.from_DB_variation(query_row)
variation.id_product = query_row[11]
variation.id_permutation = query_row[12]
variation.id_category = query_row[13]
return variation
def make_from_DB_variation(query_row):
_m = 'Variation.make_from_DB_variation'
variation = Variation()
def from_DB_variation(query_row):
_m = 'Product_Variation.from_DB_variation'
variation = Product_Variation()
variation.id_variation = query_row[0]
variation.code_variation = query_row[1]
variation.name_variation = query_row[2]
@@ -112,14 +79,15 @@ class Variation(db.Model):
variation.display_order_variation_type = query_row[10]
return variation
def from_json(json):
variation = Variation()
variation.id_variation = json[Variation.ATTR_ID_VARIATION]
variation.id_product = json[Variation.ATTR_ID_PRODUCT]
variation.id_permutation = json[Variation.ATTR_ID_PERMUTATION]
variation.id_category = json[Variation.ATTR_ID_CATEGORY]
variation.name_variation_type = json[Variation.KEY_NAME_VARIATION_TYPE]
variation.name_variation = json[Variation.KEY_NAME_VARIATION]
@classmethod
def from_json(cls, json):
variation = cls()
variation.id_variation = json[cls.ATTR_ID_VARIATION]
variation.id_product = json[cls.ATTR_ID_PRODUCT]
variation.id_permutation = json[cls.ATTR_ID_PERMUTATION]
variation.id_category = json[cls.ATTR_ID_CATEGORY]
variation.name_variation_type = json[cls.KEY_NAME_VARIATION_TYPE]
variation.name_variation = json[cls.KEY_NAME_VARIATION]
return variation
def __repr__(self):
@@ -140,32 +108,32 @@ class Variation(db.Model):
def to_json(self):
return {
Variation.ATTR_ID_VARIATION: self.id_variation,
Variation.ATTR_ID_PRODUCT: self.id_product,
Variation.ATTR_ID_PERMUTATION: self.id_permutation,
Variation.ATTR_ID_CATEGORY: self.id_category,
Variation.ATTR_ID_VARIATION_TYPE: self.id_type,
Variation.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
Variation.KEY_CODE_VARIATION: self.code_variation,
Variation.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
Variation.KEY_DISPLAY_ORDER_VARIATION: self.display_order_variation,
Variation.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
Variation.KEY_NAME_VARIATION: self.name_variation,
Variation.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
Variation.KEY_ACTIVE_VARIATION: self.active_variation,
self.ATTR_ID_PRODUCT_VARIATION: self.id_variation,
self.ATTR_ID_PRODUCT: self.id_product,
self.ATTR_ID_PRODUCT_PERMUTATION: self.id_permutation,
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
self.ATTR_ID_PRODUCT_VARIATION_TYPE: self.id_type,
self.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
self.KEY_CODE_VARIATION: self.code_variation,
self.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
self.KEY_DISPLAY_ORDER_VARIATION: self.display_order_variation,
self.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
self.KEY_NAME_VARIATION: self.name_variation,
self.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
self.KEY_ACTIVE_VARIATION: self.active_variation,
}
def to_json_variation_type(self):
return {
Variation.ATTR_ID_VARIATION_TYPE: self.id_type,
Variation.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
Variation.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
Variation.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
Variation.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
self.ATTR_ID_PRODUCT_VARIATION_TYPE: self.id_type,
self.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
self.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
self.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
self.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
}
@dataclass
class Variation_Filters():
class Product_Variation_Filters():
get_all_variation_type: bool
get_inactive_variation_type: bool
get_first_variation_type: bool
@@ -190,7 +158,7 @@ class Variation_Filters():
"""
@staticmethod
def from_form(form):
av.val_instance(form, 'form', 'User_Filters.from_form', Form_Filters_Variation)
av.val_instance(form, 'form', 'User_Filters.from_form', Form_Filters_Product_Variation)
get_inactive = av.input_bool(form.active_only.data, "active_only", "User_Filters.from_form")
id_user = form.id_user.data
return User_Filters(
@@ -215,7 +183,7 @@ class Variation_Filters():
@staticmethod
def get_default():
return Variation_Filters(
return Product_Variation_Filters(
get_all_variation_type = True,
get_inactive_variation_type = False,
get_first_variation_type = False,
@@ -226,11 +194,11 @@ class Variation_Filters():
ids_variation = ''
)
class Variation_List(BaseModel):
class Product_Variation_List(BaseModel):
variations: list = []
def add_variation(self, variation):
av.val_instance(variation, 'variation', 'Variation_List.add_variation', Variation)
av.val_instance(variation, 'variation', 'Product_Variation_List.add_variation', Product_Variation)
self.variations.append(variation)
def __repr__(self):

View File

@@ -0,0 +1,73 @@
"""
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
"""
# internal
from business_objects.store.product_variation import Product_Variation
from extensions import db
# external
class Product_Variation_Tree_Node():
variation: Product_Variation
node_parent: None
nodes_child: list
def __init__(self):
self.nodes_child = []
def from_variation_and_node_parent(variation, node_parent):
node = Product_Variation_Tree_Node()
node.variation = variation
node.node_parent = node_parent
return node
def from_node_parent(node_parent):
node = Product_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 Product_Variation_Tree():
node_root: Product_Variation_Tree_Node
def from_node_root(node_root):
tree = Product_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 from_product_permutation(product_permutation):
depth_max = len(product_permutation.variations)
node_root = Product_Variation_Tree_Node.from_variation_and_node_parent(product_permutation.variations[0], None)
node = node_root
for depth in range(depth_max - 1):
node = Product_Variation_Tree_Node.from_variation_and_node_parent(product_permutation.variations[depth + 1], node)
return Product_Variation_Tree.from_node_root(node_root)

View File

@@ -12,21 +12,27 @@ Feature: Stock Item Business Object
import lib.argument_validation as av
from lib import data_types
from forms import Form_Filters_Stock_Item
from business_objects.store.product_price import Product_Price
# from business_objects.discount import Discount
from business_objects.store.store_base import Store_Base
from extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from dataclasses import dataclass
from typing import ClassVar
from datetime import datetime
db = SQLAlchemy()
class Stock_Item(db.Model):
ATTR_ID_STOCK_ITEM: ClassVar[str] = 'id_stock_item'
class Stock_Item(db.Model, Store_Base):
ATTR_ID_CURRENCY_COST: ClassVar[str] = f'{Store_Base.ATTR_ID_CURRENCY}-cost'
FLAG_DATE_CONSUMED: ClassVar[str] = 'date-consumed'
FLAG_DATE_EXPIRATION: ClassVar[str] = 'date-expiration'
FLAG_DATE_PURCHASED: ClassVar[str] = 'date-purchased'
FLAG_DATE_RECEIVED: ClassVar[str] = 'date-received'
FLAG_DATE_UNSEALED: ClassVar[str] = 'date-unsealed'
FLAG_IS_CONSUMED: ClassVar[str] = 'is-consumed'
FLAG_IS_SEALED: ClassVar[str] = 'is-sealed'
FLAG_VALUE_LOCAL_VAT_EXCL_COST: ClassVar[str] = f'{Product_Price.FLAG_VALUE_LOCAL_VAT_EXCL}-cost'
FLAG_VALUE_LOCAL_VAT_INCL_COST: ClassVar[str] = f'{Product_Price.FLAG_VALUE_LOCAL_VAT_INCL}-cost'
id_stock = db.Column(db.Integer, primary_key=True)
id_permutation = db.Column(db.Integer)
@@ -46,15 +52,18 @@ class Stock_Item(db.Model):
is_consumed = db.Column(db.Boolean)
date_consumed = db.Column(db.DateTime)
active = db.Column(db.Boolean)
"""
can_view = db.Column(db.Boolean)
can_edit = db.Column(db.Boolean)
can_admin = db.Column(db.Boolean)
"""
def __init__(self):
super().__init__()
Store_Base.__init__(self)
def make_from_DB_stock_item(query_row):
_m = 'Product.make_from_DB_stock_item'
def from_DB_stock_item(query_row):
_m = 'Product.from_DB_stock_item'
v_arg_type = 'class attribute'
stock_item = Stock_Item()
stock_item.id_stock = query_row[0]
@@ -76,15 +85,42 @@ class Stock_Item(db.Model):
stock_item.is_consumed = av.input_bool(query_row[18], "is_consumed", _m, v_arg_type=v_arg_type)
stock_item.date_consumed = query_row[19]
stock_item.active = av.input_bool(query_row[20], "active", _m, v_arg_type=v_arg_type)
"""
stock_item.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
stock_item.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
stock_item.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
"""
return stock_item
@classmethod
def from_json(cls, json):
stock_item = cls()
stock_item.id_stock = json[cls.ATTR_ID_STOCK_ITEM]
stock_item.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
stock_item.id_product = json[cls.ATTR_ID_PRODUCT]
stock_item.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
stock_item.date_purchased = json[cls.FLAG_DATE_PURCHASED]
stock_item.date_received = json[cls.FLAG_DATE_RECEIVED]
stock_item.id_location_storage = json[cls.ATTR_ID_LOCATION_STORAGE]
stock_item.id_currency_cost = json[cls.ATTR_ID_CURRENCY_COST]
stock_item.cost_local_VAT_incl = json[cls.FLAG_VALUE_LOCAL_VAT_INCL_COST]
stock_item.cost_local_VAT_excl = json[cls.FLAG_VALUE_LOCAL_VAT_EXCL_COST]
stock_item.is_sealed = json[cls.FLAG_IS_SEALED]
stock_item.date_unsealed = json[cls.FLAG_DATE_UNSEALED]
stock_item.date_expiration = json[cls.FLAG_DATE_EXPIRATION]
stock_item.is_consumed = json[cls.FLAG_IS_CONSUMED]
stock_item.date_consumed = json[cls.FLAG_DATE_CONSUMED]
stock_item.active = json[cls.FLAG_ACTIVE]
"""
stock_item.can_view = json[cls.FLAG_CAN_VIEW]
stock_item.can_edit = json[cls.FLAG_CAN_EDIT]
stock_item.can_admin = json[cls.FLAG_CAN_ADMIN]
"""
return stock_item
"""
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)
def from_json(json_basket_item, key_id_product, key_id_permutation):
permutation = Product_Permutation.from_json(json_basket_item, key_id_product, key_id_permutation)
product = Product.from_permutation(permutation)
return product
def output_lead_time(self):

View File

@@ -0,0 +1,57 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Base Store Business Object
Description:
Abstract business object for store objects
"""
# internal
# external
from typing import ClassVar
class Store_Base():
ATTR_ID_CURRENCY: ClassVar[str] = 'id-currency'
# ATTR_ID_CURRENCY_COST: ClassVar[str] = 'id-currency-cost'
ATTR_ID_DELIVERY_REGION: ClassVar[str] = 'id-delivery-region'
ATTR_ID_DISCOUNT: ClassVar[str] = 'id-discount'
ATTR_ID_IMAGE: ClassVar[str] = 'id-image'
ATTR_ID_LOCATION_STORAGE: ClassVar[str] = 'id-location-storage'
ATTR_ID_PRODUCT: ClassVar[str] = 'id-product'
ATTR_ID_PRODUCT_CATEGORY: ClassVar[str] = 'id-category'
ATTR_ID_PRODUCT_PERMUTATION: ClassVar[str] = 'id-permutation'
ATTR_ID_PRODUCT_PRICE: ClassVar[str] = 'id-price'
ATTR_ID_PRODUCT_VARIATION: ClassVar[str] = 'id-variation'
ATTR_ID_PRODUCT_VARIATION_TYPE: ClassVar[str] = 'id_variation_type'
ATTR_ID_STOCK_ITEM: ClassVar[str] = 'id-stock-item'
FLAG_ACTIVE: ClassVar[str] = 'active'
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for name, value in vars(Store_Base).items():
if getattr(value, "__isabstractmethod__", False):
if name not in cls.__dict__:
raise TypeError(f"Can't instantiate class {cls.__name__} "
f"without implementation of abstract method {name}")
subclass_value = cls.__dict__[name]
if (isinstance(value, (staticmethod, classmethod)) and
not isinstance(subclass_value, type(value))):
raise TypeError(f"Abstract {type(value).__name__} {name} in {cls.__name__} "
f"must be implemented as a {type(value).__name__}")
def __new__(cls, *args, **kwargs):
if cls is Store_Base:
raise TypeError("Can't instantiate abstract class Store_Base directly")
return super().__new__(cls)
def __repr__(self):
pass
@classmethod
def from_json(cls, json):
pass
def to_json(self):
pass

View File

@@ -14,14 +14,11 @@ Business objects for Stripe
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
from extensions import db
# 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)

View File

@@ -9,26 +9,13 @@ Feature: User Business Object
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Filters_User
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
from business_objects.stock_item import Stock_Item
from extensions import db
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from dataclasses import dataclass
from typing import ClassVar
db = SQLAlchemy()
class User(db.Model):
KEY_USER: ClassVar[str] = 'authorisedUser' # 'user' already used
@@ -50,8 +37,8 @@ class User(db.Model):
def __init__(self):
self.is_logged_in = False
def make_from_DB_user(query_row):
_m = 'User.make_from_DB_user'
def from_DB_user(query_row):
_m = 'User.from_DB_user'
user = User()
user.id_user = query_row[0]
user.id_user_auth0 = query_row[1]
@@ -259,7 +246,7 @@ class User_Permission_Evaluation(db.Model):
can_edit = db.Column(db.Boolean)
can_admin = db.Column(db.Boolean)
def make_from_DB_user_eval(query_row):
def from_DB_user_eval(query_row):
user_permission_evaluation = User_Permission_Evaluation()
user_permission_evaluation.id_evaluation = query_row[0]
user_permission_evaluation.guid = query_row[1]

View File

@@ -12,9 +12,16 @@ Configuration variables
# IMPORTS
import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
# CLASSES
class Config:
# Miscellaneous
DEBUG = False
TESTING = False
URL_HOST = os.getenv('URL_HOST')
SECRET_KEY = os.getenv('KEY_SECRET_FLASK') # gen cmd: openssl rand -hex 32
# Add other configuration variables as needed
# MySQL
@@ -45,6 +52,7 @@ class Config:
# id_currency = 1
# id_region_delivery = 1
# Mail
MAIL_DEBUG = True
MAIL_SERVER = 'mail.partsltd.co.uk' # 'smtp.gmail.com'
MAIL_PORT = 465 # 587
MAIL_USE_TLS = False
@@ -56,10 +64,6 @@ class Config:
# Recaptcha
RECAPTCHA_PUBLIC_KEY = os.getenv('RECAPTCHA_PUBLIC_KEY')
RECAPTCHA_PRIVATE_KEY = os.getenv('RECAPTCHA_PRIVATE_KEY')
# Miscellaneous
DEBUG = False
TESTING = False
URL_HOST = os.getenv('URL_HOST')
class DevelopmentConfig(Config):
DEBUG = True
@@ -79,7 +83,7 @@ elif config_env == 'production':
else:
raise ValueError("Invalid configuration environment")
# print(f'config: {app_config}\nid auth0 client: {app_config.ID_AUTH0_CLIENT}')
# environment variables
"""

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,158 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Base DataStore
Description:
Datastore for Store
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters # Permutation_Variation_Link
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Base(BaseModel):
# Global constants
# Attributes
app: Flask = None
db: SQLAlchemy = None
session: object = None
model_config = ConfigDict(arbitrary_types_allowed=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Constructor
self.db = db
self.app = current_app
with self.app.app_context():
self.session = session
def db_procedure_execute(self, proc_name, argument_dict_list = None):
# Argument validation
_m = 'DataStore_Base.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:
# conn = Helper_DB_MySQL(self.app).get_db_connection()
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}')
# conn.session.remove()
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 db_cursor_clear(cursor):
while cursor.nextset():
print(f'new result set: {cursor.fetchall()}')
def get_regions_and_currencies(self):
_m = 'DataStore_Base.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.from_DB_currency(row)
currencies.append(currency)
print(f'currencies: {currencies}')
DataStore_Base.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.from_DB_region(row)
regions.append(region)
print(f'regions: {regions}')
DataStore_Base.db_cursor_clear(cursor)
return regions, currencies
def get_user_session(self):
return User.from_json(self.session.get(User.KEY_USER))
user = User.get_default()
try:
print(f'user session: {session[self.app.ID_TOKEN_USER]}')
info_user = session[self.app.ID_TOKEN_USER].get('userinfo')
print(f'info_user: {info_user}')
user.is_logged_in = ('sub' in list(info_user.keys()) and not info_user['sub'] == '' and not str(type(info_user['sub'])) == "<class 'NoneType'?")
user.id_user_auth0 = info_user['sub'] if user.is_logged_in else None
print(f'user.id_user_auth0: {user.id_user_auth0}')
except:
print('get user login failed')
return user
def get_user_auth0(self):
return User.from_json_auth0(self.session.get(self.app.config['ID_TOKEN_USER']))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Base Store DataStore
Description:
Datastore for Store
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_base import DataStore_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Base(DataStore_Base):
# Global constants
KEY_BASKET: ClassVar[str] = Basket.KEY_BASKET
KEY_IS_INCLUDED_VAT: ClassVar[str] = Basket.KEY_IS_INCLUDED_VAT # 'is_included_VAT'
KEY_ID_CURRENCY: ClassVar[str] = Basket.KEY_ID_CURRENCY # 'id_currency'
KEY_ID_REGION_DELIVERY: ClassVar[str] = Basket.KEY_ID_REGION_DELIVERY # 'id_region_delivery'
# Attributes
def __init__(self, **kwargs):
super().__init__(**kwargs)
def get_many_product(self, product_filters):
# redundant argument validation?
_m = 'DataStore_Store_Base.get_many_product'
av.val_instance(product_filters, 'product_filters', _m, Product_Filters)
argument_dict = product_filters.to_json()
user = self.get_user_session()
argument_dict['a_id_user'] = 1 # 'auth0|6582b95c895d09a70ba10fef' # id_user
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')
category_list = Container_Product_Category()
# Categories
result_set_1 = cursor.fetchall()
print(f'raw categories: {result_set_1}')
# categories = [Product_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 = Product_Category.from_DB_product(row) # Product_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.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.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}')
# Product_Variations
cursor.nextset()
result_set_3 = cursor.fetchall()
# print(f'variations: {result_set_3}')
# variations = [Product_Variation(**row) for row in result_set_3]
variations = []
for row in result_set_3:
new_variation = Product_Variation.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]}')
# 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.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]}')
# 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.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_Base.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
DataStore_Store_Base.db_cursor_clear(cursor)
return category_list, errors # categories, category_index
"""
def get_many_id_price(self, product_ids):
_m = 'DataStore_Store_Base.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 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_regions_and_currencies(self):
_m = 'DataStore_Store_Base.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.from_DB_currency(row)
currencies.append(currency)
print(f'currencies: {currencies}')
DataStore_Store_Base.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.from_DB_region(row)
regions.append(region)
print(f'regions: {regions}')
DataStore_Store_Base.db_cursor_clear(cursor)
return regions, currencies
def get_many_product_variation(self, variation_filters):
_m = 'DataStore_Store_Base.get_many_product_variation'
print(_m)
av.val_instance(variation_filters, 'variation_filters', _m, Product_Variation_Filters)
guid = Helper_DB_MySQL.create_guid()
# now = datetime.now()
# user = self.get_user_session()
"""
argument_dict_list = {
'a_id_user': id_user,
'a_comment': comment,
'a_guid': guid
}
"""
user = self.get_user_session()
argument_dict_list = {
# 'a_guid': guid
'a_id_user': user.id_user
, **variation_filters.to_json()
}
# argument_dict_list['a_guid'] = guid
result = self.db_procedure_execute('p_shop_get_many_product_variation', argument_dict_list)
cursor = result.cursor
result_set = cursor.fetchall()
# Product_Variations
variations = Product_Variation_List()
for row in result_set:
new_variation = Product_Variation.from_DB_variation(row)
variations.add_variation(new_variation)
errors = []
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.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_Base.db_cursor_clear(cursor)
return variations, errors

View File

@@ -0,0 +1,151 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Basket DataStore
Description:
Datastore for Store Baskets
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Basket(DataStore_Store_Base):
# Global constants
KEY_BASKET: ClassVar[str] = Basket.KEY_BASKET
# Attributes
def __init__(self, **kwargs):
super().__init__(**kwargs)
def get_metadata_basket(json_request):
try:
basket = json_request[DataStore_Store_Basket.KEY_BASKET]
except KeyError:
basket = {DataStore_Store_Basket.KEY_IS_INCLUDED_VAT: True, DataStore_Store_Basket.KEY_ID_CURRENCY: 1, DataStore_Store_Basket.KEY_ID_REGION_DELIVERY: 1}
is_included_VAT = basket[DataStore_Store_Basket.KEY_IS_INCLUDED_VAT]
id_currency = basket[DataStore_Store_Basket.KEY_ID_CURRENCY]
id_region_delivery = basket[DataStore_Store_Basket.KEY_ID_REGION_DELIVERY]
return id_currency, id_region_delivery, is_included_VAT
def edit_basket(self, ids_permutation_basket, quantities_permutation_basket, id_permutation_edit, quantity_permutation_edit, sum_not_edit, id_currency, id_region_delivery, is_included_VAT):
# redundant argument validation?
_m = 'DataStore_Store_Base.edit_basket'
print(f'{_m}\nstarting...')
# av.val_instance(filters, 'filters', _m, Filters_Product_Category)
# 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_Base.input_many_product(cursor)
category_list, errors = DataStore_Store_Base.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 = [Product_Variation(**row) for row in result_set_3]
basket = Basket(is_included_VAT, id_currency, id_region_delivery)
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.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.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_Base.db_cursor_clear(cursor)
return basket

View File

@@ -0,0 +1,75 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Product Category DataStore
Description:
Datastore for Store Product Categories
"""
# internal
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Product_Category(DataStore_Store_Base):
def __init__(self):
super().__init__()
def save_categories(self, comment, categories):
_m = 'DataStore_Store_Product_Category.save_categories'
av.val_str(comment, 'comment', _m)
av.val_list(categories, 'categories', _m, Product_Category, 1)
guid = Helper_DB_MySQL.create_guid()
now = datetime.now()
user = self.get_user_session()
for permutation in permutations:
setattr(permutation, 'guid', guid)
setattr(permutation, 'created_on', now)
setattr(permutation, 'created_by', user.id_user)
cursor = self.db.cursor()
cursor.executemany(
'INSERT INTO Shop_Product_Permutation_Temp (id_permutation, id_product, description, cost_local, id_currency_cost, profit_local_min, latency_manufacture, quantity_min, quantity_max, quantity_step, quantity_stock, is_subscription, id_recurrence_interval, count_recurrence_interval, id_stripe_product, active, display_order, guid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
permutations
)
self.db.commit()
argument_dict_list = {
'a_id_user': user.id_user,
'a_comment': comment,
'a_guid': guid
}
self.db_procedure_execute('p_shop_save_permutation', argument_dict_list)
cursor.close()

View File

@@ -0,0 +1,75 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Product Permutation DataStore
Description:
Datastore for Store Product Permutations
"""
# internal
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Product_Permutation(DataStore_Store_Base):
def __init__(self):
super().__init__()
def save_permutations(self, comment, permutations):
_m = 'DataStore_Store_Base.save_permutations'
av.val_str(comment, 'comment', _m)
av.val_list(permutations, 'list_permutations', _m, Product_Permutation, 1)
guid = Helper_DB_MySQL.create_guid()
now = datetime.now()
user = self.get_user_session()
for permutation in permutations:
setattr(permutation, 'guid', guid)
setattr(permutation, 'created_on', now)
setattr(permutation, 'created_by', user.id_user)
cursor = self.db.cursor()
cursor.executemany(
'INSERT INTO Shop_Product_Permutation_Temp (id_permutation, id_product, description, cost_local, id_currency_cost, profit_local_min, latency_manufacture, quantity_min, quantity_max, quantity_step, quantity_stock, is_subscription, id_recurrence_interval, count_recurrence_interval, id_stripe_product, active, display_order, guid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
permutations
)
self.db.commit()
argument_dict_list = {
'a_id_user': user.id_user,
'a_comment': comment,
'a_guid': guid
}
self.db_procedure_execute('p_shop_save_permutation', argument_dict_list)
cursor.close()

View File

@@ -0,0 +1,97 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Product Variation DataStore
Description:
Datastore for Store Product Variations
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Product_Variation(DataStore_Store_Base):
# Global constants
# Attributes
def __init__(self, **kwargs):
super().__init__(**kwargs)
def get_many_product_variation(self, variation_filters):
_m = 'DataStore_Store_Product_Variation.get_many_product_variation'
print(_m)
av.val_instance(variation_filters, 'variation_filters', _m, Product_Variation_Filters)
guid = Helper_DB_MySQL.create_guid()
# now = datetime.now()
# user = self.get_user_session()
"""
argument_dict_list = {
'a_id_user': id_user,
'a_comment': comment,
'a_guid': guid
}
"""
user = self.get_user_session()
argument_dict_list = {
# 'a_guid': guid
'a_id_user': user.id_user
, **variation_filters.to_json()
}
# argument_dict_list['a_guid'] = guid
result = self.db_procedure_execute('p_shop_get_many_product_variation', argument_dict_list)
cursor = result.cursor
result_set = cursor.fetchall()
# Product_Variations
variations = Product_Variation_List()
for row in result_set:
new_variation = Product_Variation.from_DB_variation(row)
variations.add_variation(new_variation)
errors = []
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.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_Product_Variation.db_cursor_clear(cursor)
return variations, errors

View File

@@ -0,0 +1,173 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Stock Item DataStore
Description:
Datastore for Store Stock Items
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Stock_Item(DataStore_Store_Base):
# Global constants
# Attributes
def __init__(self):
super().__init__()
# Stock Items
def get_many_stock_item(self, stock_item_filters):
# redundant argument validation?
_m = 'DataStore_Store_Stock_Item.get_many_stock_item'
av.val_instance(stock_item_filters, 'stock_item_filters', _m, Stock_Item_Filters)
argument_dict = stock_item_filters.to_json()
user = self.get_user_session()
argument_dict['a_id_user'] = 1 # 'auth0|6582b95c895d09a70ba10fef' # id_user
print(f'argument_dict: {argument_dict}')
print('executing p_shop_get_many_stock_item')
result = self.db_procedure_execute('p_shop_get_many_stock_item', argument_dict)
cursor = result.cursor
print('data received')
# categories, category_index = DataStore_Store_Stock_Item.input_many_product(cursor)
category_list, errors = DataStore_Store_Stock_Item.input_many_stock_item(cursor)
DataStore_Store_Stock_Item.db_cursor_clear(cursor)
return category_list, errors # categories, category_index
def input_many_stock_item(cursor):
_m = 'DataStore_Store_Stock_Item.input_many_stock_item'
category_list = Container_Product_Category()
# Categories
result_set_1 = cursor.fetchall()
print(f'raw categories: {result_set_1}')
for row in result_set_1:
new_stock_item = Stock_Item.from_DB_stock_item(row)
# category_list.add_stock_item(new_stock_item, row)
try:
index_category = category_list.get_index_category_from_id(new_stock_item.id_category)
# category_list.add_stock_item(new_stock_item)
category = category_list.categories[index_category]
"""
try:
index_product = category.get_index_product_from_id(new_stock_item.id_product)
product = category.products[index_product]
try:
index_permutation = product.get_index_permutation_from_id(new_stock_item.id_permutation)
permutation = product.permutations[index_permutation]
permutation.add_stock_item(new_stock_item)
except KeyError:
permutation = Product_Permutation.from_DB_stock_item(row)
permutation.add_stock_item(new_stock_item)
product.add_permutation(permutation)
except KeyError:
product = Product.from_DB_stock_item(row)
permutation = Product_Permutation.from_DB_stock_item(row)
permutation.add_stock_item(new_stock_item)
product.add_permutation(permutation)
category_list.add_product(product)
"""
except KeyError:
new_category = Product_Category.from_DB_stock_item(row)
"""
product = Product.from_DB_stock_item(row)
permutation = Product_Permutation.from_DB_stock_item(row)
permutation.add_stock_item(new_stock_item)
product.add_permutation(permutation)
new_category.add_product(product)
"""
category_list.add_category(new_category)
try:
index_product = category.get_index_product_from_id(new_stock_item.id_product)
product = category.products[index_product]
except KeyError:
new_product = Product.from_DB_stock_item(row)
category_list.add_product(new_product)
try:
index_permutation = product.get_index_permutation_from_id(new_stock_item.id_permutation)
permutation = product.permutations[index_permutation]
permutation.add_stock_item(new_stock_item)
except KeyError:
new_permutation = Product_Permutation.from_DB_stock_item(row)
product.add_permutation(new_permutation)
category_list.add_stock_item(new_stock_item)
# Product_Variations
cursor.nextset()
result_set_3 = cursor.fetchall()
variations = []
for row in result_set_3:
new_variation = Product_Variation.from_DB_product(row)
variations.append(new_variation)
category_list.add_variation(new_variation)
# 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.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_Stock_Item.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

View File

@@ -0,0 +1,202 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store Stripe DataStore
Description:
Datastore for Store Stripe service
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_store_base import DataStore_Store_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_Store_Stripe(DataStore_Store_Base):
# Global constants
# Attributes
key_public_stripe: str = None
key_secret_stripe: str = None
def __init__(self):
super().__init__()
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_stripe_product_new(self):
_m = 'DataStore_Store_Stripe.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.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.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_Stripe.db_cursor_clear(cursor)
return products
def get_many_stripe_price_new(self):
_m = 'DataStore_Store_Stripe.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.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.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_Stripe.db_cursor_clear(cursor)
return products
def get_many_product_new(self):
_m = 'DataStore_Store_Stripe.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 = 'DataStore_Store_Stripe.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 = 'DataStore_Store_Stripe_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 = 'DataStore_Store_Stripe_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

View File

@@ -0,0 +1,189 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: User DataStore
Description:
Datastore for Users
"""
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.store.basket import Basket, Basket_Item
from business_objects.store.product_category import Container_Product_Category, Product_Category
from business_objects.store.currency import Currency
from business_objects.store.image import Image
from business_objects.store.delivery_option import Delivery_Option
from business_objects.store.delivery_region import Delivery_Region
from business_objects.store.discount import Discount
from business_objects.store.order import Order
from business_objects.store.product import Product, Product_Permutation, Product_Price, Product_Filters
from business_objects.sql_error import SQL_Error
from business_objects.store.stock_item import Stock_Item, Stock_Item_Filters
from business_objects.user import User, User_Filters, User_Permission_Evaluation
from business_objects.store.product_variation import Product_Variation, Product_Variation_Filters, Product_Variation_List
from datastores.datastore_base import DataStore_Base
from helpers.helper_db_mysql import Helper_DB_MySQL
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
from extensions import db
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
from flask import Flask, session, current_app
from pydantic import BaseModel, ConfigDict
from typing import ClassVar
from datetime import datetime
class DataStore_User(DataStore_Base):
# Global constants
# Attributes
def __init__(self):
super().__init__()
def edit_user(self):
# redundant argument validation?
_m = 'DataStore_User.edit_user'
# av.val_instance(filters, 'filters', _m, Filters_Product_Category)
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.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_User.db_cursor_clear(cursor)
return (result_set_1[0][1] == b'\x01')
"""
def get_many_user_order(self, id_user, ids_order, n_order_max, id_checkout_session):
_m = 'DataStore_User.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.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_User.db_cursor_clear(cursor)
return orders
"""
def get_many_user(self, user_filters, user=None):
_m = 'DataStore_User.get_many_user'
print(_m)
# av.val_str(user_filters, 'user_filters', _m)
# av.val_list(permutations, 'list_permutations', _m, Product_Permutation, 1)
av.val_instance(user_filters, 'user_filters', _m, User_Filters)
guid = Helper_DB_MySQL.create_guid()
# now = datetime.now()
# user = self.get_user_session()
"""
argument_dict_list = {
'a_id_user': id_user,
'a_comment': comment,
'a_guid': guid
}
"""
if user is None:
user = self.get_user_session()
argument_dict_list = {
# 'a_guid': guid
'a_id_user': user.id_user
, 'a_id_user_auth0': user.id_user_auth0
, **user_filters.to_json()
}
# argument_dict_list['a_guid'] = guid
result = self.db_procedure_execute('p_get_many_user', argument_dict_list)
"""
query = text(f"SELECT * FROM Shop_User_Eval_Temp UE_T WHERE UE_T.guid = '{guid}'")
result = self.db.session.execute(query)
"""
cursor = result.cursor
result_set = cursor.fetchall()
print(f'raw users: {result_set}')
print(f'type result set: {str(type(result_set))}')
print(f'len result set: {len(result_set)}')
"""
user_permission_evals = []
for row in result_set:
user_permission_eval = User_Permission_Evaluation.from_DB_user_eval(row)
user_permission_evals.append(user_permission_eval)
print(f'user_permission_evals: {user_permission_evals}')
"""
users = []
if len(result_set) > 0:
for row in result_set:
print(f'row: {row}')
user = User.from_DB_user(row)
users.append(user)
print(f'user {str(type(user))}: {user}')
print(f'type users: {str(type(users))}\n type user 0: {str(type(None if len(users) == 0 else users[0]))}')
# error_list, cursor = self.get_error_list_from_cursor(cursor)
errors = []
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.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_User.db_cursor_clear(cursor)
return users, errors

View File

@@ -0,0 +1,128 @@
import sys
import os
import re
import importlib
import warnings
is_pypy = '__pypy__' in sys.builtin_module_names
warnings.filterwarnings('ignore',
'.+ distutils .+ deprecated',
DeprecationWarning)
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
if is_pypy and sys.version_info < (3, 7):
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
return
warnings.warn(
"Distutils was imported before Setuptools, but importing Setuptools "
"also replaces the `distutils` module in `sys.modules`. This may lead "
"to undesirable behaviors or errors. To avoid these issues, avoid "
"using distutils directly, ensure that setuptools is installed in the "
"traditional way (e.g. not an editable install), and/or make sure "
"that setuptools is always imported before distutils.")
def clear_distutils():
if 'distutils' not in sys.modules:
return
warnings.warn("Setuptools is replacing distutils.")
mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
for name in mods:
del sys.modules[name]
def enabled():
"""
Allow selection of distutils by environment variable.
"""
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
return which == 'local'
def ensure_local_distutils():
clear_distutils()
distutils = importlib.import_module('setuptools._distutils')
distutils.__name__ = 'distutils'
sys.modules['distutils'] = distutils
# sanity check that submodules load as expected
core = importlib.import_module('distutils.core')
assert '_distutils' in core.__file__, core.__file__
def do_override():
"""
Ensure that the local copy of distutils is preferred over stdlib.
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
for more motivation.
"""
if enabled():
warn_distutils_present()
ensure_local_distutils()
class DistutilsMetaFinder:
def find_spec(self, fullname, path, target=None):
if path is not None:
return
method_name = 'spec_for_{fullname}'.format(**locals())
method = getattr(self, method_name, lambda: None)
return method()
def spec_for_distutils(self):
import importlib.abc
import importlib.util
class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
return importlib.import_module('setuptools._distutils')
def exec_module(self, module):
pass
return importlib.util.spec_from_loader('distutils', DistutilsLoader())
def spec_for_pip(self):
"""
Ensure stdlib distutils when running under pip.
See pypa/pip#8761 for rationale.
"""
if self.pip_imported_during_build():
return
clear_distutils()
self.spec_for_distutils = lambda: None
@staticmethod
def pip_imported_during_build():
"""
Detect if pip is being imported in a build script. Ref #2355.
"""
import traceback
return any(
frame.f_globals['__file__'].endswith('setup.py')
for frame, line in traceback.walk_stack(None)
)
DISTUTILS_FINDER = DistutilsMetaFinder()
def add_shim():
sys.meta_path.insert(0, DISTUTILS_FINDER)
def remove_shim():
try:
sys.meta_path.remove(DISTUTILS_FINDER)
except ValueError:
pass

View File

@@ -0,0 +1 @@
__import__('_distutils_hack').do_override()

View File

@@ -0,0 +1 @@
import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim();

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,20 @@
Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,88 @@
Metadata-Version: 2.1
Name: pip
Version: 23.0.1
Summary: The PyPA recommended tool for installing Python packages.
Home-page: https://pip.pypa.io/
Author: The pip developers
Author-email: distutils-sig@python.org
License: MIT
Project-URL: Documentation, https://pip.pypa.io
Project-URL: Source, https://github.com/pypa/pip
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development :: Build Tools
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.7
License-File: LICENSE.txt
pip - The Python Package Installer
==================================
.. image:: https://img.shields.io/pypi/v/pip.svg
:target: https://pypi.org/project/pip/
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
:target: https://pip.pypa.io/en/latest
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
Please take a look at our documentation for how to install and use pip:
* `Installation`_
* `Usage`_
We release updates regularly, with a new version every 3 months. Find more details in our documentation:
* `Release notes`_
* `Release process`_
In pip 20.3, we've `made a big improvement to the heart of pip`_; `learn more`_. We want your input, so `sign up for our user experience research studies`_ to help us do it right.
**Note**: pip 21.0, in January 2021, removed Python 2 support, per pip's `Python 2 support policy`_. Please migrate to Python 3.
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
* `Issue tracking`_
* `Discourse channel`_
* `User IRC`_
If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
* `GitHub page`_
* `Development documentation`_
* `Development IRC`_
Code of Conduct
---------------
Everyone interacting in the pip project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _package installer: https://packaging.python.org/guides/tool-recommendations/
.. _Python Package Index: https://pypi.org
.. _Installation: https://pip.pypa.io/en/stable/installation/
.. _Usage: https://pip.pypa.io/en/stable/
.. _Release notes: https://pip.pypa.io/en/stable/news.html
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
.. _GitHub page: https://github.com/pypa/pip
.. _Development documentation: https://pip.pypa.io/en/latest/development
.. _made a big improvement to the heart of pip: https://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html
.. _learn more: https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020
.. _sign up for our user experience research studies: https://pyfound.blogspot.com/2020/03/new-pip-resolver-to-roll-out-this-year.html
.. _Python 2 support policy: https://pip.pypa.io/en/latest/development/release-process/#python-2-support
.. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.38.4)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,4 @@
[console_scripts]
pip = pip._internal.cli.main:main
pip3 = pip._internal.cli.main:main
pip3.9 = pip._internal.cli.main:main

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,13 @@
from typing import List, Optional
__version__ = "23.0.1"
def main(args: Optional[List[str]] = None) -> int:
"""This is an internal API only meant for use by pip's own console scripts.
For additional details, see https://github.com/pypa/pip/issues/7498.
"""
from pip._internal.utils.entrypoints import _wrapper
return _wrapper(args)

View File

@@ -0,0 +1,31 @@
import os
import sys
import warnings
# Remove '' and current working directory from the first entry
# of sys.path, if present to avoid using current directory
# in pip commands check, freeze, install, list and show,
# when invoked as python -m pip <command>
if sys.path[0] in ("", os.getcwd()):
sys.path.pop(0)
# If we are running from a wheel, add the wheel to sys.path
# This allows the usage python pip-*.whl/pip install pip-*.whl
if __package__ == "":
# __file__ is pip-*.whl/pip/__main__.py
# first dirname call strips of '/__main__.py', second strips off '/pip'
# Resulting path is the name of the wheel itself
# Add that to sys.path so we can import pip
path = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, path)
if __name__ == "__main__":
# Work around the error reported in #9540, pending a proper fix.
# Note: It is essential the warning filter is set *before* importing
# pip, as the deprecation happens at import time, not runtime.
warnings.filterwarnings(
"ignore", category=DeprecationWarning, module=".*packaging\\.version"
)
from pip._internal.cli.main import main as _main
sys.exit(_main())

View File

@@ -0,0 +1,50 @@
"""Execute exactly this copy of pip, within a different environment.
This file is named as it is, to ensure that this module can't be imported via
an import statement.
"""
# /!\ This version compatibility check section must be Python 2 compatible. /!\
import sys
# Copied from setup.py
PYTHON_REQUIRES = (3, 7)
def version_str(version): # type: ignore
return ".".join(str(v) for v in version)
if sys.version_info[:2] < PYTHON_REQUIRES:
raise SystemExit(
"This version of pip does not support python {} (requires >={}).".format(
version_str(sys.version_info[:2]), version_str(PYTHON_REQUIRES)
)
)
# From here on, we can use Python 3 features, but the syntax must remain
# Python 2 compatible.
import runpy # noqa: E402
from importlib.machinery import PathFinder # noqa: E402
from os.path import dirname # noqa: E402
PIP_SOURCES_ROOT = dirname(dirname(__file__))
class PipImportRedirectingFinder:
@classmethod
def find_spec(self, fullname, path=None, target=None): # type: ignore
if fullname != "pip":
return None
spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target)
assert spec, (PIP_SOURCES_ROOT, fullname)
return spec
sys.meta_path.insert(0, PipImportRedirectingFinder())
assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module"
runpy.run_module("pip", run_name="__main__", alter_sys=True)

View File

@@ -0,0 +1,19 @@
from typing import List, Optional
import pip._internal.utils.inject_securetransport # noqa
from pip._internal.utils import _log
# init_logging() must be called before any call to logging.getLogger()
# which happens at import of most modules.
_log.init_logging()
def main(args: (Optional[List[str]]) = None) -> int:
"""This is preserved for old console scripts that may still be referencing
it.
For additional details, see https://github.com/pypa/pip/issues/7498.
"""
from pip._internal.utils.entrypoints import _wrapper
return _wrapper(args)

View File

@@ -0,0 +1,311 @@
"""Build Environment used for isolation during sdist building
"""
import logging
import os
import pathlib
import site
import sys
import textwrap
from collections import OrderedDict
from types import TracebackType
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union
from pip._vendor.certifi import where
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.version import Version
from pip import __file__ as pip_location
from pip._internal.cli.spinners import open_spinner
from pip._internal.locations import get_platlib, get_purelib, get_scheme
from pip._internal.metadata import get_default_environment, get_environment
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder
logger = logging.getLogger(__name__)
def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]:
return (a, b) if a != b else (a,)
class _Prefix:
def __init__(self, path: str) -> None:
self.path = path
self.setup = False
scheme = get_scheme("", prefix=path)
self.bin_dir = scheme.scripts
self.lib_dirs = _dedup(scheme.purelib, scheme.platlib)
def get_runnable_pip() -> str:
"""Get a file to pass to a Python executable, to run the currently-running pip.
This is used to run a pip subprocess, for installing requirements into the build
environment.
"""
source = pathlib.Path(pip_location).resolve().parent
if not source.is_dir():
# This would happen if someone is using pip from inside a zip file. In that
# case, we can use that directly.
return str(source)
return os.fsdecode(source / "__pip-runner__.py")
def _get_system_sitepackages() -> Set[str]:
"""Get system site packages
Usually from site.getsitepackages,
but fallback on `get_purelib()/get_platlib()` if unavailable
(e.g. in a virtualenv created by virtualenv<20)
Returns normalized set of strings.
"""
if hasattr(site, "getsitepackages"):
system_sites = site.getsitepackages()
else:
# virtualenv < 20 overwrites site.py without getsitepackages
# fallback on get_purelib/get_platlib.
# this is known to miss things, but shouldn't in the cases
# where getsitepackages() has been removed (inside a virtualenv)
system_sites = [get_purelib(), get_platlib()]
return {os.path.normcase(path) for path in system_sites}
class BuildEnvironment:
"""Creates and manages an isolated environment to install build deps"""
def __init__(self) -> None:
temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
self._prefixes = OrderedDict(
(name, _Prefix(os.path.join(temp_dir.path, name)))
for name in ("normal", "overlay")
)
self._bin_dirs: List[str] = []
self._lib_dirs: List[str] = []
for prefix in reversed(list(self._prefixes.values())):
self._bin_dirs.append(prefix.bin_dir)
self._lib_dirs.extend(prefix.lib_dirs)
# Customize site to:
# - ensure .pth files are honored
# - prevent access to system site packages
system_sites = _get_system_sitepackages()
self._site_dir = os.path.join(temp_dir.path, "site")
if not os.path.exists(self._site_dir):
os.mkdir(self._site_dir)
with open(
os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
) as fp:
fp.write(
textwrap.dedent(
"""
import os, site, sys
# First, drop system-sites related paths.
original_sys_path = sys.path[:]
known_paths = set()
for path in {system_sites!r}:
site.addsitedir(path, known_paths=known_paths)
system_paths = set(
os.path.normcase(path)
for path in sys.path[len(original_sys_path):]
)
original_sys_path = [
path for path in original_sys_path
if os.path.normcase(path) not in system_paths
]
sys.path = original_sys_path
# Second, add lib directories.
# ensuring .pth file are processed.
for path in {lib_dirs!r}:
assert not path in sys.path
site.addsitedir(path)
"""
).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
)
def __enter__(self) -> None:
self._save_env = {
name: os.environ.get(name, None)
for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
}
path = self._bin_dirs[:]
old_path = self._save_env["PATH"]
if old_path:
path.extend(old_path.split(os.pathsep))
pythonpath = [self._site_dir]
os.environ.update(
{
"PATH": os.pathsep.join(path),
"PYTHONNOUSERSITE": "1",
"PYTHONPATH": os.pathsep.join(pythonpath),
}
)
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
for varname, old_value in self._save_env.items():
if old_value is None:
os.environ.pop(varname, None)
else:
os.environ[varname] = old_value
def check_requirements(
self, reqs: Iterable[str]
) -> Tuple[Set[Tuple[str, str]], Set[str]]:
"""Return 2 sets:
- conflicting requirements: set of (installed, wanted) reqs tuples
- missing requirements: set of reqs
"""
missing = set()
conflicting = set()
if reqs:
env = (
get_environment(self._lib_dirs)
if hasattr(self, "_lib_dirs")
else get_default_environment()
)
for req_str in reqs:
req = Requirement(req_str)
# We're explicitly evaluating with an empty extra value, since build
# environments are not provided any mechanism to select specific extras.
if req.marker is not None and not req.marker.evaluate({"extra": ""}):
continue
dist = env.get_distribution(req.name)
if not dist:
missing.add(req_str)
continue
if isinstance(dist.version, Version):
installed_req_str = f"{req.name}=={dist.version}"
else:
installed_req_str = f"{req.name}==={dist.version}"
if not req.specifier.contains(dist.version, prereleases=True):
conflicting.add((installed_req_str, req_str))
# FIXME: Consider direct URL?
return conflicting, missing
def install_requirements(
self,
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
) -> None:
prefix = self._prefixes[prefix_as_string]
assert not prefix.setup
prefix.setup = True
if not requirements:
return
self._install_requirements(
get_runnable_pip(),
finder,
requirements,
prefix,
kind=kind,
)
@staticmethod
def _install_requirements(
pip_runnable: str,
finder: "PackageFinder",
requirements: Iterable[str],
prefix: _Prefix,
*,
kind: str,
) -> None:
args: List[str] = [
sys.executable,
pip_runnable,
"install",
"--ignore-installed",
"--no-user",
"--prefix",
prefix.path,
"--no-warn-script-location",
]
if logger.getEffectiveLevel() <= logging.DEBUG:
args.append("-v")
for format_control in ("no_binary", "only_binary"):
formats = getattr(finder.format_control, format_control)
args.extend(
(
"--" + format_control.replace("_", "-"),
",".join(sorted(formats or {":none:"})),
)
)
index_urls = finder.index_urls
if index_urls:
args.extend(["-i", index_urls[0]])
for extra_index in index_urls[1:]:
args.extend(["--extra-index-url", extra_index])
else:
args.append("--no-index")
for link in finder.find_links:
args.extend(["--find-links", link])
for host in finder.trusted_hosts:
args.extend(["--trusted-host", host])
if finder.allow_all_prereleases:
args.append("--pre")
if finder.prefer_binary:
args.append("--prefer-binary")
args.append("--")
args.extend(requirements)
extra_environ = {"_PIP_STANDALONE_CERT": where()}
with open_spinner(f"Installing {kind}") as spinner:
call_subprocess(
args,
command_desc=f"pip subprocess to install {kind}",
spinner=spinner,
extra_environ=extra_environ,
)
class NoOpBuildEnvironment(BuildEnvironment):
"""A no-op drop-in replacement for BuildEnvironment"""
def __init__(self) -> None:
pass
def __enter__(self) -> None:
pass
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
pass
def cleanup(self) -> None:
pass
def install_requirements(
self,
finder: "PackageFinder",
requirements: Iterable[str],
prefix_as_string: str,
*,
kind: str,
) -> None:
raise NotImplementedError()

View File

@@ -0,0 +1,293 @@
"""Cache Management
"""
import hashlib
import json
import logging
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Set
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import InvalidWheelFilename
from pip._internal.models.direct_url import DirectUrl
from pip._internal.models.format_control import FormatControl
from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.urls import path_to_url
logger = logging.getLogger(__name__)
ORIGIN_JSON_NAME = "origin.json"
def _hash_dict(d: Dict[str, str]) -> str:
"""Return a stable sha224 of a dictionary."""
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha224(s.encode("ascii")).hexdigest()
class Cache:
"""An abstract class - provides cache directories for data from links
:param cache_dir: The root of the cache.
:param format_control: An object of FormatControl class to limit
binaries being read from the cache.
:param allowed_formats: which formats of files the cache should store.
('binary' and 'source' are the only allowed values)
"""
def __init__(
self, cache_dir: str, format_control: FormatControl, allowed_formats: Set[str]
) -> None:
super().__init__()
assert not cache_dir or os.path.isabs(cache_dir)
self.cache_dir = cache_dir or None
self.format_control = format_control
self.allowed_formats = allowed_formats
_valid_formats = {"source", "binary"}
assert self.allowed_formats.union(_valid_formats) == _valid_formats
def _get_cache_path_parts(self, link: Link) -> List[str]:
"""Get parts of part that must be os.path.joined with cache_dir"""
# We want to generate an url to use as our cache key, we don't want to
# just re-use the URL because it might have other items in the fragment
# and we don't care about those.
key_parts = {"url": link.url_without_fragment}
if link.hash_name is not None and link.hash is not None:
key_parts[link.hash_name] = link.hash
if link.subdirectory_fragment:
key_parts["subdirectory"] = link.subdirectory_fragment
# Include interpreter name, major and minor version in cache key
# to cope with ill-behaved sdists that build a different wheel
# depending on the python version their setup.py is being run on,
# and don't encode the difference in compatibility tags.
# https://github.com/pypa/pip/issues/7296
key_parts["interpreter_name"] = interpreter_name()
key_parts["interpreter_version"] = interpreter_version()
# Encode our key url with sha224, we'll use this because it has similar
# security properties to sha256, but with a shorter total output (and
# thus less secure). However the differences don't make a lot of
# difference for our use case here.
hashed = _hash_dict(key_parts)
# We want to nest the directories some to prevent having a ton of top
# level directories where we might run out of sub directories on some
# FS.
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
return parts
def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
can_not_cache = not self.cache_dir or not canonical_package_name or not link
if can_not_cache:
return []
formats = self.format_control.get_allowed_formats(canonical_package_name)
if not self.allowed_formats.intersection(formats):
return []
candidates = []
path = self.get_path_for_link(link)
if os.path.isdir(path):
for candidate in os.listdir(path):
candidates.append((candidate, path))
return candidates
def get_path_for_link(self, link: Link) -> str:
"""Return a directory to store cached items in for link."""
raise NotImplementedError()
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
"""
raise NotImplementedError()
class SimpleWheelCache(Cache):
"""A cache of wheels for future installs."""
def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
super().__init__(cache_dir, format_control, {"binary"})
def get_path_for_link(self, link: Link) -> str:
"""Return a directory to store cached wheels for link
Because there are M wheels for any one sdist, we provide a directory
to cache them in, and then consult that directory when looking up
cache hits.
We only insert things into the cache if they have plausible version
numbers, so that we don't contaminate the cache with things that were
not unique. E.g. ./package might have dozens of installs done for it
and build a version of 0.0...and if we built and cached a wheel, we'd
end up using the same wheel even if the source has been edited.
:param link: The link of the sdist for which this will cache wheels.
"""
parts = self._get_cache_path_parts(link)
assert self.cache_dir
# Store wheels within the root cache_dir
return os.path.join(self.cache_dir, "wheels", *parts)
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
candidates = []
if not package_name:
return link
canonical_package_name = canonicalize_name(package_name)
for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
try:
wheel = Wheel(wheel_name)
except InvalidWheelFilename:
continue
if canonicalize_name(wheel.name) != canonical_package_name:
logger.debug(
"Ignoring cached wheel %s for %s as it "
"does not match the expected distribution name %s.",
wheel_name,
link,
package_name,
)
continue
if not wheel.supported(supported_tags):
# Built for a different python/arch/etc
continue
candidates.append(
(
wheel.support_index_min(supported_tags),
wheel_name,
wheel_dir,
)
)
if not candidates:
return link
_, wheel_name, wheel_dir = min(candidates)
return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
class EphemWheelCache(SimpleWheelCache):
"""A SimpleWheelCache that creates it's own temporary cache directory"""
def __init__(self, format_control: FormatControl) -> None:
self._temp_dir = TempDirectory(
kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
globally_managed=True,
)
super().__init__(self._temp_dir.path, format_control)
class CacheEntry:
def __init__(
self,
link: Link,
persistent: bool,
):
self.link = link
self.persistent = persistent
self.origin: Optional[DirectUrl] = None
origin_direct_url_path = Path(self.link.file_path).parent / ORIGIN_JSON_NAME
if origin_direct_url_path.exists():
self.origin = DirectUrl.from_json(origin_direct_url_path.read_text())
class WheelCache(Cache):
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
This Cache allows for gracefully degradation, using the ephem wheel cache
when a certain link is not found in the simple wheel cache first.
"""
def __init__(
self, cache_dir: str, format_control: Optional[FormatControl] = None
) -> None:
if format_control is None:
format_control = FormatControl()
super().__init__(cache_dir, format_control, {"binary"})
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
self._ephem_cache = EphemWheelCache(format_control)
def get_path_for_link(self, link: Link) -> str:
return self._wheel_cache.get_path_for_link(link)
def get_ephem_path_for_link(self, link: Link) -> str:
return self._ephem_cache.get_path_for_link(link)
def get(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Link:
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
if cache_entry is None:
return link
return cache_entry.link
def get_cache_entry(
self,
link: Link,
package_name: Optional[str],
supported_tags: List[Tag],
) -> Optional[CacheEntry]:
"""Returns a CacheEntry with a link to a cached item if it exists or
None. The cache entry indicates if the item was found in the persistent
or ephemeral cache.
"""
retval = self._wheel_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=True)
retval = self._ephem_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=False)
return None
@staticmethod
def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None:
origin_path = Path(cache_dir) / ORIGIN_JSON_NAME
if origin_path.is_file():
origin = DirectUrl.from_json(origin_path.read_text())
# TODO: use DirectUrl.equivalent when https://github.com/pypa/pip/pull/10564
# is merged.
if origin.url != download_info.url:
logger.warning(
"Origin URL %s in cache entry %s does not match download URL %s. "
"This is likely a pip bug or a cache corruption issue.",
origin.url,
cache_dir,
download_info.url,
)
origin_path.write_text(download_info.to_json(), encoding="utf-8")

View File

@@ -0,0 +1,4 @@
"""Subpackage containing all of pip's command line interface related code
"""
# This file intentionally does not import submodules

Some files were not shown because too many files have changed in this diff Show More