1.started removal of CDNs.\n 2. Improved modular structure for all parts of project including database.
This commit is contained in:
@@ -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)
|
||||
"""
|
||||
@@ -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.
BIN
__pycache__/extensions.cpython-312.pyc
Normal file
BIN
__pycache__/extensions.cpython-312.pyc
Normal file
Binary file not shown.
571
app.py
571
app.py
@@ -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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
)
|
||||
@@ -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]
|
||||
|
||||
BIN
business_objects/store/__pycache__/basket.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/basket.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/currency.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/currency.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/discount.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/discount.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/image.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/image.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/order.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/order.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/product.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/product.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/product_price.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/product_price.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/stock_item.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/stock_item.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/store_base.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/store_base.cpython-312.pyc
Normal file
Binary file not shown.
@@ -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)
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
454
business_objects/store/product.py
Normal file
454
business_objects/store/product.py
Normal 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
|
||||
)
|
||||
@@ -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:
|
||||
365
business_objects/store/product_permutation.py
Normal file
365
business_objects/store/product_permutation.py
Normal 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
|
||||
"""
|
||||
109
business_objects/store/product_price.py
Normal file
109
business_objects/store/product_price.py
Normal 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
|
||||
@@ -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):
|
||||
73
business_objects/store/product_variation_tree.py
Normal file
73
business_objects/store/product_variation_tree.py
Normal 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)
|
||||
@@ -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):
|
||||
57
business_objects/store/store_base.py
Normal file
57
business_objects/store/store_base.py
Normal 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
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
|
||||
14
config.py
14
config.py
@@ -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
|
||||
"""
|
||||
|
||||
BIN
datastores/__pycache__/datastore_base.cpython-312.pyc
Normal file
BIN
datastores/__pycache__/datastore_base.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
datastores/__pycache__/datastore_store_base.cpython-312.pyc
Normal file
BIN
datastores/__pycache__/datastore_store_base.cpython-312.pyc
Normal file
Binary file not shown.
BIN
datastores/__pycache__/datastore_store_basket.cpython-312.pyc
Normal file
BIN
datastores/__pycache__/datastore_store_basket.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
datastores/__pycache__/datastore_user.cpython-312.pyc
Normal file
BIN
datastores/__pycache__/datastore_user.cpython-312.pyc
Normal file
Binary file not shown.
158
datastores/datastore_base.py
Normal file
158
datastores/datastore_base.py
Normal 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
294
datastores/datastore_store_base.py
Normal file
294
datastores/datastore_store_base.py
Normal 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
|
||||
151
datastores/datastore_store_basket.py
Normal file
151
datastores/datastore_store_basket.py
Normal 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
|
||||
75
datastores/datastore_store_product_category.py
Normal file
75
datastores/datastore_store_product_category.py
Normal 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()
|
||||
75
datastores/datastore_store_product_permutation.py
Normal file
75
datastores/datastore_store_product_permutation.py
Normal 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()
|
||||
97
datastores/datastore_store_product_variation.py
Normal file
97
datastores/datastore_store_product_variation.py
Normal 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
|
||||
173
datastores/datastore_store_stock_item.py
Normal file
173
datastores/datastore_store_stock_item.py
Normal 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
|
||||
|
||||
202
datastores/datastore_store_stripe.py
Normal file
202
datastores/datastore_store_stripe.py
Normal 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
|
||||
|
||||
189
datastores/datastore_user.py
Normal file
189
datastores/datastore_user.py
Normal 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
|
||||
128
env_test/Lib/site-packages/_distutils_hack/__init__.py
Normal file
128
env_test/Lib/site-packages/_distutils_hack/__init__.py
Normal 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
|
||||
Binary file not shown.
Binary file not shown.
1
env_test/Lib/site-packages/_distutils_hack/override.py
Normal file
1
env_test/Lib/site-packages/_distutils_hack/override.py
Normal file
@@ -0,0 +1 @@
|
||||
__import__('_distutils_hack').do_override()
|
||||
1
env_test/Lib/site-packages/distutils-precedence.pth
Normal file
1
env_test/Lib/site-packages/distutils-precedence.pth
Normal file
@@ -0,0 +1 @@
|
||||
import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim();
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
20
env_test/Lib/site-packages/pip-23.0.1.dist-info/LICENSE.txt
Normal file
20
env_test/Lib/site-packages/pip-23.0.1.dist-info/LICENSE.txt
Normal 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.
|
||||
88
env_test/Lib/site-packages/pip-23.0.1.dist-info/METADATA
Normal file
88
env_test/Lib/site-packages/pip-23.0.1.dist-info/METADATA
Normal 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
|
||||
1002
env_test/Lib/site-packages/pip-23.0.1.dist-info/RECORD
Normal file
1002
env_test/Lib/site-packages/pip-23.0.1.dist-info/RECORD
Normal file
File diff suppressed because it is too large
Load Diff
5
env_test/Lib/site-packages/pip-23.0.1.dist-info/WHEEL
Normal file
5
env_test/Lib/site-packages/pip-23.0.1.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.38.4)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
13
env_test/Lib/site-packages/pip/__init__.py
Normal file
13
env_test/Lib/site-packages/pip/__init__.py
Normal 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)
|
||||
31
env_test/Lib/site-packages/pip/__main__.py
Normal file
31
env_test/Lib/site-packages/pip/__main__.py
Normal 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())
|
||||
50
env_test/Lib/site-packages/pip/__pip-runner__.py
Normal file
50
env_test/Lib/site-packages/pip/__pip-runner__.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
19
env_test/Lib/site-packages/pip/_internal/__init__.py
Normal file
19
env_test/Lib/site-packages/pip/_internal/__init__.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
311
env_test/Lib/site-packages/pip/_internal/build_env.py
Normal file
311
env_test/Lib/site-packages/pip/_internal/build_env.py
Normal 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()
|
||||
293
env_test/Lib/site-packages/pip/_internal/cache.py
Normal file
293
env_test/Lib/site-packages/pip/_internal/cache.py
Normal 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")
|
||||
4
env_test/Lib/site-packages/pip/_internal/cli/__init__.py
Normal file
4
env_test/Lib/site-packages/pip/_internal/cli/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Subpackage containing all of pip's command line interface related code
|
||||
"""
|
||||
|
||||
# This file intentionally does not import submodules
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user