Initial commit

This commit is contained in:
2024-04-17 15:07:51 +01:00
commit f1b095ba83
280 changed files with 30850 additions and 0 deletions

54
DEPRECATED - routes.py Normal file
View File

@@ -0,0 +1,54 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Controller - Webpage routing
Description:
Defines the routes and view functions for each page.
Manages the interaction between the frontend and backend.
"""
from flask import render_template, url_for, Blueprint
from app import app
from app.forms import Form_Contact
# from forms import MyForm
# from app import MyForm
from models.model_view_contact import Model_View_Contact
@app.route('/', methods=['GET'])
def home():
return render_template('_home.html', title='Home')
@app.route('/store', methods=['GET'])
def store_home():
return render_template('_store_home.html', title='Store Home')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = Form_Contact()
if form.validate_on_submit():
# Handle form submission
email = form.sender_email.data
CC = form.sender_CC.data
name = form.sender_name.data
msg = form.sender_message.data
# return render_template('contact.html', form=form)
# return render_template('_contact.html', title='Contact Us')
return render_template('contact.html', model=Model_View_Contact(form))
"""
@app.route('/about')
def about():
return render_template('about.html')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = MyForm()
if form.validate_on_submit():
# Handle form submission
pass
return render_template('contact.html', form=form)
"""

10
README Normal file
View File

@@ -0,0 +1,10 @@
Precision and Research Technology Systems Limited
Website with online store
Powered by flask
host for machine:
python -m flask run
host for local network:
python -m flask run --host=0.0.0.0

20
__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Initialisation
Description:
Initializes the Flask application.
Initializes any extensions used in the project.
"""
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
# from app import routes
# import business_objects, lib, models

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

644
app.py Normal file
View File

@@ -0,0 +1,644 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App General
Feature: App
Description:
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from config import app_config, Config
# from routes import bp_home
from app.forms import Form_Contact, Form_Basket_Add, Form_Basket_Edit, Form_Billing, Form_Delivery_Region, Form_Currency # , Form_Product
from models.model_view_base import Model_View_Base
from models.model_view_home import Model_View_Home
from models.model_view_store import Model_View_Store
from models.model_view_store_home import Model_View_Store_Home
from models.model_view_store_product import Model_View_Store_Product
from models.model_view_store_basket import Model_View_Store_Basket
from models.model_view_store_checkout import Model_View_Store_Checkout
from models.model_view_store_checkout_success import Model_View_Store_Checkout_Success
from models.model_view_contact import Model_View_Contact
from business_objects.product import Product # , Product_Image_Filters, Resolution_Level_Enum
import lib.argument_validation as av
from business_objects.basket import Basket_Item
from datastores.datastore_store import DataStore_Store
from business_objects.product import Product_Filters
# external
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
import stripe
import json
from dotenv import load_dotenv, find_dotenv
import os
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
import jwt
# VARIABLE INSTANTIATION
app = Flask(__name__)
CORS(app)
# app.register_blueprint(bp_home, url_prefix='')
email_recipient = 'edward.middletonsmith@gmail.com' # 'noreply'
app.config.from_object(app_config)
print(f'secret key = {app.secret_key}')
app.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_DATABASE_URI
app.SQLALCHEMY_TRACK_MODIFICATIONS = Config.SQLALCHEMY_TRACK_MODIFICATIONS
app.ID_AUTH0_CLIENT = Config.ID_AUTH0_CLIENT
app.ID_AUTH0_CLIENT_SECRET = Config.ID_AUTH0_CLIENT_SECRET
app.DOMAIN_AUTH0 = Config.DOMAIN_AUTH0
app.ID_TOKEN_USER = Config.ID_TOKEN_USER
"""
app.is_included_VAT = Config.is_included_VAT
app.KEY_IS_INCLUDED_VAT = Config.KEY_IS_INCLUDED_VAT
app.code_currency = Config.code_currency
app.KEY_CODE_CURRENCY = Config.KEY_CODE_CURRENCY
app.code_region_delivery = Config.code_region_delivery
app.KEY_CODE_REGION_DELIVERY = Config.KEY_CODE_REGION_DELIVERY
app.KEY_ID_CURRENCY = Config.KEY_ID_CURRENCY
app.KEY_ID_REGION_DELIVERY = Config.KEY_ID_REGION_DELIVERY
app.id_currency = Config.id_currency
app.id_region_delivery = Config.id_region_delivery
"""
db = SQLAlchemy(app)
oauth = OAuth(app)
oauth.register(
"auth0",
client_id = app.ID_AUTH0_CLIENT,
client_secret = app.ID_AUTH0_CLIENT_SECRET, # =env.get("AUTH0_CLIENT_SECRET"),
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=f'https://{app.DOMAIN_AUTH0}/.well-known/openid-configuration'
)
# session[app.ID_TOKEN_USER] = {'userinfo': {'sub': ''}}
# METHODS
# ROUTING
@app.route('/', methods=['GET'])
def home():
return render_template('_page_home.html', model=Model_View_Home(db, get_info_user(), app))
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = Form_Contact()
if form.validate_on_submit():
# Handle form submission
email = form.email.data
CC = form.CC.data # not in use
name = form.name.data
msg = form.msg.data
# send email
pass
return render_template('_page_contact.html', model=Model_View_Contact(db, get_info_user(), app, form))
# Store
@app.route('/store', methods=['GET'])
def store_home():
print("store home")
try:
data = request.json
except:
data = {}
print(f'data={data}')
try:
id_currency = data.id_currency
except:
id_currency = Model_View_Store.ID_CURRENCY_DEFAULT
print(f"id_currency = {id_currency}")
try:
id_region_delivery = data.id_region_delivery
except:
id_region_delivery = Model_View_Store.ID_REGION_DELIVERY_DEFAULT
print(f"id_region_delivery = {id_region_delivery}")
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
# model.get_regions_and_currencies()
# model.categories = Model_View_Store_Home.get_many_product_category(db)
# product = categories[list(categories.keys())[0]][0]
model.get_many_product_category(Product_Filters(
model.id_user,
True, '', False,
True, '', False, False,
True, '', False,
False, '', False, True,
False, id_region_delivery, False,
False, id_currency, False,
True, '', False
))
"""
for cat in model.categories:
# for product in model.categories[cat]:
i_cat = model.category_index[cat.id_category]
for p in cat.products:
print(f'adding basket add form for product with id: {p.id_product}')
model.categories[i_cat].products[model.categories[i_cat].product_index[p.id_product]].add_form_basket_add()
model.categories[i_cat].products[model.categories[i_cat].product_index[p.id_product]].form_basket_add = Form_Basket_Add()
print('form added')
# print('form added for product {p.id}')
print('rendering page store home')
"""
for cat in model.category_list.categories:
# for product in model.categories[cat]:
# i_cat = model.category_index[cat.id_category]
print(f'category: {cat.name}')
for p in cat.products:
print(f'product: {p.name}')
print(f'selected permutation: {p.get_permutation_selected()}')
return render_template('_page_store_home.html', model = model) # "<html><body><h1>Boobs</h1></html></body>"
# POST request
# return jsonify(Success=True, data={'html_block': render_template('_page_store_home.html', model = model)})
# update with local basket, if not logged in- partial
@app.route('/store/basket_load', methods=['POST'])
def basket_load():
_m = 'basket_load'
print(f'{_m}\nstarting...')
data = request.json
print(f'data={data}')
# model, html_block = render_basket_from_JSON(data)
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
# model.import_JSON_basket(data)
model.get_basket(data)
model.is_included_VAT = is_included_VAT
html_block = render_template('_block_store_basket.html', model = model)
print(f'html_block:\n{html_block}')
return jsonify(Success=True, data={'html_block': html_block, 'basket': model.basket.to_json()}) # {'items': [b_i.to_json() for b_i in model.basket]}}) # { 'html': render_template('_block_store_basket.html', model = model), 'basket': model.basket })
@app.route('/store/basket_add', methods=['POST'])
def basket_add():
_m = 'basket_add'
data = request.json # .get('data')
print(f'data: {data}')
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
model.is_included_VAT = is_included_VAT
form_data = data[Model_View_Store.key_form]
print(f'form_data: {form_data}')
form = Form_Basket_Add(**form_data)
print('form acquired')
print(form.__repr__)
if form.validate_on_submit():
print('valid form')
# model = input_JSON_basket(model, data)
# if not logged in:
try:
print('importing basket')
# print('success' if model.import_JSON_basket(data) else 'failure')
model.get_basket(data)
permutation_id, quantity = model.import_JSON_basket_item(data, form)
print(f'permutation_id: {permutation_id}\nquantity: {quantity}')
print(f'editing basket:')
model.basket_item_edit(permutation_id, quantity, True) # new_basket =
except:
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
html_block = render_template('_block_store_basket.html', model = model)
# print(f'html_block:\n{html_block}')
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}}) # 'items': [b_i.to_json() for b_i in model.basket]}})
return jsonify({'status': 'failure', 'Message': 'Invalid quantities'})
@app.route('/store/basket_edit', methods=['POST'])
def basket_edit():
_m = 'basket_edit'
data = request.json # .get('data')
print(f'data: {data}')
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
model.is_included_VAT = is_included_VAT
form_data = data[Model_View_Store.key_form]
print(f'form_data: {form_data}')
form = Form_Basket_Edit(**form_data)
print('form acquired')
print(form.__repr__)
if form.validate_on_submit():
print('valid form')
# model = input_JSON_basket(model, data)
# if not logged in:
try:
# print('importing basket')
# model.import_JSON_basket(data)
model.get_basket(data)
permutation_id, quantity = model.import_JSON_basket_item(data, form)
model.basket_item_edit(permutation_id, quantity, False) # new_basket =
except:
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
html_block = render_template('_block_store_basket.html', model = model)
# print(f'html_block:\n{html_block}')
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}})
return jsonify({'status': 'failure', 'Message': 'Invalid quantities'})
@app.route('/store/basket_delete', methods=['POST'])
def basket_delete():
_m = 'basket_delete'
data = request.json # .get('data')
print(f'data: {data}')
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery)
model.is_included_VAT = is_included_VAT
try:
# print('importing basket')
# model.import_JSON_basket(data)
model.get_basket(data)
permutation_id, quantity = model.import_JSON_basket_item(data)
model.basket_item_edit(permutation_id, 0, False) # new_basket =
except:
return jsonify({'status': 'failure', 'Message': 'Bad data received by controller'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
html_block = render_template('_block_store_basket.html', model = model)
# print(f'html_block:\n{html_block}')
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': model.basket.to_json_list()}})
# delete basket item with product_id
item_deleted = False
for basket_item in model.basket:
if basket_item.product.id_product == product_id:
model.basket.remove(basket_item)
item_deleted = True
break
if not item_deleted:
return jsonify({'status': 'failure', 'Message': 'Basket item removal failure: product not found in basket.'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
html_block = render_template('_block_store_basket.html', model = model)
# print(f'html_block:\n{html_block}')
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': [b_i.to_json() for b_i in model.basket]}})
@app.route('/store/basket', methods=['GET']) # 'POST'
def store_basket():
# _m = 'basket_review'
try:
data = request.json
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
model = Model_View_Store_Basket(db, get_info_user(), app, id_currency, id_region_delivery)
model.is_included_VAT = is_included_VAT
except:
raise Exception('Bad data received by controller')
return render_template('_page_store_basket.html', model=model)
data = request.json # .get('data')
print(f'data: {data}')
# if logged in
#html_block = render_template('_page_store_basket.html', model = model) # with basket from database
try:
print('importing basket')
model.import_JSON_basket(data)
except:
return jsonify({'status': 'failure', 'Message': 'Bad basket data received by controller'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
html_block = render_template('_page_store_billing.html', model = model)
# print(f'html_block:\n{html_block}')
return jsonify(Success = True, data = { 'html_block': html_block, 'basket': {'items': [b_i.to_json() for b_i in model.basket]}})
@app.route('/store/basket_info', methods=['POST'])
def basket_info():
_m = 'basket_info'
data = request.json # .get('data')
print(f'data: {data}')
model = Model_View_Store_Basket(db, get_info_user(), app)
form_data = data[Model_View_Store.key_form]
print(f'form_data: {form_data}')
form = Form_Billing(**form_data)
print('form acquired')
print(form.__repr__)
if form.validate_on_submit():
print('valid form')
# model = input_JSON_basket(model, data)
# if not logged in:
data_info = {}
try:
info_type = data[model.key_info_type]
print('importing address information')
data_info[model.key_region] = form.region.data
print(f'region: {data_info[model.key_region]}')
data_info[model.key_name_full] = form.name_full.data
print(f'full name: {data_info[model.key_name_full]}')
data_info[model.key_phone_number] = form.phone_number.data
print(f'phone number: {data_info[model.key_phone_number]}')
data_info[model.key_postcode] = form.postcode.data
print(f'postcode: {data_info[model.key_postcode]}')
data_info[model.key_address1] = form.address_1.data
print(f'address line 1: {data_info[model.key_address1]}')
data_info[model.key_address2] = form.address_2.data
print(f'address line 2: {data_info[model.key_address2]}')
data_info[model.key_city] = form.city.data
print(f'city: {data_info[model.key_city]}')
data_info[model.key_county] = form.county.data
print(f'county: {data_info[model.key_county]}')
data_info[model.key_info_identical] = form.identical
print(f'identical: {data_info[model.key_info_identical]}')
except:
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
# html_block = render_template('_block_store_basket.html', model = model)
# print(f'html_block:\n{html_block}')
data = {}
data[model.key_info_type] = model.key_info_billing if (info_type == model.key_info_billing) else model.key_info_delivery
data[info_type] = data_info
return jsonify(Success = True, data = data)
return jsonify({'status': 'failure', 'Message': f'Invalid address information\n{form.errors}'})
@app.route('/store/product?permutationId=<permutation_id>regionId=&<region_id>&currencyId=<currency_id>', methods=['GET']) # <product_id>&
def store_product(permutation_id, region_id, currency_id):
_m = 'store_product'
"""
av.full_val_int(product_id, 'product_id', _m)
product_id = int(product_id)
print(f'product_id: {product_id}')
if permutation_id == 'None' or str(type(permutation_id)) == "<class 'NoneType'>":
permutation_id = None
else:
"""
# app.id_region_delivery = region_id
# app.id_currency = currency_id
av.full_val_int(permutation_id, 'permutation_id', _m)
permutation_id = int(permutation_id)
print(f'{_m}\nstarting...')
# print(f'product id: {product_id}')
print(f'permutation id: {permutation_id}')
try:
model = Model_View_Store_Product(db, get_info_user(), app, permutation_id, currency_id, region_id)
print('model reached')
# model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(request.json)
# model.get_many_product_category(product_ids = str(product_id))
# print('categories reached')
# product = model.categories[0].products[0]# [list(categories.keys())[0]][0]
# print('product reached')
# return jsonify({'data': render_template('_page_store_product.html', model=model)})
permutation_selected = model.product.get_permutation_selected()
print(f'selected permutation: {permutation_selected}')
return render_template('_page_store_product.html', model=model)
except:
print('except reached')
return jsonify({'status': 'error'})
# Stripe
@app.route('/config', methods=['GET'])
def get_publishable_key():
price = stripe.Price.retrieve(Model_View_Store_Checkout.get_price_id(Product.template().id))
return jsonify({
'publicKey': Model_View_Store_Checkout.key_public_stripe, # os.getenv('KEY_PUBLIC_STRIPE'),
'unitAmount': price['unit_amount'],
'currency': price['currency']
})
# Create Stripe prices
@app.route('/store/product_create_all', methods=['GET'])
def product_create_all():
# _m = 'product_create_all'
model = Model_View_Store_Checkout(db, get_info_user(), app)
products, currencies = model.get_many_product_new()
html = f"<html><body>"
for product in products:
product.id_stripe_product = model.create_product(product)
html += f"<h1>product id = {product.id}</h1><br><h1>id_stripe_product = {product.id_stripe_product}</h1>"
html += "</body></html>"
return html
@app.route('/store/price_create_all', methods=['GET'])
def price_create_all():
# _m = 'price_create_all'
model = Model_View_Store_Checkout(db, get_info_user(), app)
products, currencies = model.get_many_price_new()
html = f"<html><body>"
for product in products:
product.id_stripe_price = model.create_price(product, currencies[product.id])
html += f"<h1>product id = {product.id}</h1><br><h1>id_stripe_price = {product.id_stripe_price}</h1><br><h1>currency = {currencies[product.id]}</h1>"
html += "</body></html>"
return html
# Fetch the Checkout Session to display the JSON result on the success page
@app.route('/store/checkout_session?<session_id>', methods=['GET'])
def get_checkout_session(session_id):
id = request.args.get('session_id')
_m = 'get_checkout_session'
# av.full_val_int(session_id, 'session_id', _m)
av.val_str(session_id, 'session_id', _m)
print(f'url var normal session id: {session_id}')
print(f'{_m}\nstarting...')
# session_id = id
session_id = session_id
print(f'request.args checkout session id: {session_id}') # for function
checkout_session = stripe.checkout.Session.retrieve(session_id)
return jsonify(checkout_session)
@app.route('/store/checkout', methods=['POST', 'GET'])
def create_checkout_session():
# quantity = request.form.get('quantity', 1)
# domain_url = os.getenv('DOMAIN')
model = Model_View_Store_Checkout(db, get_info_user(), app)
print('checkout model created')
try:
data = request.json # .get('data')
print(f'data: {data}')
print('importing basket')
model.get_basket(data)
model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(data)
# model.import_JSON_basket(data)
# print('getting is subscription')
# is_subscription_checkout_session = data[model.key_is_subscription]
code_currency = 'GBP' # data[model.key_code_currency]
print(f'currency code: {code_currency}')
except:
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
items = []
for item in model.basket.items:
permutation = item.product.get_permutation_selected()
price = permutation.get_price_from_code_currency(code_currency)
items.append({'price': price.id_stripe_price,
'quantity': item.quantity })
# if is_subscription_checkout_session:
# break
print(f'items = {items}')
try:
# Create new Checkout Session for the order
# Other optional params include:
# [billing_address_collection] - to display billing address details on the page
# [customer] - if you have an existing Stripe Customer ID
# [payment_intent_data] - lets capture the payment later
# [customer_email] - lets you prefill the email input in the form
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
# For full details see https://stripe.com/docs/api/checkout/sessions/create
stripe.api_key = model.key_secret_stripe
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
checkout_session = stripe.checkout.Session.create(
success_url=Model_View_Store_Checkout.url_host + '/store/checkout_success%3Fsession_id={CHECKOUT_SESSION_ID}',
cancel_url=Model_View_Store_Checkout.url_host + '/store/checkout_cancelled',
mode='subscription' if False else 'payment', # is_subscription_checkout_session
# automatic_tax={'enabled': True},
line_items=items
)
data_out = {}
# data_out['Success'] = True
data_out[f'{model.key_id_checkout}'] = checkout_session.id
data_out[f'{model.key_url_checkout}'] = checkout_session.url
# return jsonify(Success = True, data = data_out) # Success = True, f'{model.key_id_checkout}' = checkout_session.id)
print(f'checkout session url: {checkout_session.url}')
# redirect(checkout_session.url) # , code=303)
return jsonify(Success = True, data = data_out)
# return get_checkout_session(checkout_session.id)
except Exception as e:
return jsonify(error=str(e)), 403
@app.route('/store/checkout_success?<session_id>', methods=['GET'])
def checkout_success(session_id):
_m = 'store checkout success'
# av.full_val_int(session_id, 'session_id', _m)
av.val_str(session_id, 'session_id', _m)
if (session_id[:len('session_id=')] == 'session_id='):
session_id = session_id[len('session_id='):]
print(f'url var normal session id: {session_id}')
print(f'{_m}\nstarting...')
id = request.args.get('sessionId')
print(f'request.args checkout session id: {id}')
checkout_session = stripe.checkout.Session.retrieve(session_id)
print(f'checkout session data: {checkout_session}')
# validate billing information
model = Model_View_Store_Checkout_Success(db, get_info_user(), app)
return render_template('_page_store_checkout_success.html', model=model)
@app.route('/store/checkout_cancelled', methods=['GET'])
def checkout_cancelled():
_m = 'store checkout success'
print(f'{_m}\nstarting...')
return render_template('_page_store_checkout_cancelled.html', model=Model_View_Store_Checkout(db, get_info_user(), app))
# include VAT in prices?
@app.route('/store/set_is_included_VAT', methods=['POST'])
def set_is_included_VAT():
_m = 'set_is_included_VAT'
print(f'{_m}\nstarting...')
data = request.json
print(f'data={data}')
app.is_included_VAT = not app.is_included_VAT # session[app.KEY_IS_INCLUDED_VAT] # data[app.KEY_IS_INCLUDED_VAT]
return jsonify(Success=True, data={Model_View_Base.KEY_IS_INCLUDED_VAT: app.is_included_VAT})
# delivery region
@app.route('/store/set_delivery_region', methods=['POST'])
def set_delivery_region():
_m = 'set_delivery_region'
print(f'{_m}\nstarting...')
data = request.json
print(f'data={data}')
# model = Model_View_Store(db, get_info_user(), app)
form_data = data[Model_View_Store.key_form]
print(f'form_data: {form_data}')
"""
form = Form_Delivery_Region(**form_data)
print('form acquired')
print(form.__repr__)
if form.validate_on_submit():
app.id_region_delivery = form.id_region_delivery.data
"""
app.id_region_delivery = form_data[Model_View_Base.KEY_ID_REGION_DELIVERY]
print(f'id_region_delivery: {app.id_region_delivery}')
return jsonify(Success=True, data={Model_View_Base.KEY_ID_REGION_DELIVERY: app.id_region_delivery})
# currency
@app.route('/store/set_currency', methods=['POST'])
def set_currency():
_m = 'set_currency'
print(f'{_m}\nstarting...')
data = request.json
print(f'data={data}')
# model = Model_View_Store(db, get_info_user(), app)
form_data = data[Model_View_Store.key_form]
print(f'form_data: {form_data}')
"""
form = Form_Currency(**form_data)
print('form acquired')
print(form.__repr__)
if form.validate_on_submit():
app.id_currency = form.id_currency.data
print(f'id_currency: {app.id_currency}')
"""
app.id_currency = form_data[Model_View_Base.KEY_ID_CURRENCY]
print(f'id_currency: {app.id_currency}')
return jsonify(Success=True, data={Model_View_Base.KEY_ID_CURRENCY: app.id_currency})
# User authentication
@app.route("/login")
def login():
print(f'redirect uri: {url_for("login_callback", _external=True)}')
return oauth.auth0.authorize_redirect(
redirect_uri=url_for("login_callback", _external=True)
)
@app.route("/login_callback", methods=["GET", "POST"])
def login_callback():
token = oauth.auth0.authorize_access_token()
session[app.ID_TOKEN_USER] = token
# import user id
print(f'str(type(token)) = {str(type(token))}')
print(f'token = {token}')
userinfo = token.get('userinfo')
print(f'user info: {userinfo}')
# id_user = token.get('sub')
id_user = userinfo.get('sub')
print(f'user ID: {id_user}')
# id_user = get_id_user()
# add user to database
# DataStore_Store(db, userinfo).add_new_user(id_user) # this is part of get basket - should occur on page load
return redirect("/")
@app.route("/logout")
def logout():
session.clear()
return redirect(
"https://" + app.DOMAIN_AUTH0
+ "/v2/logout?"
+ urlencode(
{
"returnTo": url_for("home", _external=True),
"client_id": app.ID_AUTH0_CLIENT,
},
quote_via=quote_plus,
)
)
def get_info_user():
try:
return session[app.ID_TOKEN_USER].get('userinfo') # .get('sub')
except:
return {'sub': ''}
# Onload
if __name__ == '__main__':
# app.run()
app.run(debug=True, host="0.0.0.0", port=5000)

View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Business Objects
Description:
Initialises business objects module.
"""

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.

Binary file not shown.

164
business_objects/basket.py Normal file
View File

@@ -0,0 +1,164 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Basket Business Object
Description:
Business object for basket
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
# from lib import data_types
from business_objects.product import Product, Product_Filters
from business_objects.discount import Discount
from business_objects.delivery_option import Delivery_Option
# from forms import Form_Product
# from models.model_view_store import Model_View_Store # circular
# from datastores.datastore_store import DataStore_Store # circular
# from forms import Form_Basket_Add, Form_Basket_Edit # possibly circular
# external
# from enum import Enum
from flask import jsonify
import locale
# VARIABLE INSTANTIATION
# CLASSES
class Basket_Item():
product: Product
quantity: int
delivery_option: Delivery_Option
discounts: list
# form: Form_Product
is_included_VAT: bool
"""
def __init__(self):
self.is_unavailable_in_currency_or_region = False
self.is_available = True
"""
def make_from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
# Initialiser - validation
_m = 'Basket_Item.make_from_product_and_quantity'
v_arg_type = 'class attribute'
av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
av.full_val_float(quantity, 'quantity', _m, product.get_quantity_min(), v_arg_type=v_arg_type)
basket_item = Basket_Item()
basket_item.product = product
basket_item.quantity = quantity
basket_item.is_included_VAT = is_included_VAT
return basket_item
def add_discount(self, discount):
av.val_instance(discount, 'discount', 'Basket_Item.add_discount', Discount, v_arg_type='class attribute')
self.discounts.append(discount)
def set_delivery_option(self, delivery_option):
av.val_instance(delivery_option, 'delivery_option', 'Basket_Item.set_delivery_option', Delivery_Option, v_arg_type='class attribute')
self.delivery_option = delivery_option
def update_quantity(self, quantity):
_m = 'Basket_Item.update_quantity'
v_arg_type = 'class attribute'
av.full_val_float(quantity, 'quantity', _m, self.product.get_quantity_min(), v_arg_type=v_arg_type)
self.quantity = quantity
def jsonify(self):
return jsonify(self)
def to_json(self):
permutation = self.product.get_permutation_selected()
return {
'product_id': self.product.id_product,
'permutation_id': permutation.id_permutation,
'price': permutation.output_price(self.is_included_VAT),
'quantity': self.quantity
}
def get_subtotal(self):
permutation = self.product.get_permutation_selected()
return round(self.product.get_price_local(self.is_included_VAT) * self.quantity, 2) if permutation.is_available else 0
def output_currency(self):
return self.product.output_currency()
def output_subtotal(self):
locale.setlocale(locale.LC_ALL, '')
subtotal = self.get_subtotal()
permutation = self.product.get_permutation_selected()
return 'Not available in this currency or region' if permutation.is_unavailable_in_currency_or_region else 'Not available' if not permutation.is_available else f'{self.product.output_currency()} {locale.format_string("%d", subtotal, grouping=True)}'
def __repr__(self):
return f'''
product: {self.product}
quantity: {self.quantity}
subtotal: {self.get_subtotal()}
'''
class Basket():
items: list
def __init__(self):
self.items = []
def add_item(self, item):
av.val_instance(item, 'item', 'Basket.add_item', Basket_Item)
self.items.append(item)
def to_csv(self):
ids_permutation = ''
quantities_permutation = ''
for b_i in range(len(self.items)):
basket_item = self.items[b_i]
product = basket_item.product
if b_i > 0:
ids_permutation += ','
quantities_permutation += ','
ids_permutation += str(product.get_id_permutation())
quantities_permutation += str(basket_item.quantity)
print(f'ids_permutation_basket = {ids_permutation}')
print(f'quantities_permutation_basket = {quantities_permutation}')
return ids_permutation, quantities_permutation
def to_json_list(self):
json_list = []
for item in self.items:
json_list.append(item.to_json())
return json_list
def to_json(self):
return {'items': self.to_json_list()}
def output_total(self):
sum = 0
for b_i in self.items:
sum += b_i.get_subtotal()
symbol = self.items[0].output_currency() if len(self.items) > 0 else ''
return f'{symbol} {locale.format_string("%d", sum, grouping=True)}'
def len(self):
return len(self.items)
"""
def get_key_product_index_from_ids_product_permutation(id_product, id_permutation):
return f'{id_product},{"" if id_permutation is None else id_permutation}'
"""
def __repr__(self):
repr = f'Basket:'
for basket_item in self.items:
print(f'{basket_item}')
repr = f'{repr}\n{basket_item}'
return repr
def get_ids_permutation_unavailable(self):
ids_permutation = []
for item in self.items:
permutation = item.product.get_permutation_selected()
if not permutation.is_available:
ids_permutation.append(permutation.id_permutation)
return ids_permutation

View File

@@ -0,0 +1,273 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Category Business Object
Description:
Business object for product
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
from business_objects.product import Product, Product_Permutation, Price
from business_objects.variation import Variation
from business_objects.image import Image
from business_objects.delivery_option import Delivery_Option
from business_objects.discount import Discount
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Enum_Product_Category(Enum):
ASSISTIVE_DEVICES = 0
HOME_DECOR = 1
MISCELLANEOUS = 99
def text(self):
return Enum_Product_Category.Enum_Product_Category_Text(self)
def Enum_Product_Category_Text(category):
av.val_instance(category, 'category', 'Product_Category_Enum_Text', Enum_Product_Category)
if category == Enum_Product_Category.ASSISTIVE_DEVICES:
return 'Assistive devices'
elif category == Enum_Product_Category.HOME_DECOR:
return 'Home decor'
else:
return 'Other'
def get_member_by_text(text):
av.val_str(text, 'text', 'Enum_Product_Category.get_member_by_text')
return data_types.get_enum_member_by_text(Enum_Product_Category, text.upper())
class Category(db.Model):
id_category = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
description = db.Column(db.String(4000))
display_order = db.Column(db.Integer)
"""
def __new__(cls, id, name, description, display_order):
_m = 'Category.__new__'
v_arg_type = 'class attribute'
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
av.val_str(description, 'description', _m, max_len=4001, v_arg_type=v_arg_type)
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
return super(Category, cls).__new__(cls)
"""
def __init__(self): # , id, name, description, display_order):
"""
self.id_category = id
self.name = name
self.description = description
self.display_order = display_order
"""
self.products = []
self.product_index = {}
super().__init__()
def make_from_DB_product(query_row):
category = Category()
category.id_category = query_row[0]
category.name = query_row[1]
category.description = query_row[2]
category.display_order = query_row[3]
return category
"""
def key_product_index_from_ids_product_permutation(id_product, id_permutation):
return f'{id_product},{"" if id_permutation is None else id_permutation}'
def key_product_index_from_product(product):
av.val_instance(product, 'product', 'Category.key_product_index_from_product', Product)
return f'{product.id_product},{"" if product.id_permutation is None else product.id_permutation}'
"""
def get_index_product(self, product):
return self.get_index_product_from_id(product.id_product)
def get_index_product_from_id(self, id_product):
try:
return self.product_index[id_product]
except:
raise KeyError(f'product id: {id_product} not in product index keys: {self.product_index.keys()} with category id: {self.id_category}')
def get_index_product_from_id_permutation(self, id_permutation):
for product in self.products:
try:
index_permutation = product.get_index_permutation_from_id(id_permutation)
return self.get_index_product(product)
except:
pass
raise KeyError(f'permutation id: {id_permutation} not in category id: {self.id_category}')
def add_product(self, product):
_m = 'Category.add_product'
av.val_instance(product, 'product', _m, Product)
# self.product_index.append(len(self.products))
# self.product_index[Category.key_product_index_from_ids_product_permutation(product.id_product, product.id_permutation)] = len(self.products)
try:
self.get_index_product(product)
raise ValueError(f"{av.error_msg_str(product, 'product', _m, Product)}\nProduct already in category.")
except KeyError:
self.product_index[product.id_product] = len(self.products)
self.products.append(product)
def add_permutation(self, permutation):
_m = 'Category.add_permutation'
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
# self.product_index.append(len(self.products))
# self.product_index[Category.key_product_index_from_ids_product_permutation(product.id_product, product.id_permutation)] = len(self.products)
index_product = self.get_index_product_from_id(permutation.id_product)
# index_product = self.product_index[permutation.id_product]
self.products[index_product].add_permutation(permutation)
def add_variation(self, variation):
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
index_product = self.get_index_product_from_id(variation.id_product)
self.products[index_product].add_variation(variation)
def add_price(self, price):
av.val_instance(price, 'price', 'Category.add_price', Price)
index_product = self.get_index_product_from_id(price.id_product)
self.products[index_product].add_price(price)
def add_image(self, image):
av.val_instance(image, 'image', 'Category.add_image', Image)
index_product = self.get_index_product_from_id(image.id_product)
self.products[index_product].add_image(image)
def add_delivery_option(self, delivery_option):
av.val_instance(delivery_option, 'delivery_option', 'Category.add_delivery_option', Delivery_Option)
index_product = self.get_index_product_from_id(delivery_option.id_product)
self.products[index_product].add_delivery_option(delivery_option)
def add_discount(self, discount):
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
index_product = self.get_index_product_from_id(discount.id_product)
self.products[index_product].add_discount(discount)
def get_all_variation_trees(self):
for product in self.products:
if product.has_variations:
print(f'product with id:{product.id_product} has variations')
product.get_variation_trees()
"""
def index_product_from_ids_product_permutation(self, id_product, id_permutation):
key = Category.key_product_index_from_ids_product_permutation(id_product, id_permutation)
print(f'product_index: {self.product_index}')
print(f'Key Error: {key}')
try:
return self.product_index[key]
except KeyError:
pass
"""
def __repr__(self):
return f'''
id: {self.id_category}
name: {self.name}
description: {self.description}
display_order: {self.display_order}
products: {self.products}
'''
def get_permutation_first(self):
if not (len(self.products) == 0):
print(f'getting first permutation from product')
return None if len(self.products) == 0 else self.products[0].get_permutation_selected()
class Product_Category_Filters():
category_ids: str # csv
product_ids: str # csv
def __new__(cls, product_ids, product_categories):
# Initialiser - validation
_m = 'Product_Filters.__new__'
v_arg_type = 'class attribute'
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
return super(Product_Category_Filters, cls).__new__(cls)
def __init__(self, product_ids, product_categories):
# Constructor
self.ids = product_ids
self.categories = product_categories
class Category_List():
categories: list
def __init__(self):
self.categories = []
def add_category(self, category):
av.val_instance(category, 'category', 'Category_List.add_category', Category)
self.categories.append(category)
def get_index_category_from_id(self, id_category):
for index_category in range(len(self.categories)):
category = self.categories[index_category]
if category.id_category == id_category:
return index_category
raise ValueError(f"{av.error_msg_str(id_category, 'id_category', 'Category_List.get_index_category_from_id', int)}\nID not in list")
def get_index_category_from_id_permutation(self, id_permutation):
for index_category in range(len(self.categories)):
category = self.categories[index_category]
try:
index_product = category.get_index_product_from_id_permutation(id_permutation)
return index_category
except:
pass
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Category_List.get_index_category_from_id_permutation', int)}. Permutation ID not in list")
def add_product(self, product):
av.val_instance(product, 'product', 'Category_List.add_product', Product)
index_category = self.get_index_category_from_id(product.id_category)
self.categories[index_category].add_product(product)
def add_permutation(self, permutation):
av.val_instance(permutation, 'permutation', 'Category_List.add_permutation', Product_Permutation)
index_category = self.get_index_category_from_id(permutation.id_category)
self.categories[index_category].add_permutation(permutation)
def add_variation(self, variation):
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
index_category = self.get_index_category_from_id(variation.id_category)
self.categories[index_category].add_variation(variation)
def add_price(self, price):
av.val_instance(price, 'price', 'Category.add_price', Price)
index_category = self.get_index_category_from_id(price.id_category)
self.categories[index_category].add_price(price)
def add_image(self, image):
av.val_instance(image, 'image', 'Category.add_image', Image)
index_category = self.get_index_category_from_id(image.id_category)
self.categories[index_category].add_image(image)
def add_delivery_option(self, delivery_option):
av.val_instance(delivery_option, 'delivery_option', 'Category.add_delivery_option', Delivery_Option)
index_category = self.get_index_category_from_id(delivery_option.id_category)
self.categories[index_category].add_delivery_option(delivery_option)
def add_discount(self, discount):
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
index_category = self.get_index_category_from_id(discount.id_category)
self.categories[index_category].add_discount(discount)
def get_all_variation_trees(self):
for category in self.categories:
category.get_all_variation_trees()
def __repr__(self):
return f'categories: {self.categories}'
def get_permutation_first(self):
print(f'getting first permutation from category list')
if not (len(self.categories) == 0):
print(f'getting first permutation from category')
return None if len(self.categories) == 0 else self.categories[0].get_permutation_first()
def get_count_categories(self):
return len(self.categories)

View File

@@ -0,0 +1,96 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Business Object
Description:
Business object for product
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
"""
class Currency_Enum(Enum):
GBP = 1
def text(self):
return Currency_Enum.Currency_Enum_Text(self)
def Currency_Enum_Text(currency):
av.val_instance(currency, 'currency', 'Currency_Enum_Text', Currency_Enum)
if currency == Currency_Enum.GBP:
return 'GBP'
else:
# return 'Unknown'
raise ValueError("Unknown Currency Enum.")
def get_member_by_text(text):
for member in Resolution_Level_Enum.__members__.values():
if member.name == text:
return member
raise ValueError("Unknown Currency Enum.")
# return Resolution_Level_Enum.HIGH
"""
class Currency(db.Model):
id_currency = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String)
name = db.Column(db.String)
symbol = db.Column(db.String)
factor_from_GBP = db.Column(db.Float)
display_order = db.Column(db.Integer)
def make_from_DB_currency(query_row):
# _m = 'Currency.make_from_DB_currency'
# v_arg_type = 'class attribute'
currency = Currency()
currency.id_currency = query_row[0]
currency.code = query_row[1]
currency.name = query_row[2]
currency.symbol = query_row[3]
currency.factor_from_GBP = query_row[4]
currency.display_order = query_row[5]
return currency
"""
def make_from_DB_product(query_row):
_m = 'Currency.make_from_DB_product'
v_arg_type = 'class attribute'
currency = Currency()
currency.id_permutation = query_row[0]
currency.id_product = query_row[1]
currency.id_category = query_row[2]
currency.id_variation = query_row[3]
return currency
"""
def __repr__(self):
return f'''
id: {self.id_currency}
name: {self.name}
code: {self.code}
symbol: {self.symbol}
factor from GBP: {self.factor_from_GBP}
display_order: {self.display_order}
'''

View File

@@ -0,0 +1,115 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Delivery Option Business Object
Description:
Business object for delivery option
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
"""
class Delivery_Option():
name: str
delay_min: int # days
delay_max: int
quantity_min: float
quantity_max: float
regions: list # [Enum_Delivery_Region]
cost: float
def __new__(cls, name, delay_min, delay_max, quantity_min, quantity_max, regions, cost):
_m = 'Delivery_Option.__new__'
v_arg_type = 'class attribute'
av.val_str(name, 'name', _m, v_arg_type = v_arg_type)
av.val_int(delay_min, 'delay_min', _m, 0, v_arg_type = v_arg_type)
av.val_int(delay_max, 'delay_max', _m, 0, v_arg_type = v_arg_type)
av.val_float(quantity_min, 'quantity_min', _m, 0, v_arg_type = v_arg_type)
av.val_float(quantity_max, 'quantity_max', _m, 0, v_arg_type = v_arg_type)
av.val_list_instances(regions, 'regions', _m, Enum_Delivery_Region, v_arg_type = v_arg_type)
av.val_float(cost, 'cost', _m, 0, v_arg_type = v_arg_type)
return super(Delivery_Option, cls).__new__(cls)
def __init__(self, name, delay_min, delay_max, quantity_min, quantity_max, regions, cost):
self.name = name
self.delay_min = delay_min
self.delay_max = delay_max
self.quantity_min = quantity_min
self.quantity_max = quantity_max
self.regions = regions
self.cost = cost
"""
class Delivery_Option(db.Model):
id_option = db.Column(db.Integer, primary_key=True)
id_product = db.Column(db.Integer)
id_permutation = db.Column(db.Integer)
id_category = db.Column(db.Integer)
code = db.Column(db.String(50))
name = db.Column(db.String(100))
latency_min = db.Column(db.Integer)
latency_max = db.Column(db.Integer)
quantity_min = db.Column(db.Integer)
quantity_max = db.Column(db.Integer)
codes_region = db.Column(db.String(4000))
names_region = db.Column(db.String(4000))
price_GBP = db.Column(db.Float)
display_order = db.Column(db.Integer)
def __init__(self):
self.delivery_regions = []
def make_from_DB_product(query_row):
option = Delivery_Option()
option.id_option = query_row[0]
option.id_product = query_row[1]
option.id_permutation = query_row[2]
option.id_category = query_row[3]
option.code = query_row[4]
option.name = query_row[5]
option.latency_min = query_row[6]
option.latency_max = query_row[7]
option.quantity_min = query_row[8]
option.quantity_max = query_row[9]
option.codes_region = query_row[10]
option.names_region = query_row[11]
option.price_GBP = query_row[12]
option.display_order = query_row[13]
return option
def __repr__(self):
return f'''
id: {self.id_option}
id_product: {self.id_product}
id_category: {self.id_category}
name: {self.name}
code: {self.code}
latency_min: {self.latency_min}
latency_max: {self.latency_max}
quantity_min: {self.quantity_min}
quantity_max: {self.quantity_max}
codes_region: {self.codes_region}
names_region: {self.names_region}
price_GBP: {self.price_GBP}
display_order: {self.display_order}
'''

View File

@@ -0,0 +1,96 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Delivery Region Business Object
Description:
Business object for delivery region
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Enum_Delivery_Region(Enum):
UK = 0
class Delivery_Region(db.Model):
id_region = db.Column(db.Integer, primary_key=True)
"""
id_category = db.Column(db.Integer)
id_product = db.Column(db.Integer)
id_discount = db.Column(db.Integer)
"""
code = db.Column(db.String(50))
name = db.Column(db.String(200))
active = db.Column(db.Boolean)
display_order = db.Column(db.Integer)
"""
def __new__(cls, id, id_category, id_product, id_discount, code, name, display_order):
_m = 'Delivery_Region.__new__'
v_arg_type = 'class attribute'
av.val_int(id, 'id', _m, 0, v_arg_type = v_arg_type)
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
av.val_int(id_product, 'id_product', _m, 0, v_arg_type = v_arg_type)
av.val_int(id_discount, 'id_discount', _m, v_arg_type = v_arg_type)
av.val_str(code, 'code', _m, max_len = 50, v_arg_type = v_arg_type)
av.val_str(name, 'name', _m, max_len = 100, v_arg_type = v_arg_type)
av.val_int(display_order, 'display_order', _m, v_arg_type = v_arg_type)
return super(Delivery_Region, cls).__new__(cls)
def __init__(self, id, id_category, id_product, id_discount, code, name, display_order):
self.id_region = id
self.id_category = id_category
self.id_product = id_product
self.id_discount = id_discount
self.name = name
self.code = code
self.display_order = display_order
"""
def make_from_DB_product(query_row):
region = Delivery_Region()
region.id_region = query_row[0]
region.name = query_row[1]
region.code = query_row[2]
# self.display_order = query_row[3]
return region
def make_from_DB_region(query_row):
region = Delivery_Region()
region.id_region = query_row[0]
region.code = query_row[1]
region.name = query_row[2]
region.active = query_row[3]
region.display_order = query_row[4]
return region
def __repr__(self):
return f'''
id: {self.id_region}
name: {self.name}
code: {self.code}
active: {self.active}
display_order: {self.display_order}
'''

View File

@@ -0,0 +1,92 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Discount Business Object
Description:
Business object for discount
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Discount(db.Model):
id_discount = db.Column(db.Integer, primary_key=True)
id_category = db.Column(db.Integer)
id_product = db.Column(db.Integer)
id_permutation = db.Column(db.Integer)
code = db.Column(db.String(50))
name = db.Column(db.String(200))
multiplier = db.Column(db.Float)
subtractor = db.Column(db.Float)
apply_multiplier_first = db.Column(db.Boolean)
quantity_min = db.Column(db.Integer)
quantity_max = db.Column(db.Integer)
date_start = db.Column(db.DateTime)
date_end = db.Column(db.DateTime)
codes_region = db.Column(db.String(4000))
names_region = db.Column(db.String(4000))
codes_currency = db.Column(db.String(4000))
names_currency = db.Column(db.String(4000))
display_order = db.Column(db.Integer)
def __init__(self):
self.delivery_regions = []
def make_from_DB_product(query_row):
discount = Discount()
discount.id_discount = query_row[0]
discount.id_category = query_row[1]
discount.id_product = query_row[2]
discount.id_permutation = query_row[3]
discount.code = query_row[4]
discount.name = query_row[5]
discount.multiplier = query_row[6]
discount.subtractor = query_row[7]
discount.apply_multiplier_first = query_row[8]
discount.quantity_min = query_row[9]
discount.quantity_max = query_row[10]
discount.date_start = query_row[11]
discount.date_end = query_row[12]
discount.codes_region = query_row[13]
discount.names_region = query_row[14]
discount.codes_currency = query_row[15]
discount.names_currency = query_row[16]
discount.display_order = query_row[17]
return discount
def __repr__(self):
return f'''
id: {self.id_discount}
id_category: {self.id_category}
id_product: {self.id_product}
name: {self.name}
code: {self.code}
multiplier: {self.multiplier}
quantity_min: {self.quantity_min}
quantity_max: {self.quantity_max}
date_start: {self.date_start}
date_end: {self.date_end}
display_order: {self.display_order}
'''

132
business_objects/image.py Normal file
View File

@@ -0,0 +1,132 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Image Business Object
Description:
Business object for product image
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Resolution_Level_Enum(Enum):
THUMBNAIL = 0
LOW = 1
HIGH = 2
FULL = 3
def text(self):
return Resolution_Level_Enum.Resolution_Level_Enum_Text(self)
def Resolution_Level_Enum_Text(category):
av.val_instance(category, 'category', 'Resolution_Level_Enum_Text', Resolution_Level_Enum)
if category == Resolution_Level_Enum.THUMBNAIL:
return 'Thumbnail'
elif category == Resolution_Level_Enum.LOW:
return 'Low resolution'
elif category == Resolution_Level_Enum.HIGH:
return 'High resolution'
elif category == Resolution_Level_Enum.FULL:
return 'Full resolution'
else:
return 'Unknown'
def get_member_by_text(text):
for member in Resolution_Level_Enum.__members__.values():
if member.name == text:
return member
return Resolution_Level_Enum.HIGH
class Image(db.Model):
id_image = db.Column(db.Integer, primary_key=True)
id_product = db.Column(db.Integer)
id_permutation = db.Column(db.Integer)
id_category = db.Column(db.Integer)
url = db.Column(db.String(255))
active = db.Column(db.Boolean)
display_order = db.Column(db.Integer)
"""
def __new__(cls, id, id_product, id_category, url, display_order):
_m = 'Image.__new__'
v_arg_type = 'class attribute'
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
av.val_int(id_product, 'id_product', _m, 0, v_arg_type=v_arg_type)
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
av.val_str(url, 'url', _m, max_len=254, v_arg_type=v_arg_type)
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
return super(Image, cls).__new__(cls)
def __init__(self, id, id_product, id_category, url, display_order):
self.id_image = id
self.id_product = id_product
self.id_category = id_category
self.url = url
self.display_order = display_order
super().__init__()
"""
def make_from_DB_product(query_row):
_m = 'Image.make_from_DB_product'
# print(f'image: {query_row}')
image = Image()
image.id_image = query_row[0]
image.id_product = query_row[1]
image.id_permutation = query_row[2]
image.id_category = query_row[3]
image.url = query_row[4]
image.active = query_row[5]
image.display_order = query_row[6]
return image
def __repr__(self):
return f'''
id: {self.id_image}
id_product: {self.id_product}
id_category: {self.id_category}
url: {self.url}
display_order: {self.display_order}
'''
class Product_Image_Filters():
product_id: int
get_thumbnail: bool
get_remaining_LQ: bool
def __new__(cls, product_id, get_thumbnail, get_remaining_LQ):
# Initialiser - validation
_m = 'Product_Filters.__new__'
v_arg_type = 'class attribute'
av.val_int(product_id, 'product_id', _m, v_arg_type=v_arg_type)
av.val_bool(get_thumbnail, 'get_thumbnail', _m, v_arg_type=v_arg_type)
av.val_bool(get_remaining_LQ, 'get_remaining_LQ', _m, v_arg_type=v_arg_type)
return super(Product, cls).__new__(cls)
def __init__(self, product_id, get_thumbnail, get_remaining_LQ):
# Constructor
self.product_id = product_id
self.get_thumbnail = get_thumbnail
self.get_remaining_LQ = get_remaining_LQ

93
business_objects/order.py Normal file
View File

@@ -0,0 +1,93 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Order Business Object
Description:
Business object for order
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
# from lib import data_types
from business_objects.product import Product
from business_objects.delivery_option import Delivery_Option
# from forms import Form_Product
# from models.model_view_store import Model_View_Store # circular
# external
# from enum import Enum
from flask import jsonify
import locale
# VARIABLE INSTANTIATION
# CLASSES
class Order():
category: str
product: Product
quantity: int
subtotal: float
delivery_option: Delivery_Option
# form: Form_Product
def __new__(cls, category, product, quantity):
# Initialiser - validation
_m = 'Product.__new__'
v_arg_type = 'class attribute'
av.val_str(category, 'category', _m, v_arg_type=v_arg_type)
av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
av.full_val_float(quantity, 'quantity', _m, product.quantity_min, v_arg_type=v_arg_type)
return super(Basket_Item, cls).__new__(cls)
def __init__(self, category, product, quantity):
# Constructor
self.category = category
self.product = product
self.quantity = quantity
self.subtotal = round(self.product.price_GBP_full * self.quantity, 2)
"""
self.form = Form_Product()
if self.form.validate_on_submit():
# Handle form submission
pass
"""
def update_quantity(self, quantity):
_m = 'Basket_Item.update_quantity'
v_arg_type = 'class attribute'
av.full_val_float(quantity, 'quantity', _m, self.product.quantity_min, v_arg_type=v_arg_type)
self.quantity = quantity
self.subtotal = round(self.product.price_GBP_full * self.quantity, 2)
def jsonify(self):
return jsonify(self)
def to_json(self):
return {
'product_id': self.product.id_product,
'price': self.product.price_GBP_full,
'quantity': self.quantity
}
def output_subtotal(self):
locale.setlocale(locale.LC_ALL, '')
return locale.format_string("%d", self.subtotal, grouping=True)
def __repr__(self):
return f'''
category: {self.category}
product: {self.product}
quantity: {self.quantity}
subtotal: {self.subtotal}
'''

704
business_objects/product.py Normal file
View File

@@ -0,0 +1,704 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Business Object
Description:
Business object for product
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
from business_objects.discount import Discount
from business_objects.variation import Variation
from business_objects.image import Image
from business_objects.delivery_option import Delivery_Option
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
from dataclasses import dataclass
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Enum_Status_Stock(Enum):
OUT = 0
LOW = 1
IN = 99
def text(self):
return Enum_Status_Stock.Enum_Status_Stock_Text(self)
def Enum_Status_Stock_Text(status):
av.val_instance(status, 'category', 'Enum_Status_Stock_Text', Enum_Status_Stock)
if status == Enum_Status_Stock.OUT:
return 'Out of stock'
elif status == Enum_Status_Stock.LOW:
return 'Low on stock'
else:
return 'Fully stocked'
def get_member_by_text(text):
return data_types.get_enum_member_by_text(Enum_Status_Stock, text.upper())
class Variation_Tree_Node():
variation: Variation
node_parent: None
nodes_child: list
def __init__(self):
self.nodes_child = []
def make_from_variation_and_node_parent(variation, node_parent):
node = Variation_Tree_Node()
node.variation = variation
node.node_parent = node_parent
return node
def make_from_node_parent(node_parent):
node = Variation_Tree_Node()
node.node_parent = node_parent
return node
def add_child(self, node_child):
self.nodes_child.append(node_child)
def is_leaf(self):
return (len(self.nodes_child) == 0)
class Variation_Tree:
node_root: Variation_Tree_Node
def make_from_node_root(node_root):
tree = Variation_Tree()
tree.node_root = node_root
return tree
def get_variation_type_list(self):
variation_types = []
node = self.node_root
at_leaf_node = node.is_leaf()
while not at_leaf_node:
variation_types.append(node.variation.name_variation_type)
at_leaf_node = node.is_leaf()
if not at_leaf_node:
node = node.nodes_child[0]
return variation_types
def is_equal(self, tree):
my_type_list = self.get_variation_type_list()
sz_me = len(my_type_list)
other_type_list = tree.get_variation_type_list()
sz_other = len(other_type_list)
is_equal = (sz_me == sz_other)
if is_equal:
for index_type in range(sz_me):
if sz_me[index_type] != sz_other[index_type]:
is_equal = False
break
return is_equal
def make_from_product_permutation(product_permutation):
depth_max = len(product_permutation.variations)
node_root = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[0], None)
node = node_root
for depth in range(depth_max - 1):
node = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[depth + 1], node)
return Variation_Tree.make_from_node_root(node_root)
class Product(db.Model):
id_product = db.Column(db.Integer, primary_key=True)
id_category = db.Column(db.Integer)
name = db.Column(db.String(255))
display_order = db.Column(db.Integer)
can_view = db.Column(db.Boolean)
can_edit = db.Column(db.Boolean)
can_admin = db.Column(db.Boolean)
# form_basket_add: Form_Basket_Add
# form_basket_edit: Form_Basket_Edit
# has_variations: bool
# index_permutation_selected: int
def __init__(self):
self.permutations = []
self.permutation_index = {}
self.variation_trees = []
self.index_permutation_selected = None
self.has_variations = False
super().__init__()
self.form_basket_add = Form_Basket_Add()
self.form_basket_edit = Form_Basket_Edit()
def make_from_DB_product(query_row):
_m = 'Product.make_from_DB_product'
v_arg_type = 'class attribute'
product = Product()
product.id_product = query_row[0]
product.id_category = query_row[5]
product.name = query_row[2]
product.has_variations = av.input_bool(query_row[4], "has_variations", _m, v_arg_type=v_arg_type)
product.display_order = query_row[17]
product.can_view = av.input_bool(query_row[19], "can_view", _m, v_arg_type=v_arg_type)
product.can_edit = av.input_bool(query_row[20], "can_edit", _m, v_arg_type=v_arg_type)
product.can_admin = av.input_bool(query_row[21], "can_admin", _m, v_arg_type=v_arg_type)
return product
"""
def make_from_permutation(permutation, has_variations = False):
_m = 'Product.make_from_permutation'
v_arg_type = 'class attribute'
av.val_instance(permutation, 'permutation', _m, Product_Permutation, v_arg_type=v_arg_type)
product = Product()
product.has_variations = has_variations
product.index_permutation_selected = 0
product.id_product = permutation.id_product
product.id_category = permutation.id_category
product.display_order = permutation.display_order
product.can_view = permutation.can_view
product.can_edit = permutation.can_edit
product.can_admin = permutation.can_admin
product.permutations.append(permutation)
# product.get_variation_trees()
return product
"""
def add_permutation(self, permutation):
_m = 'Product.add_permutation'
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
try:
self.permutation_index[permutation.id_permutation]
raise ValueError(f"{av.error_msg_str(permutation, 'permutation', _m, Product_Permutation)}\nPermutation ID already in product")
except KeyError:
self.permutation_index[permutation.id_permutation] = len(self.permutations)
self.permutations.append(permutation)
"""
if self.has_variations:
self.has_variations = False
"""
if self.index_permutation_selected is None:
self.index_permutation_selected = self.permutation_index[permutation.id_permutation]
print(f'setting selected permutation for product {self.id_product} to {self.index_permutation_selected}') # :\n{self.permutations[self.index_permutation_selected]}
"""
def make_from_permutations(permutations):
_m = 'Product.make_from_permutations'
v_arg_type = 'class attribute'
if len(permutations) == 0:
raise ValueError(av.error_msg_str(permutations, 'permutations', _m, list, v_arg_type=v_arg_type))
product = Product()
product.has_variations = True
product.index_permutation_selected = 0
product.id_product = permutations[0].id_product
product.id_category = permutations[0].id_category
product.display_order = permutations[0].display_order
product.can_view = True
product.can_edit = True
product.can_admin = True
for permutation in permutations:
product.can_view &= permutation.can_view
product.can_edit &= permutation.can_edit
product.can_admin &= permutation.can_admin
product.permutations.append(permutations)
product.get_variation_trees()
return product
"""
def get_variation_trees(self):
for index_permutation in range(len(self.permutations)):
variation_tree = Variation_Tree.make_from_product_permutation(self.permutations[index_permutation])
found_variation_tree_match = False
for index_tree in range(len(self.variation_trees)):
if self.variation_trees[index_tree].is_equal(variation_tree):
found_variation_tree_match = True
break
if not found_variation_tree_match:
self.variation_trees.append(variation_tree)
def make_from_DB_Stripe_product(query_row):
permutation = Product_Permutation.make_from_DB_Stripe_product(query_row)
product = Product.make_from_permutation(permutation)
return product
def make_from_DB_Stripe_price(query_row):
permutation = Product_Permutation.make_from_DB_Stripe_price(query_row)
product = Product.make_from_permutation(permutation)
return product
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
permutation = Product_Permutation.make_from_json(json_basket_item, key_id_product, key_id_permutation)
product = Product.make_from_permutation(permutation)
return product
def get_permutation_selected(self):
try:
return self.permutations[self.index_permutation_selected]
except:
raise ValueError(f'list index {self.index_permutation_selected} out of range')
def output_lead_time(self):
return self.get_permutation_selected().output_lead_time()
def output_delivery_date(self):
return self.get_permutation_selected().output_delivery_date()
def output_price(self, is_included_VAT):
return self.get_permutation_selected().output_price(is_included_VAT)
def output_price_VAT_incl(self):
return self.get_permutation_selected().output_price(True)
def output_price_VAT_excl(self):
return self.get_permutation_selected().output_price(False)
def get_price_local(self, is_included_VAT):
if is_included_VAT:
return self.get_price_local_VAT_incl()
else:
return self.get_price_local_VAT_excl()
def get_price_local_VAT_incl(self):
return self.get_permutation_selected().get_price_local_VAT_incl()
def get_price_local_VAT_excl(self):
return self.get_permutation_selected().get_price_local_VAT_excl()
def get_quantity_min(self):
return self.get_permutation_selected().quantity_min
def get_id_permutation(self):
return self.get_permutation_selected().id_permutation
def get_image_from_index(self, index_image):
return self.get_permutation_selected().images[index_image]
def get_name(self):
return self.get_permutation_selected().name
def get_description(self):
return self.get_permutation_selected().description
def output_currency(self):
return self.get_permutation_selected().get_price().symbol_currency
"""
def add_form_basket_add(self):
self.form_basket_add = None
def add_form_basket_edit(self):
self.form_basket_edit = None
"""
def __repr__(self):
return f'''Product
id_product: {self.id_product}
id_category: {self.id_category}
name: {self.name}
display_order: {self.display_order}
can_view: {self.can_view}
can_edit: {self.can_edit}
can_admin: {self.can_admin}
has_variations: {self.has_variations}
permutations: {self.permutations}
variation trees: {self.variation_trees}
'''
"""
def get_index_permutation_from_id(self, id_permutation):
if id_permutation is None and not self.has_variations:
return 0
for index_permutation in range(len(self.permutations)):
permutation = self.permutations[index_permutation]
if permutation.id_permutation == id_permutation:
return index_permutation
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Product.get_index_permutation_from_id', int)}\nPermutation ID not found.")
"""
def add_variation(self, variation):
av.val_instance(variation, 'variation', 'Product.add_variation', Variation)
# print(f'variation: {variation}')
index_permutation = self.permutation_index[variation.id_permutation] # self.get_index_permutation_from_id(variation.id_permutation)
self.permutations[index_permutation].add_variation(variation)
def add_price(self, price):
av.val_instance(price, 'price', 'Product.add_price', Price)
index_permutation = self.permutation_index[price.id_permutation] # self.get_index_permutation_from_id(price.id_permutation)
self.permutations[index_permutation].add_price(price)
def add_image(self, image):
av.val_instance(image, 'image', 'Product.add_image', Image)
index_permutation = self.permutation_index[image.id_permutation] # self.get_index_permutation_from_id(image.id_permutation)
self.permutations[index_permutation].add_image(image)
def add_delivery_option(self, delivery_option):
av.val_instance(delivery_option, 'delivery_option', 'Product.add_delivery_option', Delivery_Option)
index_permutation = self.permutation_index[delivery_option.id_permutation] # self.get_index_permutation_from_id(delivery_option.id_permutation)
self.permutations[index_permutation].add_delivery_option(delivery_option)
def add_discount(self, discount):
av.val_instance(discount, 'discount', 'Product.add_discount', Discount)
index_permutation = self.permutation_index[discount.id_permutation] # self.get_index_permutation_from_id(discount.id_permutation)
self.permutations[index_permutation].add_discount(discount)
class Product_Permutation(db.Model):
id_product = db.Column(db.Integer, primary_key=True)
id_permutation = db.Column(db.Integer, primary_key=True)
# name = db.Column(db.String(255))
description = db.Column(db.String(4000))
# price_GBP_full = db.Column(db.Float)
# price_GBP_min = db.Column(db.Float)
has_variations = db.Column(db.Boolean)
id_category = db.Column(db.Integer)
latency_manufacture = db.Column(db.Integer)
quantity_min = db.Column(db.Float)
quantity_max = db.Column(db.Float)
quantity_step = db.Column(db.Float)
quantity_stock = db.Column(db.Float)
id_stripe_product = db.Column(db.String(100))
is_subscription = db.Column(db.Boolean)
name_recurrence_interval = db.Column(db.String(255))
name_plural_recurrence_interval = db.Column(db.String(256))
count_recurrence_interval = db.Column(db.Integer)
display_order = db.Column(db.Integer)
can_view = db.Column(db.Boolean)
can_edit = db.Column(db.Boolean)
can_admin = db.Column(db.Boolean)
# form_basket_add: Form_Basket_Add
# form_basket_edit: Form_Basket_Edit
# is_unavailable_in_currency_or_region: bool
# is_available: bool
def __init__(self):
self.variations = []
self.variation_index = {}
self.prices = []
self.price_index = {}
self.images = []
self.image_index = {}
self.delivery_options = []
self.delivery_option_index = {}
self.discounts = []
self.discount_index = {}
super().__init__()
self.form_basket_add = Form_Basket_Add()
self.form_basket_edit = Form_Basket_Edit()
self.is_unavailable_in_currency_or_region = False
self.is_available = True
def make_from_DB_product(query_row):
_m = 'Product_Permutation.make_from_DB_product'
v_arg_type = 'class attribute'
print(f'query_row: {query_row}')
permutation = Product_Permutation()
permutation.id_product = query_row[0]
permutation.id_permutation = query_row[1]
# permutation.name = query_row[2]
permutation.description = query_row[3]
# permutation.price_GBP_full = query_row[4]
# permutation.price_GBP_min = query_row[5]
permutation.has_variations = query_row[4]
permutation.id_category = query_row[5]
permutation.latency_manufacture = query_row[6]
permutation.quantity_min = query_row[7]
permutation.quantity_max = query_row[8]
permutation.quantity_step = query_row[9]
permutation.quantity_stock = query_row[10]
permutation.id_stripe_product = query_row[11]
permutation.is_subscription = av.input_bool(query_row[12], "is_subscription", _m, v_arg_type=v_arg_type)
permutation.name_recurrence_interval = query_row[13]
permutation.name_plural_recurrence_interval = query_row[14]
permutation.count_recurrence_interval = query_row[15]
permutation.display_order = query_row[18]
permutation.can_view = av.input_bool(query_row[19], "can_view", _m, v_arg_type=v_arg_type)
permutation.can_edit = av.input_bool(query_row[20], "can_edit", _m, v_arg_type=v_arg_type)
permutation.can_admin = av.input_bool(query_row[21], "can_admin", _m, v_arg_type=v_arg_type)
return permutation
def make_from_DB_Stripe_product(query_row):
_m = 'Product_Permutation.make_from_DB_Stripe_product'
v_arg_type = 'class attribute'
permutation = Product_Permutation()
permutation.id_product = query_row[0]
# permutation.name = query_row[1]
permutation.description = query_row[2]
return permutation
def make_from_DB_Stripe_price(query_row):
_m = 'Product_Permutation.make_from_DB_Stripe_price'
v_arg_type = 'class attribute'
permutation = Product_Permutation()
permutation.id_product = query_row[0]
# permutation.price_GBP_full = query_row[1]
permutation.id_stripe_product = query_row[2]
permutation.is_subscription = av.input_bool(query_row[3], "is_subscription", _m, v_arg_type=v_arg_type)
permutation.name_recurrence_interval = query_row[4]
permutation.count_recurrence_interval = query_row[5]
return permutation
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
_m = 'Product_Permutation.make_from_json'
v_arg_type = 'class attribute'
permutation = Product_Permutation()
permutation.id_product = json_basket_item[key_id_product]
permutation.id_permutation = json_basket_item[key_id_permutation]
return permutation
def get_price(self):
return self.prices[0]
def get_price_local_VAT_incl(self):
price = self.get_price()
return price.value_local_VAT_incl
def get_price_local_VAT_excl(self):
price = self.get_price()
return price.value_local_VAT_excl
def output_lead_time(self):
return '1 day' if self.latency_manufacture == 1 else f'{self.latency_manufacture} days'
def output_delivery_date(self):
return (datetime.now() + timedelta(days=self.latency_manufacture)).strftime('%A, %d %B %Y')
def output_price(self, is_included_VAT):
if self.is_unavailable_in_currency_or_region:
return 'Not available in currency and region'
if not self.is_available:
return 'Not available'
price = self.get_price()
locale.setlocale(locale.LC_ALL, '')
if is_included_VAT:
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_incl, grouping=True)}'
else:
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_excl, grouping=True)}'
def output_currency(self):
if not self.is_available:
return ''
price = self.get_price()
return price.code_currency
"""
def output_price_VAT_incl(self):
locale.setlocale(locale.LC_ALL, '')
return locale.format_string("%d", self.price_GBP_VAT_incl, grouping=True)
def output_price_VAT_excl(self):
locale.setlocale(locale.LC_ALL, '')
return locale.format_string("%d", self.price_GBP_VAT_excl, grouping=True)
def add_form_basket_add(self):
self.form_basket_add = None
def add_form_basket_edit(self):
self.form_basket_edit = None
"""
def __repr__(self):
return f'''Product_Permutation
id_product: {self.id_product}
id_permutation: {self.id_permutation}
description: {self.description}
id_category: {self.id_category}
latency_manufacture: {self.latency_manufacture}
quantity_min: {self.quantity_min}
quantity_max: {self.quantity_max}
quantity_step: {self.quantity_step}
quantity_stock: {self.quantity_stock}
id_stripe_product: {self.id_stripe_product}
is_subscription: {self.is_subscription}
name_recurrence_interval: {self.name_recurrence_interval}
name_plural_recurrence_interval: {self.name_plural_recurrence_interval}
count_recurrence_interval: {self.count_recurrence_interval}
display_order: {self.display_order}
can_view: {self.can_view}
can_edit: {self.can_edit}
can_admin: {self.can_admin}
variations: {self.variations}
images: {self.images}
delivery_options: {self.delivery_options}
prices: {self.prices}
'''
"""
price_GBP_full: {self.price_GBP_full}
price_GBP_min: {self.price_GBP_min}
"""
def add_variation(self, variation):
_m = 'Product_Permutation.add_variation'
av.val_instance(variation, 'variation', _m, Variation)
try:
self.variation_index[variation.id_variation]
raise ValueError(f"{av.error_msg_str(variation, 'variation', _m, Variation)}\nVariation already in product.")
except KeyError:
self.variation_index[variation.id_variation] = len(self.variations)
self.variations.append(variation)
def add_price(self, price):
_m = 'Product_Permutation.add_price'
av.val_instance(price, 'price', _m, Price)
try:
self.price_index[price.display_order]
raise ValueError(f"{av.error_msg_str(price, 'price', _m, Price)}\nPrice already in product.")
except KeyError:
self.price_index[price.display_order] = len(self.prices)
self.prices.append(price)
def add_image(self, image):
_m = 'Product_Permutation.add_image'
av.val_instance(image, 'image', _m, Image)
try:
self.image_index[image.id_image]
raise ValueError(f"{av.error_msg_str(image, 'image', _m, Image)}\nImage already in product.")
except KeyError:
self.image_index[image.id_image] = len(self.images)
self.images.append(image)
def add_delivery_option(self, delivery_option):
_m = 'Product_Permutation.add_delivery_option'
av.val_instance(delivery_option, 'delivery_option', _m, Delivery_Option)
try:
self.delivery_option_index[delivery_option.id_option]
raise ValueError(f"{av.error_msg_str(delivery_option, 'delivery_option', _m, Delivery_Option)}\nDelivery_Option already in product.")
except KeyError:
self.delivery_option_index[delivery_option.id_option] = len(self.delivery_options)
self.delivery_options.append(delivery_option)
def add_discount(self, discount):
_m = 'Product_Permutation.add_discount'
av.val_instance(discount, 'discount', _m, Discount)
try:
self.discount_index[discount.display_order]
raise ValueError(f"{av.error_msg_str(discount, 'discount', _m, Discount)}\nDiscount already in product.")
except KeyError:
self.discount_index[discount.display_order] = len(self.discounts)
self.discounts.append(discount)
def get_image_from_index(self, index_image):
try:
return self.images[index_image]
except:
raise IndexError(f"Invalid image index: {index_image}")
def get_price_from_code_currency(self, code_currency):
for price in self.prices:
if price.code_currency == code_currency:
return price
"""
class Product_Filters():
ids: str # csv
categories: str # csv
def __new__(cls, product_ids, product_categories):
# Initialiser - validation
_m = 'Product_Filters.__new__'
v_arg_type = 'class attribute'
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
return super(Product, cls).__new__(cls)
def __init__(self, product_ids, product_categories):
# Constructor
self.ids = product_ids
self.categories = product_categories
class Price():
product: Product
currency: Currency_Enum
def make_from_product_currency(product, code_currency):
name_method = 'make_from_product_currency'
av.val_instance(product, 'product', name_method, Product)
price = Price()
price.product = product
price.currency = Currency_Enum.get_member_by_text(code_currency)
"""
class Price(db.Model):
id_price = db.Column(db.Integer)
id_product = db.Column(db.Integer)
id_permutation = db.Column(db.Integer)
id_category = db.Column(db.Integer)
id_currency = db.Column(db.Integer)
code_currency = db.Column(db.String)
name_currency = db.Column(db.String)
symbol_currency = db.Column(db.String)
id_region = db.Column(db.Integer)
value_local_VAT_incl = db.Column(db.Float)
value_local_VAT_excl = db.Column(db.Float)
display_order = db.Column(db.Float, primary_key=True)
def make_from_DB_product(query_row):
_m = 'Price.make_from_DB_product'
price = Price()
price.id_price = query_row[0]
price.id_permutation = query_row[1]
price.id_product = query_row[2]
price.id_category = query_row[3]
price.id_currency = query_row[4]
price.code_currency = query_row[5]
price.name_currency = query_row[6]
price.symbol_currency = query_row[7]
price.id_region = query_row[8]
price.value_local_VAT_incl = query_row[9]
price.value_local_VAT_excl = query_row[10]
price.display_order = query_row[11]
return price
def __repr__(self):
return f'''Price
id: {self.id_price}
id_permutation: {self.id_permutation}
id_product: {self.id_product}
id_category: {self.id_category}
id_currency: {self.id_currency}
code_currency: {self.code_currency}
name_currency: {self.name_currency}
symbol_currency: {self.symbol_currency}
id_region: {self.id_region}
value_local (VAT incl): {self.value_local_VAT_incl}
value_local (VAT excl): {self.value_local_VAT_excl}
display_order (UID): {self.display_order}
'''
"""
class Permutation_Variation_Link(db.Model):
id_permutation = db.Column(db.Integer)
id_product = db.Column(db.Integer)
id_category = db.Column(db.Integer)
id_variation = db.Column(db.Integer)
def make_from_DB_product(query_row):
_m = 'Permutation_Variation_Link.make_from_DB_product'
v_arg_type = 'class attribute'
link = Permutation_Variation_Link()
link.id_permutation = query_row[0]
link.id_product = query_row[1]
link.id_category = query_row[2]
link.id_variation = query_row[3]
return link
"""
@dataclass
class Product_Filters():
id_user: str
get_all_category: bool
ids_category: str
get_inactive_category: bool
get_all_product: bool
ids_product: str
get_inactive_product: bool
get_first_product_only: bool
get_all_permutation: bool
ids_permutation: str
get_inactive_permutation: bool
get_all_image: bool
ids_image: str
get_inactive_image: bool
get_first_image_only: bool
get_all_region: bool
ids_region: str
get_inactive_region: bool
get_all_currency: bool
ids_currency: str
get_inactive_currency: bool
get_all_discount: bool
ids_discount: str
get_inactive_discount: bool
def to_json(self):
return {
'a_id_user': self.id_user,
'a_get_all_category': self.get_all_category,
'a_ids_category': self.ids_category,
'a_get_inactive_category': self.get_inactive_category,
'a_get_all_product': self.get_all_product,
'a_ids_product': self.ids_product,
'a_get_inactive_product': self.get_inactive_product,
'a_get_first_product_only': self.get_first_product_only,
'a_get_all_permutation': self.get_all_permutation,
'a_ids_permutation': self.ids_permutation,
'a_get_inactive_permutation': self.get_inactive_permutation,
'a_get_all_image': self.get_all_image,
'a_ids_image': self.ids_image,
'a_get_inactive_image': self.get_inactive_image,
'a_get_first_image_only': self.get_first_image_only,
'a_get_all_delivery_region': self.get_all_region,
'a_ids_delivery_region': self.ids_region,
'a_get_inactive_delivery_region': self.get_inactive_region,
'a_get_all_currency': self.get_all_currency,
'a_ids_currency': self.ids_currency,
'a_get_inactive_currency': self.get_inactive_currency,
'a_get_all_discount': self.get_all_discount,
'a_ids_discount': self.ids_discount,
'a_get_inactive_discount': self.get_inactive_discount
}

View File

@@ -0,0 +1,65 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: SQL Error Business Object
Description:
Business object for SQL errors
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class SQL_Error(db.Model):
display_order = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(50))
msg = db.Column(db.String(4000))
name = db.Column(db.String(500))
description = db.Column(db.String(4000))
"""
def __new__(cls, display_order, code, msg):
_m = 'SQL_Error.__new__'
v_arg_type = 'class attribute'
av.val_int(display_order, 'display_order', _m)
av.val_str(code, 'code', _m, max_len=50, v_arg_type=v_arg_type)
av.val_str(msg, 'msg', _m, max_len=4000, v_arg_type=v_arg_type)
return super(SQL_Error, cls).__new__(cls)
def __init__(self, display_order, code, msg):
self.display_order = display_order
self.code = code
self.msg = msg
super().__init__()
"""
def make_from_DB_record(record):
error = SQL_Error()
error.display_order = record[0]
error.code = record[1]
error.msg = record[2]
error.name = record[3]
error.description = record[4]
return error

164
business_objects/stripe.py Normal file
View File

@@ -0,0 +1,164 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Stripe Business Object
Description:
Business objects for Stripe
"""
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Stripe_Product(db.Model):
id_product = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
description = db.Column(db.String(4000))
price_GBP_full = db.Column(db.Float)
id_category = db.Column(db.Integer)
lead_time_manuf = db.Column(db.Integer)
quantity_min = db.Column(db.Float)
quantity_max = db.Column(db.Float)
quantity_step = db.Column(db.Float)
quantity_stock = db.Column(db.Float)
id_stripe_product = db.Column(db.String(255))
id_stripe_price = db.Column(db.String(255))
is_subscription = db.Column(db.Boolean)
name_recurring_interval = db.Column(db.String(255))
name_plural_recurring_interval = db.Column(db.String(256))
count_recurring_interval = db.Column(db.Integer)
display_order = db.Column(db.Integer)
can_view = db.Column(db.Boolean)
can_edit = db.Column(db.Boolean)
can_admin = db.Column(db.Boolean)
# form_basket_add: Form_Basket_Add
# form_basket_edit: Form_Basket_Edit
def __new__(cls, id, name, description, price_GBP_full, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, display_order, can_view, can_edit, can_admin):
_m = 'Product.__new__'
v_arg_type = 'class attribute'
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
av.val_str(description, 'description', _m, max_len=4000, v_arg_type=v_arg_type)
av.full_val_float(price_GBP_full, 'price_GBP_full', _m, 0., v_arg_type=v_arg_type)
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
av.val_int(lead_time_manuf, 'lead_time_manuf', _m, 0, v_arg_type=v_arg_type)
av.full_val_float(quantity_step, 'quantity_step', _m, 0., v_arg_type=v_arg_type)
av.full_val_float(quantity_min, 'quantity_min', _m, quantity_step, v_arg_type=v_arg_type)
av.full_val_float(quantity_max, 'quantity_max', _m, quantity_min, v_arg_type=v_arg_type)
av.full_val_float(quantity_stock, 'quantity_stock', _m, 0, v_arg_type=v_arg_type)
av.val_str(id_stripe_product, 'id_stripe_product', _m, max_len=100, v_arg_type=v_arg_type)
av.val_str(id_stripe_price, 'id_stripe_price', _m, max_len=100, v_arg_type=v_arg_type)
av.full_val_bool(is_subscription, 'is_subscription', _m, v_arg_type=v_arg_type)
print(f'is_subscription: {is_subscription}, {av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)}')
is_subscription = av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)
if is_subscription:
av.val_str(name_recurring_interval, 'name_recurring_interval', _m, max_len=255, v_arg_type=v_arg_type)
av.val_str(name_plural_recurring_interval, 'name_plural_recurring_interval', _m, max_len=256, v_arg_type=v_arg_type)
av.val_int(count_recurring_interval, 'count_recurring_interval', _m, 0, v_arg_type=v_arg_type)
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
av.full_val_bool(can_view, 'can_view', _m, v_arg_type=v_arg_type)
# can_view = av.input_bool(can_view, "can_view", _m, v_arg_type=v_arg_type)
av.full_val_bool(can_edit, 'can_edit', _m, v_arg_type=v_arg_type)
# can_edit = av.input_bool(can_edit, "can_edit", _m, v_arg_type=v_arg_type)
av.full_val_bool(can_admin, 'can_admin', _m, v_arg_type=v_arg_type)
# can_admin = av.input_bool(can_admin, "can_admin", _m, v_arg_type=v_arg_type)
return super(Product, cls).__new__(cls) # , id, name, description, price_GBP, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
# is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, can_view, can_edit, can_admin)
def __init__(self, id, name, description, price_GBP_full, id_category, lead_time_manuf, quantity_min, quantity_max, quantity_step, quantity_stock, id_stripe_product, id_stripe_price,
is_subscription, name_recurring_interval, name_plural_recurring_interval, count_recurring_interval, display_order, can_view, can_edit, can_admin):
_m = 'Product.__new__'
v_arg_type = 'class attribute'
self.id_product = id
self.name = name
self.description = description
self.price_GBP_full = price_GBP_full
self.id_category = id_category
self.lead_time_manuf = lead_time_manuf
self.quantity_min = quantity_min
self.quantity_max = quantity_max
self.quantity_step = quantity_step
self.quantity_stock = quantity_stock
self.id_stripe_product = id_stripe_product
self.id_stripe_price = id_stripe_price
self.is_subscription = av.input_bool(is_subscription, "is_subscription", _m, v_arg_type=v_arg_type)
self.name_recurring_interval = name_recurring_interval
self.name_plural_recurring_interval = name_plural_recurring_interval
self.count_recurring_interval = count_recurring_interval
self.display_order = display_order
self.can_view = av.input_bool(can_view, "can_view", _m, v_arg_type=v_arg_type)
self.can_edit = av.input_bool(can_edit, "can_edit", _m, v_arg_type=v_arg_type)
self.can_admin = av.input_bool(can_admin, "can_admin", _m, v_arg_type=v_arg_type)
self.variations = []
self.images = []
self.delivery_options = []
self.discounts = []
self.discount_index = {}
super().__init__()
self.form_basket_add = Form_Basket_Add()
self.form_basket_edit = Form_Basket_Edit()
def output_lead_time(self):
return '1 day' if self.lead_time_manuf == 1 else f'{self.lead_time_manuf} days'
def output_delivery_date(self):
return (datetime.now() + timedelta(days=self.lead_time_manuf)).strftime('%A, %d %B %Y')
def output_price(self):
locale.setlocale(locale.LC_ALL, '')
return locale.format_string("%d", self.price_GBP_full, grouping=True)
"""
def add_form_basket_add(self):
self.form_basket_add = None
def add_form_basket_edit(self):
self.form_basket_edit = None
"""
def __repr__(self):
return f'''Product
id: {self.id_product}
name: {self.name}
description: {self.description}
price_GBP_full: {self.price_GBP_full}
id_category: {self.id_category}
lead_time_manuf: {self.lead_time_manuf}
quantity_min: {self.quantity_min}
quantity_max: {self.quantity_max}
quantity_step: {self.quantity_step}
quantity_stock: {self.quantity_stock}
id_stripe_product: {self.id_stripe_product}
id_stripe_price: {self.id_stripe_price}
is_subscription: {self.is_subscription}
name_recurring_interval: {self.name_recurring_interval}
name_plural_recurring_interval: {self.name_plural_recurring_interval}
count_recurring_interval: {self.count_recurring_interval}
display_order: {self.display_order}
can_view: {self.can_view}
can_edit: {self.can_edit}
can_admin: {self.can_admin}
variations: {self.variations}
images: {self.images}
delivery_options: {self.delivery_options}
'''
def add_discount(self, discount):
_m = 'Category.add_product'
av.val_instance(discount, 'discount', _m, Discount)
# self.product_index.append(len(self.products))
self.discount_index[discount.id_discount] = len(self.discounts)
self.discounts.append(discount)

View File

@@ -0,0 +1,94 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Product Variation Business Object
Description:
Business object for product variation
"""
# IMPORTS
# VARIABLE INSTANTIATION
# CLASSES
# METHODS
# IMPORTS
# internal
import lib.argument_validation as av
from lib import data_types
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
from enum import Enum
from datetime import datetime, timedelta
import locale
from flask_sqlalchemy import SQLAlchemy
# VARIABLE INSTANTIATION
db = SQLAlchemy()
# CLASSES
class Variation(db.Model):
id_variation = db.Column(db.Integer, primary_key=True)
id_product = db.Column(db.Integer)
id_permutation = db.Column(db.Integer)
id_category = db.Column(db.Integer)
code_variation_type = db.Column(db.String(50))
name_variation_type = db.Column(db.String(255))
code_variation = db.Column(db.String(50))
name_variation = db.Column(db.String(255))
display_order = db.Column(db.Integer)
"""
def __new__(cls, id, id_product, id_category, name_type, code_type, name, code, display_order):
_m = 'Variation.__new__'
v_arg_type = 'class attribute'
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
av.val_int(id_product, 'id_product', _m, 0, v_arg_type=v_arg_type)
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
av.val_str(code_type, 'code_type', _m, max_len=50, v_arg_type=v_arg_type)
av.val_str(name_type, 'name_type', _m, max_len=256, v_arg_type=v_arg_type)
av.val_str(code, 'code', _m, max_len=50, v_arg_type=v_arg_type)
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
return super(Variation, cls).__new__(cls)
def __init__(self, id, id_product, id_category, name_type, code_type, name, code, display_order):
self.id_variation = id
self.id_product = id_product
self.id_category = id_category
self.name_variation_type = name_type
self.code_variation_type = code_type
self.name_variation = name
self.code_variation = code
self.display_order = display_order
super().__init__()
"""
def make_from_DB_product(query_row):
variation = Variation()
variation.id_variation = query_row[0]
variation.id_product = query_row[1]
variation.id_permutation = query_row[2]
variation.id_category = query_row[3]
variation.code_variation_type = query_row[4]
variation.name_variation_type = query_row[5]
variation.code_variation = query_row[6]
variation.name_variation = query_row[7]
variation.display_order = query_row[8]
return variation
def __repr__(self):
return f'''
id: {self.id_variation}
id_product: {self.id_product}
id_permutation: {self.id_permutation}
id_category: {self.id_category}
code_variation_type: {self.code_variation_type}
name_variation_type: {self.name_variation_type}
code_variation: {self.code_variation}
name_variation: {self.name_variation}
display_order: {self.display_order}
'''

59
config.py Normal file
View File

@@ -0,0 +1,59 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Configuration
Description:
Configuration variables
"""
# IMPORTS
import os
# CLASSES
class Config:
DEBUG = False
TESTING = False
SECRET_KEY = os.getenv('KEY_SECRET_FLASK') # gen cmd: openssl rand -hex 32
# Add other configuration variables as needed
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI')
SQLALCHEMY_TRACK_MODIFICATIONS = False
ID_AUTH0_CLIENT = os.getenv('ID_AUTH0_CLIENT')
ID_AUTH0_CLIENT_SECRET = os.getenv('ID_AUTH0_CLIENT_SECRET')
DOMAIN_AUTH0 = os.getenv('DOMAIN_AUTH0')
ID_TOKEN_USER = 'user'
is_included_VAT = True
"""
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
code_currency = 1
KEY_CODE_CURRENCY = 'id_currency'
code_region_delivery = 1
KEY_CODE_REGION_DELIVERY = 'id_region_delivery'
KEY_ID_CURRENCY = 'id_currency'
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
"""
id_currency = 1
id_region_delivery = 1
class DevelopmentConfig(Config):
DEBUG = True
# Add development-specific configuration variables
class ProductionConfig(Config):
# Add production-specific configuration variables
pass
# Set the configuration class based on the environment
# You can change 'development' to 'production' when deploying
config_env = 'development'
if config_env == 'development':
app_config = DevelopmentConfig
elif config_env == 'production':
app_config = ProductionConfig
else:
raise ValueError("Invalid configuration environment")

11
datastores/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: DataStores
Description:
Initialises datastores module.
"""

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,738 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: DataStores
Feature: Store DataStore
Description:
Datastore for Store
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
# from routes import bp_home
import lib.argument_validation as av
from business_objects.category import Category_List, Category
from business_objects.product import Product, Product_Permutation, Price, Product_Filters # Permutation_Variation_Link
from business_objects.variation import Variation
from business_objects.image import Image
from business_objects.currency import Currency
from business_objects.delivery_region import Delivery_Region
from business_objects.delivery_option import Delivery_Option
from business_objects.discount import Discount
from business_objects.basket import Basket, Basket_Item
from business_objects.order import Order
from business_objects.sql_error import SQL_Error
# from models.model_view_store_checkout import Model_View_Store_Checkout # circular!
# external
# from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import text
import stripe
import os
# VARIABLE INSTANTIATION
# CLASSES
class DataStore_Store():
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
KEY_ID_CURRENCY = 'id_currency'
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
# Attributes
db: SQLAlchemy
# Global constants
def __new__(cls, db, info_user, app):
# Initialiser - validation
_m = 'DataStore_Store.__new__'
v_arg_type = 'class attribute'
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
return super(DataStore_Store, cls).__new__(cls)
def __init__(self, db, info_user, app):
# Constructor
self.db = db
self.info_user = info_user
self.app = app
self.key_secret_stripe = os.environ.get("KEY_SECRET_STRIPE")
self.key_public_stripe = os.environ.get("KEY_PUBLIC_STRIPE")
# For sample support and debugging, not required for production:
stripe.set_app_info(
'stripe-samples/checkout-one-time-payments',
version='0.0.1',
url='https://github.com/stripe-samples/checkout-one-time-payments')
stripe.api_key = self.key_secret_stripe
def get_many_product_category(self, product_filters):
# redundant argument validation?
_m = 'DataStore_Store.get_many_product_category'
av.val_instance(product_filters, 'product_filters', _m, Product_Filters)
"""
av.val_str(category_ids, 'category_ids', _m)
av.val_str(product_ids, 'product_ids', _m)
av.val_bool(get_all_category, 'get_all_category', _m)
av.val_bool(get_all_product, 'get_all_product', _m)
av.val_int(max_products_per_category, 'max_products_per_category', _m)
argument_dict_list = {
'a_id_user': self.info_user.get('sub'),
'a_get_all_categories': 0,
'a_ids_category': category_ids,
'a_get_inactive_categories': 0,
'a_ids_product': product_ids,
'a_get_inactive_products': 0,
'a_get_first_product_only': 1 if (max_products_per_category == 1) else 0,
'a_get_all_products': 1 if get_all_product else 0,
'a_ids_image': '',
'a_get_inactive_images': 0,
'a_get_first_image_only': 0,
'a_get_all_images': 1
}
"""
argument_dict = product_filters.to_json()
print(f'argument_dict: {argument_dict}')
print('executing p_shop_get_many_product')
result = self.db_procedure_execute('p_shop_get_many_product', argument_dict)
cursor = result.cursor
print('data received')
# categories, category_index = DataStore_Store.input_many_product(cursor)
category_list, errors = DataStore_Store.input_many_product(cursor)
DataStore_Store.db_cursor_clear(cursor)
return category_list, errors # categories, category_index
def edit_basket(self, ids_permutation_basket, quantities_permutation_basket, id_permutation_edit, quantity_permutation_edit, sum_not_edit, id_currency, id_region_delivery):
# redundant argument validation?
_m = 'DataStore_Store.edit_basket'
print(f'{_m}\nstarting...')
# av.val_instance(filters, 'filters', _m, Product_Category_Filters)
# av.val_str(ids_product_basket, 'ids_product_basket', _m)
av.val_str(ids_permutation_basket, 'ids_permutation_basket', _m)
# av.val_str(quantities_product_basket, 'quantities_product_basket', _m)
av.val_str(quantities_permutation_basket, 'quantities_permutation_basket', _m)
"""
if id_product_edit == 'None':
id_product_edit = None
else:
print(f'id_product_edit: {id_product_edit}')
av.val_int(id_product_edit, 'id_product_edit', _m)
"""
if id_permutation_edit == 'None' or str(type(id_permutation_edit)) =="<class 'NoneType'>":
id_permutation_edit = None
else:
print(f'id_permutation_edit: {id_permutation_edit}')
print(str(type(id_permutation_edit)))
av.val_int(id_permutation_edit, 'id_permutation_edit', _m)
if quantity_permutation_edit == 'None' or str(type(quantity_permutation_edit)) =="<class 'NoneType'>":
quantity_permutation_edit = None
else:
print(f'quantity_permutation_edit: {quantity_permutation_edit}')
av.val_int(quantity_permutation_edit, 'quantity_permutation_edit', _m)
if sum_not_edit == 'None':
sum_not_edit = None
else:
print(f'sum_not_edit: {sum_not_edit}')
av.val_bool(sum_not_edit, 'sum_not_edit', _m)
argument_dict_list = {
'a_id_user': self.info_user.get('sub'),
# 'a_ids_product_basket': ids_product_basket,
'a_ids_permutation_basket': ids_permutation_basket,
# 'a_quantities_product_basket': quantities_product_basket,
'a_quantities_permutation_basket': quantities_permutation_basket,
# 'a_id_product_edit': id_product_edit if id_permutation_edit is None else None,
'a_id_permutation_edit': id_permutation_edit,
'a_quantity_permutation_edit': quantity_permutation_edit,
'a_sum_not_edit': 1 if sum_not_edit else 0,
'a_id_currency': id_currency,
'a_id_region_purchase': id_region_delivery
}
result = self.db_procedure_execute('p_shop_edit_user_basket', argument_dict_list)
print('data received')
cursor = result.cursor
# categories, category_index = DataStore_Store.input_many_product(cursor)
category_list, errors = DataStore_Store.input_many_product(cursor)
print(f'cursor: {str(cursor)}')
# Basket
if not cursor.nextset():
raise Exception("No more query results! Cannot open basket contents")
result_set = cursor.fetchall()
print(f'raw basket: {result_set}')
# print(f'variations: {result_set_3}')
# variations = [Variation(**row) for row in result_set_3]
basket = Basket()
for row in result_set:
index_category = category_list.get_index_category_from_id(row[0])
category = category_list.categories[index_category]
index_product = category.get_index_product_from_id(row[1])
product = category.products[index_product]
basket_item = Basket_Item.make_from_product_and_quantity_and_VAT_included(product, row[7], self.app.is_included_VAT)
print(f'adding basket item: {row}')
print(f'basket item: {basket_item}')
basket.add_item(basket_item) # basket.append(basket_item) # Basket_Item(category.name, product, row[4]))
print(f'basket: {basket}')
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_2]
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
DataStore_Store.db_cursor_clear(cursor)
return basket
def edit_user(self):
# redundant argument validation?
_m = 'DataStore_Store.edit_user'
# av.val_instance(filters, 'filters', _m, Product_Category_Filters)
argument_dict_list = {
'a_id_user': self.info_user.get('sub'),
'a_name': self.info_user.get('name'),
'a_email': self.info_user.get('email'),
'a_email_verified': 1 if self.info_user.get('email_verified') == 'True' else 0
}
result = self.db_procedure_execute('p_shop_edit_user', argument_dict_list)
cursor = result.cursor
result_set_1 = cursor.fetchall()
print(f'raw user data: {result_set_1}')
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_2]
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
DataStore_Store.db_cursor_clear(cursor)
return (result_set_1[0][1] == b'\x01')
def db_procedure_execute(self, proc_name, argument_dict_list = None):
# Argument validation
_m = 'DataStore_Store.db_procedure_execute'
av.val_str(proc_name, 'proc_name', _m)
has_arguments = not str(type(argument_dict_list)) == "<class 'NoneType'>"
if has_arguments:
# av.val_list_instances(argument_dict_list, 'argument_dict_list', _m, dict)
pass
# Methods
proc_string = f'CALL {proc_name}('
if has_arguments:
arg_keys = list(argument_dict_list.keys())
for i in range(len(arg_keys)):
proc_string += f'{"" if i == 0 else ", "}:{arg_keys[i]}'
proc_string += ')'
proc_string = text(proc_string)
print(f'{_m}\nproc_string: {proc_string}\nargs: {argument_dict_list}')
# with self.db.session.begin() as session:
if has_arguments:
result = self.db.session.execute(proc_string, argument_dict_list)
else:
result = self.db.session.execute(proc_string)
print(f'result: {result}')
return result
cursor = result.cursor
result_set_1 = cursor.fetchall()
print(f'categories: {result_set_1}')
cursor.nextset()
result_set_2 = cursor.fetchall()
print(f'products: {result_set_2}')
"""
def get_many_id_price(self, product_ids):
_m = 'DataStore_Store.get_many_id_price'
av.val_str(product_ids, 'product_ids', _m)
price_ids = []
for product_id in product_ids.split(','):
if product_id == 'prod_PB0NUOSEs06ymG':
price_ids.append() # get price id
return price_ids
"""
def input_many_product(cursor):
_m = 'DataStore_Store.input_many_product'
category_list = Category_List()
# Categories
result_set_1 = cursor.fetchall()
print(f'raw categories: {result_set_1}')
# categories = [Category(row[0], row[1], row[2], row[3]) for row in result_set_1]
# categories = []
# category_index = {}
for row in result_set_1:
new_category = Category.make_from_DB_product(row) # Category(row[0], row[1], row[2], row[3])
# category_index[new_category.id_category] = len(categories)
# categories.append(new_category)
category_list.add_category(new_category)
# print(f'categories: {[c.id_category for c in categories]}')
# Products
cursor.nextset()
result_set_2 = cursor.fetchall()
# print(f'products: {result_set_2}')
products = [] # [Product(**row) for row in result_set_2]
product_index = {}
for row in result_set_2:
new_permutation = Product_Permutation.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
index_category = category_list.get_index_category_from_id(new_permutation.id_category)
category = category_list.categories[index_category]
try:
index_product = category.get_index_product_from_id(new_permutation.id_product)
category_list.add_permutation(new_permutation)
# product = products[index_product]
# product.add_permutation(new_permutation)
except KeyError:
product_index[new_permutation.id_product] = len(products)
product = Product.make_from_DB_product(row)
product.add_permutation(new_permutation)
products.append(product)
# categories[category_index[new_product.id_category]].add_product(new_product)
category_list.add_product(product)
# category_list.add_permutation(new_permutation)
# print(f'products: {[p.id_product for p in products]}') # {products}')
print(f'category_list: {category_list}')
# Variations
cursor.nextset()
result_set_3 = cursor.fetchall()
# print(f'variations: {result_set_3}')
# variations = [Variation(**row) for row in result_set_3]
variations = []
for row in result_set_3:
new_variation = Variation.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
variations.append(new_variation)
# products[product_index[new_variation.id_product]].variations.append(new_variation)
# index_category = category_index[new_variation.id_category]
# index_product = categories[index_category].index_product_from_ids_product_permutation(new_variation.id_product, new_variation.id_permutation)
# categories[index_category].products[index_product].variations.append(new_variation)
category_list.add_variation(new_variation)
# print(f'variations: {variations}')
# print(f'products: {[p.id_product for p in products]}')
"""
# Permutation Variation Links
cursor.nextset()
result_set_4 = cursor.fetchall()
# print(f'variations: {result_set_3}')
# variations = [Variation(**row) for row in result_set_3]
permutation_variation_links = []
for row in result_set_3:
permutation_variation_link = Permutation_Variation_Link.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
permutation_variation_links.append(permutation_variation_link)
# categories[category_index[new_variation.id_category]].products[categories[category_index[new_variation.id_category]].product_index[new_variation.id_product]].variations.append(new_variation)
categories[category_index[new_variation.id_category]].products[categories[category_index[new_variation.id_category]].product_index[new_variation.id_product]].variations.append(new_variation)
print(f'variations: {variations}')
print(f'products: {[p.id_product for p in products]}')
"""
# Prices
cursor.nextset()
result_set_4 = cursor.fetchall()
# print(f'variations: {result_set_3}')
# variations = [Variation(**row) for row in result_set_3]
prices = []
for row in result_set_4:
price = Price.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
prices.append(price)
"""
index_category = category_index[price.id_category]
index_product = categories[index_category].index_product_from_ids_product_permutation(price.id_product, price.id_permutation)
categories[index_category].products[index_product].prices.append(price)
"""
category_list.add_price(price)
# print(f'prices: {prices}')
# print(f'products: {[p.id_product for p in products]}')
"""
# Currencies
cursor.nextset()
result_set_5 = cursor.fetchall()
# print(f'variations: {result_set_3}')
# variations = [Variation(**row) for row in result_set_3]
prices = []
for row in result_set_4:
price = Price.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7])
prices.append(price)
index_category = category_index[price.id_category]
categories[index_category].products[index_category].product_index[price.id_product].prices.append(price)
print(f'prices: {prices}')
print(f'products: {[p.id_product for p in products]}')
"""
# Images
cursor.nextset()
result_set_5 = cursor.fetchall()
# print(f'images: {result_set_4}')
# images = [Image(**row) for row in result_set_4]
images = []
for row in result_set_5:
new_image = Image.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4])
images.append(new_image)
# products[product_index[new_image.id_product]].images.append(new_image)
"""
index_category = category_index[new_image.id_category]
index_product = categories[index_category].index_product_from_ids_product_permutation(new_image.id_product, new_image.id_permutation)
categories[index_category].products[index_product].images.append(new_image)
"""
category_list.add_image(new_image)
# print(f'images: {images}')
# print(f'products: {[p.id_product for p in products]}')
# Delivery options
cursor.nextset()
result_set_7 = cursor.fetchall()
delivery_options = []
for row in result_set_7:
new_delivery_option = Delivery_Option.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12])
delivery_options.append(new_delivery_option)
# products[product_index[new_delivery_option.id_product]].delivery_options.append(new_delivery_option)
"""
index_category = category_index[new_delivery_option.id_category]
index_product = categories[index_category].index_product_from_ids_product_permutation(new_delivery_option.id_product, new_delivery_option.id_permutation)
categories[index_category].products[index_product].delivery_options.append(new_delivery_option)
"""
category_list.add_delivery_option(new_delivery_option)
# print(f'delivery_options: {delivery_options}')
# print(f'products: {products}')
# Discounts
cursor.nextset()
result_set_8 = cursor.fetchall()
discounts = []
for row in result_set_8:
new_discount = Discount.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10])
discounts.append(new_discount)
# i_cat = category_index[new_discount.id_category]
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts.append(new_delivery_option)
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discount_index[new_discount.id_discount] = len(categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts)
# categories[i_cat].products[categories[i_cat].product_index[new_delivery_option.id_product]].discounts.append(new_discount)
"""
index_category = category_index[new_discount.id_category]
index_product = categories[index_category].index_product_from_ids_product_permutation(new_discount.id_product, new_discount.id_permutation)
categories[index_category].products[index_product].add_discount(new_discount)
"""
category_list.add_discount(new_discount)
# print(f'discounts: {discounts}')
# print(f'products: {products}')
"""
# Delivery Regions
cursor.nextset()
result_set_6 = cursor.fetchall()
delivery_regions = []
for row in result_set_6:
new_delivery_region = Delivery_Region.make_from_DB_product(row) # (row[0], row[1], row[2], row[3], row[4], row[5], row[6])
delivery_regions.append(new_delivery_region)
# products[product_index[new_delivery_option.id_product]].delivery_regions.append(new_delivery_region)
i_cat = category_index[new_delivery_region.id_category]
i_prod = categories[i_cat].product_index[new_delivery_region.id_product]
categories[i_cat].products[i_prod].discounts[categories[i_cat].products[i_prod].
categories[i_cat].products[i_prod].discounts[categories[i_cat].products[i_prod].discount_index[new_delivery_region.id_discount]].delivery_regions.append(new_delivery_region)
print(f'delivery_regions: {delivery_regions}')
print(f'products: {products}')
"""
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
errors = []
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # (row[0], row[1])
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
category_list.get_all_variation_trees()
"""
for category in category_list.categories:
print(f'category: {category.name}')
for product in category.products:
permutation = product.get_permutation_selected()
print(f'product: {product.name}\nselected permutation: {permutation}')
"""
if len(errors) > 0:
for error in errors:
if error.code == 'PRODUCT_AVAILABILITY':
ids_permutation_unavailable = DataStore_Store.get_ids_permutation_from_error_availability(error.msg)
for id_permutation in ids_permutation_unavailable:
index_category = category_list.get_index_category_from_id_permutation(id_permutation)
category = category_list.categories[index_category]
index_product = category.get_index_product_from_id_permutation(id_permutation)
product = category.products[index_product]
index_permutation = product.get_index_permutation_from_id(id_permutation)
permutation = product.permutations[index_permutation]
permutation.is_available = False
if 'region' in error.msg or 'currency' in error.msg:
permutation.is_unavailable_in_currency_or_region = True
return category_list, errors # categories, category_index
def db_cursor_clear(cursor):
while cursor.nextset():
print(f'new result set: {cursor.fetchall()}')
def get_ids_permutation_from_error_availability(msg_error_availability):
ids_permutation = []
index_colon = msg_error_availability.find(':', msg_error_availability.find(':'))
msg_error_availability = msg_error_availability[index_colon + 1:]
index_comma = 0
while index_comma > -1:
msg_error_availability = msg_error_availability[index_comma:]
index_comma = msg_error_availability.find(',')
ids_permutation.append(msg_error_availability[:index_comma])
return ids_permutation
def get_many_user_order(self, id_user, ids_order, n_order_max, id_checkout_session):
_m = 'Model_View_Store.get_many_user_order'
# av.val_str(id_user)
# validation conducted by server
argument_dict_list = {
'a_id_user': id_user,
'a_ids_order': ids_order,
'a_n_order_max': n_order_max,
'a_id_checkout_session': id_checkout_session
}
print('executing p_shop_get_many_user_order')
result = self.db_procedure_execute('p_shop_get_many_user_order', argument_dict_list)
cursor = result.cursor
print('data received')
# Discount Delivery Regions
cursor.nextset()
result_set_1 = cursor.fetchall()
orders = []
for row in result_set_1:
new_order = Order(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
orders.append(new_order)
print(f'orders: {orders}')
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
DataStore_Store.db_cursor_clear(cursor)
return orders
def get_many_stripe_product_new(self):
_m = 'Model_View_Store.get_many_stripe_product_new'
_m_db = 'p_shop_get_many_stripe_product_new'
# av.val_str(id_user)
# validation conducted by server
argument_dict_list = {
'a_id_user': self.info_user
}
print(f'executing {_m_db}')
result = self.db_procedure_execute(_m_db, argument_dict_list)
cursor = result.cursor
print('data received')
# Products
cursor.nextset()
result_set_1 = cursor.fetchall()
products = []
for row in result_set_1:
new_product = Product.make_from_DB_Stripe_product(row) # Product(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
products.append(new_product)
print(f'products: {products}')
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
DataStore_Store.db_cursor_clear(cursor)
return products
def get_many_stripe_price_new(self):
_m = 'Model_View_Store.get_many_stripe_price_new'
_m_db = 'p_shop_get_many_stripe_price_new'
# av.val_str(id_user)
# validation conducted by server
argument_dict_list = {
'a_id_user': self.info_user
}
print(f'executing {_m_db}')
result = self.db_procedure_execute(_m_db, argument_dict_list)
cursor = result.cursor
print('data received')
# Products
cursor.nextset()
result_set_1 = cursor.fetchall()
products = []
for row in result_set_1:
new_product = Product.make_from_DB_Stripe_price(row) # Product(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14], row[15], row[16], row[17], row[18], row[19])
products.append(new_product)
print(f'products: {products}')
# Errors
cursor.nextset()
result_set_e = cursor.fetchall()
print(f'raw errors: {result_set_e}')
if len(result_set_e) > 0:
errors = [SQL_Error.make_from_DB_record(row) for row in result_set_e] # [SQL_Error(row[0], row[1]) for row in result_set_e]
for error in errors:
print(f"Error [{error.code}]: {error.msg}")
DataStore_Store.db_cursor_clear(cursor)
return products
def get_many_product_new(self):
_m = 'Model_View_Store.get_many_product_new'
# Stripe
new_products = self.get_many_stripe_product_new()
for product in new_products:
product.id_stripe_product = self.create_stripe_product(product)
return new_products
def get_many_price_new(self):
_m = 'Model_View_Store.get_many_product_new'
# Stripe
new_products = self.get_many_stripe_price_new()
for product in new_products:
product.id_stripe_price = self.create_stripe_price(product)
return new_products
# Stripe
def create_stripe_product(self, product): # _name, product_description):
_m = 'Model_View_Store_Checkout.create_stripe_product'
# av.val_str(product_name, 'product_name', _m)
# av.val_str(product_description, 'product_description', _m)
av.val_instance(product, 'product', _m, Product)
print(f'stripe.api_key = {stripe.api_key}')
new_product = stripe.Product.create(
name = product.name,
description = product.description,
)
# Save these identifiers
print(f"Success! Here is your new Stripe product id: {new_product.id}")
return new_product.id
def create_stripe_price(self, product, currency): # product_id, product_price, product_currency, product_is_subscription, product_recurring_interval = '', product_interval_count = 0):
_m = 'Model_View_Store_Checkout.create_stripe_price'
"""
av.val_str(p_id, 'p_id', _m)
av.full_val_float(p_price, 'p_price', _m, 0.01)
p_price = round(p_price, 2)
av.val_str(p_currency, 'p_currency', _m)
av.full_val_bool(p_is_subscription, 'p_is_subscription', _m)
p_is_subscription = bool(p_is_subscription)
av.val_str(p_recurring_interval, 'p_recurring_interval', _m)
av.full_val_int(p_interval_count, 'p_interval_count', _m, 1 if p_is_subscription else 0)
p_interval_count = int(p_interval_count)
"""
av.val_instance(product, 'product', _m, Product)
av.val_str(currency, 'currency', _m)
print(f'stripe.api_key = {stripe.api_key}')
new_product_price = stripe.Price.create(
unit_amount = product.unit_price,
currency = currency,
recurring = { "interval": product.name_recurring_interval, "interval_count": product.count_recurring_interval } if product.is_subscription else None,
product = product.id_stripe_product
)
# Save these identifiers
print(f"Success! Here is your Stripe product price id: {new_product_price.id} for {product.name}")
return new_product_price.id
def get_regions_and_currencies(self):
_m = 'Model_View_Store.get_regions_and_currencies'
_m_db_currency = 'p_shop_get_many_currency'
_m_db_region = 'p_shop_get_many_region'
argument_dict_list_currency = {
'a_get_inactive_currency': 0
}
argument_dict_list_region = {
'a_get_inactive_currency': 0
}
print(f'executing {_m_db_currency}')
result = self.db_procedure_execute(_m_db_currency, argument_dict_list_currency)
cursor = result.cursor
print('data received')
# cursor.nextset()
result_set_1 = cursor.fetchall()
currencies = []
for row in result_set_1:
currency = Currency.make_from_DB_currency(row)
currencies.append(currency)
print(f'currencies: {currencies}')
DataStore_Store.db_cursor_clear(cursor)
print(f'executing {_m_db_region}')
result = self.db_procedure_execute(_m_db_region, argument_dict_list_region)
cursor = result.cursor
print('data received')
# cursor.nextset()
result_set_1 = cursor.fetchall()
regions = []
for row in result_set_1:
region = Delivery_Region.make_from_DB_region(row)
regions.append(region)
print(f'regions: {regions}')
DataStore_Store.db_cursor_clear(cursor)
return regions, currencies
def get_metadata_basket(json_request):
is_included_VAT = json_request[DataStore_Store.KEY_IS_INCLUDED_VAT]
id_currency = json_request[DataStore_Store.KEY_ID_CURRENCY]
id_region_delivery = json_request[DataStore_Store.KEY_ID_REGION_DELIVERY]
return id_currency, id_region_delivery, is_included_VAT

102
forms.py Normal file
View File

@@ -0,0 +1,102 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Forms - User data input
Description:
Defines Flask-WTF forms for handling user input.
"""
# IMPORTS
# internal
# from models.model_view_store import Model_View_Store # circular
# external
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField, BooleanField, IntegerField, SelectField
from wtforms.validators import InputRequired, NumberRange, Regexp, DataRequired
class Form_Contact(FlaskForm):
email = StringField('Email address')
CC = BooleanField('Would you like to receive a copy of this email request?') # not in use
name = StringField('Name')
msg = TextAreaField('Message')
submit = SubmitField('Submit')
class Form_Register(FlaskForm):
email = StringField('Email address')
CC = BooleanField('Would you like to receive a copy of this email request?') # not in use
name = StringField('Name')
msg = TextAreaField('Message')
submit = SubmitField('Submit')
"""
class Form_Product(FlaskForm): # for basket, product tiles, product add
# PositiveIntegerField with validation constraints
quantity = IntegerField(
'Quantity',
validators=[
# InputRequired(message='Quantity'),
NumberRange(min=1, message='Please enter a positive integer')
],
default=1
)
"""
class Form_Basket_Add(FlaskForm): # for basket, product tiles, product add
# PositiveIntegerField with validation constraints
quantity = IntegerField(
'Quantity',
validators=[
# InputRequired(message='Quantity'),
NumberRange(min=1, message='Please enter a positive integer')
],
default=1
# render_kw={'id-product': ''} # {Model_View_Store.attr_id_product: ''}
)
submit = SubmitField('Add')
form_type = 'Form_Basket_Add'
class Form_Basket_Edit(FlaskForm): # for basket, product tiles, product add
# PositiveIntegerField with validation constraints
quantity = IntegerField(
'Quantity',
validators=[
# InputRequired(message='Quantity'),
NumberRange(min=1, message='Please enter a positive integer')
],
default=1
# render_kw={'id-product': ''} # {Model_View_Store.attr_id_product: ''}
)
submit = SubmitField('Update')
form_type = 'Form_Basket_Edit'
class Form_Billing(FlaskForm):
identical = BooleanField('Use delivery address')
region = SelectField('Country / region', choices=[('uk', 'UK'), ('international', 'International')], validators=[DataRequired()])
name_full = StringField('Full name')
phone_number = StringField('Phone number', validators=[Regexp(r'^\+?[0-9\s]{5,20}$', message='Only numbers, plus symbol, and space are allowed.'), DataRequired()])
postcode = StringField('Post code', validators=[DataRequired()])
address_1 = StringField('Address line 1', validators=[DataRequired()])
address_2 = StringField('Address line 2 (optional)')
city = StringField('City', validators=[DataRequired()])
county = StringField('County', validators=[DataRequired()])
submit = SubmitField('Submit')
form_type_billing_not_delivery = False
def output_id(self):
return 'formBilling' if self.form_type_billing_not_delivery else 'formDeliver'
class Form_Is_Included_VAT(FlaskForm):
is_included = BooleanField('Include VAT')
class Form_Delivery_Region(FlaskForm):
id_region_delivery = SelectField('Region')
class Form_Currency(FlaskForm):
id_currency = SelectField('Currency')

11
helpers/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Helpers
Description:
Initialises helpers module.
"""

11
lib/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Library
Description:
Initialises library module.
"""

Binary file not shown.

Binary file not shown.

Binary file not shown.

1315
lib/argument_validation.py Normal file

File diff suppressed because it is too large Load Diff

37
lib/data_types.py Normal file
View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 27 12:33:59 2023
@author: Edward Middleton-Smith
Argument Validation
"""
# CLASSES
# ATTRIBUTE DECLARATION
# METHODS
# FUNCTION
# ARGUMENTS
# ARGUMENT VALIDATION
# ATTRIBUTE + VARIABLE INSTANTIATION
# METHODS
# RETURNS
# NORMAL METHODS
# FUNCTION
# ARGUMENTS
# ARGUMENT VALIDATION
# VARIABLE INSTANTIATION
# METHODS
# RETURNS
# IMPORTS
# CLASSES
# METHODS
def get_enum_member_by_text(enum_class, text):
for member in enum_class.__members__.values():
if member.name == text:
return member
raise ValueError(f'{text} is not in {enum_class}')

11
models/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Module Initialisation
Feature: Models
Description:
Initialises view data models module.
"""

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

104
models/model_view_base.py Normal file
View File

@@ -0,0 +1,104 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Base View Model
Description:
Base data model for views
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
# from routes import bp_home
import lib.argument_validation as av
from forms import Form_Is_Included_VAT, Form_Delivery_Region, Form_Currency
# external
from abc import ABC, abstractmethod, abstractproperty
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Base(ABC):
# Attributes
is_user_logged_in: bool
id_user: str
form_is_included_VAT: Form_Is_Included_VAT
form_delivery_region: Form_Delivery_Region
form_currency: Form_Currency
# app: Flask
is_page_store: bool
# Global constants
FLAG_BUTTON_MODAL_CLOSE = 'btn-overlay-close'
FLAG_BUTTON_SUBMIT = 'btn-submit'
FLAG_CARD = 'card'
FLAG_COLLAPSIBLE = 'collapsible'
FLAG_COLUMN = 'column'
FLAG_CONTAINER = 'container'
FLAG_CONTAINER_INPUT = FLAG_CONTAINER + '-input'
FLAG_INITIALISED = 'initialised'
FLAG_ROW = 'column'
FLAG_SCROLLABLE = 'scrollable'
FLAG_SUBMITTED = 'submitted'
# flagIsDatePicker = 'is-date-picker'
HASH_PAGE_CONTACT = '/contact'
HASH_PAGE_ERROR_NO_PERMISSION = '/error'
HASH_PAGE_HOME = '/'
HASH_PAGE_STORE_HOME = '/store'
HASH_PAGE_STORE_PRODUCT = '/store/product'
ID_FORM_CURRENCY = 'formCurrency'
ID_FORM_DELIVERY_REGION = 'formDeliveryRegion'
ID_FORM_IS_INCLUDED_VAT = 'formIsIncludedVAT'
ID_MODAL_SERVICES = 'modalServices'
ID_MODAL_TECHNOLOGIES = 'modalTechnologies'
ID_NAV_CONTACT = 'navContact'
ID_NAV_HOME = 'navHome'
ID_NAV_STORE_HOME = 'navStoreHome'
ID_NAV_STORE_PRODUCT = 'navStoreProduct'
ID_PAGE_BODY = 'pageBody'
URL_HOST = 'http://127.0.0.1:5000'
URL_GITHUB = 'https://github.com/Teddy-1024'
URL_LINKEDIN = 'https://uk.linkedin.com/in/lordteddyms'
@abstractproperty
def title(self):
pass
def __new__(cls, db, info_user, app): # , *args, **kwargs
# Initialiser - validation
_m = 'Model_View_Base.__new__'
v_arg_type = 'class attribute'
print(f'{_m}\nstarting')
# return super().__new__(cls, *args, **kwargs)
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
return super(Model_View_Base, cls).__new__(cls)
def __init__(self, db, info_user, app):
# Constructor
_m = 'Model_View_Base.__init__'
v_arg_type = 'class attribute'
print(f'{_m}\nstarting')
av.val_instance(db, 'db', _m, SQLAlchemy, v_arg_type=v_arg_type)
self.db = db
self.info_user = info_user
print(f'info_user: {info_user}\ntype: {str(type(info_user))}')
self.is_user_logged_in = ('sub' in list(info_user.keys()) and not info_user['sub'] == '' and not str(type(info_user['sub'])) == "<class 'NoneType'?")
print(f'is_user_logged_in: {self.is_user_logged_in}')
self.id_user = info_user['sub'] if self.is_user_logged_in else ''
self.form_is_included_VAT = Form_Is_Included_VAT()
self.form_delivery_region = Form_Delivery_Region()
self.form_currency = Form_Currency()
self.app = app
self.is_page_store = False
def output_bool(self, boolean):
return str(boolean).lower()

View File

@@ -0,0 +1,46 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Contact View Model
Description:
Data model for contact view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
from lib import argument_validation as av
# external
from flask_wtf import FlaskForm
from abc import abstractproperty
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Contact(Model_View_Base):
# Attributes
@property
def title(self):
return 'Contact'
def __new__(cls, db, info_user, app, form):
# Initialiser - validation
_m = 'Model_View_Contact.__new__'
av.val_instance(form, 'form', _m, FlaskForm)
return super(Model_View_Contact, cls).__new__(cls, db, info_user, app)
def __init__(self, db, info_user, app, form):
# Constructor
super().__init__(db, info_user, app)
self.form = form

41
models/model_view_home.py Normal file
View File

@@ -0,0 +1,41 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Home View Model
Description:
Data model for home view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_base import Model_View_Base
# from routes import bp_home
# external
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Home(Model_View_Base):
# Attributes
@property
def title(self):
return 'Home'
def __new__(cls, db, info_user, app):
# Initialiser - validation
print(f'info_user: {info_user}')
return super(Model_View_Home, cls).__new__(cls, db, info_user, app)
def __init__(self, db, info_user, app):
# Constructor
super().__init__(db, info_user, app)

360
models/model_view_store.py Normal file
View File

@@ -0,0 +1,360 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Parent View Model
Description:
Parent data model for store views
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
# from context import models
from models.model_view_base import Model_View_Base
from business_objects.product import Product, Product_Filters # Product_Image_Filters,
from business_objects.image import Resolution_Level_Enum
import lib.argument_validation as av
from datastores.datastore_store import DataStore_Store
from forms import Form_Basket_Edit, Form_Is_Included_VAT, Form_Delivery_Region, Form_Currency
from business_objects.basket import Basket_Item, Basket
from business_objects.category import Category
# external
from flask import send_file, jsonify
from flask_sqlalchemy import SQLAlchemy
import locale
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store(Model_View_Base):
# Attributes
# id_user: str
db: SQLAlchemy
basket: Basket # list # dict
# basket_total: float
id_currency: bool
id_region_delivery: bool
is_included_VAT: bool
show_delivery_option: bool # for checkout page
# Global constants
ATTR_FORM_TYPE = 'form-type'
ATTR_ID_PRODUCT_CATEGORY = 'id-product-category'
ATTR_ID_PRODUCT = 'id-product'
ATTR_ID_PERMUTATION = 'id-permutation'
FLAG_BASKET_ITEM_DELETE = 'basket-item-delete'
FLAG_BUTTON_BASKET_ADD = Model_View_Base.FLAG_BUTTON_SUBMIT + '.btnAdd2Basket'
FLAG_BUTTON_BUY_NOW = 'btnBuyNow'
HASH_PAGE_STORE_BASKET = '/store/basket'
HASH_STORE_BASKET_ADD = '/store/basket_add'
HASH_STORE_BASKET_DELETE = '/store/basket_delete'
HASH_STORE_BASKET_EDIT = '/store/basket_edit'
HASH_STORE_BASKET_LOAD = '/store/basket_load'
HASH_STORE_SET_CURRENCY = '/store/set_currency'
HASH_STORE_SET_DELIVERY_REGION = '/store/set_delivery_region'
HASH_STORE_SET_IS_INCLUDED_VAT = '/store/set_is_included_VAT'
ID_BASKET = 'basket'
ID_BASKET_CONTAINER = 'basketContainer'
ID_BASKET_TOTAL = 'basketTotal'
ID_BUTTON_CHECKOUT = 'btnCheckout'
ID_BUTTON_BASKET_ADD = 'btnBasketAdd'
ID_BUTTON_BUY_NOW = 'btnBuyNow'
ID_CURRENCY_DEFAULT = 1
ID_LABEL_BASKET_EMPTY = 'basketEmpty'
ID_REGION_DELIVERY_DEFAULT = 1
IS_INCLUDED_VAT_DEFAULT = True
KEY_BASKET = 'basket'
# KEY_CODE_CURRENCY = 'code_currency'
KEY_FORM = 'form'
KEY_ID_CURRENCY = 'id_currency'
KEY_ID_PRODUCT = 'product_id'
KEY_ID_PERMUTATION = 'permutation_id'
KEY_ID_REGION_DELIVERY = 'id_region_delivery'
KEY_IS_INCLUDED_VAT = 'is_included_VAT'
KEY_PRICE = 'price'
KEY_QUANTITY = 'quantity'
TYPE_FORM_BASKET_ADD = 'Form_Basket_Add'
TYPE_FORM_BASKET_EDIT = 'Form_Basket_Edit'
# development variables
# valid_product_id_list = ['prod_PB0NUOSEs06ymG']
def __new__(cls, db, info_user, app, id_currency, id_region_delivery): # , *args, **kwargs``
# Initialiser - validation
_m = 'Model_View_Store.__new__'
v_arg_type = 'class attribute'
print(f'{_m}\nstarting')
# av.val_str(id_user, 'id_user', _m)
# return super().__new__(cls, *args, **kwargs)
return super().__new__(cls, db, info_user, app) # Model_View_Store, cls
def __init__(self, db, info_user, app, id_currency, id_region_delivery):
# Constructor
_m = 'Model_View_Store.__init__'
print(f'{_m}\nstarting')
super().__init__(db, info_user, app)
self.is_page_store = True
self.basket = Basket()
# self.basket_total = 0
# self.db = db
# if logged in:
# else:
self.id_currency = id_currency
self.id_region_delivery = id_region_delivery
self.show_delivery_option = True
self.form_is_included_VAT = Form_Is_Included_VAT()
regions, currencies = self.get_regions_and_currencies()
self.form_currency = Form_Currency()
self.form_currency.id_currency.choices = [(currency.id_currency, f'{currency.code} - {currency.name}') for currency in currencies]
self.form_currency.id_currency.data = str(self.id_currency) if len(currencies) > 0 else None
self.form_delivery_region = Form_Delivery_Region()
self.form_delivery_region.id_region_delivery.choices = [(region.id_region, f'{region.code} - {region.name}') for region in regions]
self.form_delivery_region.id_region_delivery.data = str(self.id_region_delivery) if len(regions) > 0 else None
def get_many_product_category(self, product_filters): # category_ids = '', product_ids = '', get_all_category = True, get_all_product = True, max_products_per_category = -1):
_m = 'Model_View_Store.get_many_product_category'
av.val_instance(product_filters, 'product_filters', _m, Product_Filters)
"""
av.val_str(category_ids, 'category_ids', _m)
av.val_str(product_ids, 'product_ids', _m)
av.val_bool(get_all_category, 'get_all_category', _m)
av.val_bool(get_all_product, 'get_all_product', _m)
av.val_int(max_products_per_category, 'max_products_per_category', _m)
"""
# get products from database
# call datastore method
# return [Product.template()]
self.category_list, errors = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(product_filters) # category_ids, product_ids, get_all_category, get_all_product, max_products_per_category)
# self.categories = categories
# self.category_index = category_index
return
if get_all_category or get_all_product:
prod = Product.template()
prod_list = [ prod, prod ]
return { 'MISCELLANEOUS': prod_list if max_products_per_category < 0 else prod_list[:max_products_per_category] }
if product_ids == 'panties123':
prod = Product.template()
return { 'MISCELLANEOUS': [ prod] }
# def product_category_getMany(self, category_ids = '', product_ids = '', get_all_category = True, get_all_product = True):
# return Model_View_Store.product_category_getMany(category_ids, product_ids, get_all_category, get_all_product)
def get_many_product_image_src(self, product_id, image_ids = '', get_primary_only = True, resolution_level = ''):
_m = 'Model_View_Store.get_many_product_image'
# print(f'{_m}\n')
# av.val_instance(filters, 'filters', _m, Product_Image_Filters)
av.val_int(product_id, 'product_id', _m)
# av.full_val_int(product_id, 'product_id', _m)
# product_id = int(product_id)
av.val_str(image_ids, 'image_ids', _m)
av.full_val_bool(get_primary_only, 'get_primary_only', _m)
get_primary_only = bool(get_primary_only)
resolution_level = Resolution_Level_Enum.get_member_by_text(resolution_level)
av.val_instance(resolution_level, 'resolution_level', _m, Resolution_Level_Enum)
# if (filters.product_id < 0 or filters.product_id not in self.valid_product_id_list):
if (product_id not in Model_View_Store.valid_product_id_list): # product_id < 0 or
return ''
path_suffix = 'jpg' # get_suffix_from_product_id(product_id)
path_file = f'/static/images/{product_id}.{path_suffix}'
return path_file
# return send_file(path_file, mimetype=f'image/{path_suffix}')
"""
def get_product_category_text(self, category):
return Enum_Product_Category.get_member_by_text(category).text()
def add_2_basket(product_id, quantity, basket_local):
_m = 'Model_View_Store.add_2_basket'
av.full_val_int(product_id, 'product_id', _m)
product_id = str(product_id)
av.full_val_int(quantity, 'quantity', _m)
quantity = int(quantity)
av.val_instance(basket_local, 'basket_local', _m, dict)
# send to database
# update basket on webpage with new database status
if product_id in basket_local:
basket_local[product_id] += quantity
else:
basket_local[product_id] = quantity
return basket_local // jsonify(basket_local)
"""
def basket_item_edit(self, permutation_id, quantity, quantity_sum_not_replace):
_m = 'Model_View_Store.basket_item_edit'
# av.full_val_int(product_id, 'product_id', _m)
# product_id = int(product_id)
# av.val_instance(db, 'db', _m, SQLAlchemy)
print(f'{_m} starting')
# print(f'product_id: {product_id}\npermutation_id: {permutation_id}\nquantity = {quantity}')
# av.full_val_int(product_id, 'product_id', _m)
# print('valid product id')
av.full_val_int(quantity, 'quantity', _m)
quantity = int(quantity)
# item_added = False
print(f'basket: {self.basket}')
ids_permutation, quantities_permutation = self.basket.to_csv()
self.basket = DataStore_Store(self.db, self.info_user, self.app).edit_basket(ids_permutation, quantities_permutation, permutation_id, quantity, quantity_sum_not_replace, self.app.id_currency, self.app.id_region_delivery)
return True
def get_basket(self, json_data):
self.import_JSON_basket(json_data)
if self.is_user_logged_in:
ids_permutation, quantities_permutation = self.basket.to_csv()
self.basket = DataStore_Store(self.db, self.info_user, self.app).edit_basket(ids_permutation, quantities_permutation, None, None, None, self.app.id_currency, self.app.id_region_delivery)
# return self.basket
def _get_json_basket_id_CSVs_product_permutation(self, basket):
product_ids = ''
permutation_ids = ''
item_index_dict = {}
if len(basket) > 0:
for index_item in range(len(basket)):
if index_item > 0:
product_ids += ','
permutation_ids += ','
basket_item = basket[index_item]
id_product = basket_item[self.key_id_product]
id_permutation = basket_item[self.key_id_permutation]
id_permutation = '' if (id_permutation is None or id_permutation == 'None') else str(id_permutation)
product_ids += str(id_product) # str(basket[b].product.id)
permutation_ids += id_permutation # str(basket[b].product.id)
# item_index_dict[Basket.get_key_product_index_from_ids_product_permutation(id_product, id_permutation)] = index_item
item_index_dict[id_permutation] = index_item
print(f'product_ids = {product_ids}')
print(f'permutation_ids = {permutation_ids}')
return product_ids, permutation_ids, item_index_dict
def _get_basket_from_json(self, json_data):
basket = json_data[self.key_basket]['items']
av.val_instance(basket, 'basket', 'Model_View_Store._get_basket_from_json', list)
print(f'basket = {basket}')
return basket
def import_JSON_basket(self, json_data):
_m = 'Model_View_Store.import_JSON_basket'
# av.val_instance(db, 'db', _m, SQLAlchemy)
items = self._get_basket_from_json(json_data)
print(f'json basket items: {items}')
product_ids, permutation_ids, item_index_dict = self._get_json_basket_id_CSVs_product_permutation(items)
category_list, errors = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(Product_Filters(
self.id_user, # :a_id_user
True, '', False, # :a_get_all_category, :a_ids_category, :a_get_inactive_category
False, product_ids, False, False, # :a_get_all_product, :a_ids_product, :a_get_inactive_product, :a_get_first_product_only
False, permutation_ids, False, # :a_get_all_permutation, :a_ids_permutation, :a_get_inactive_permutation
False, '', False, True, # :a_get_all_image, :a_ids_image, :a_get_inactive_image, :a_get_first_image_only
False, str(self.app.id_region_delivery), False, # :a_get_all_delivery_region, :a_ids_delivery_region, :a_get_inactive_delivery_region
False, str(self.app.id_currency), False, # :a_get_all_currency, :a_ids_currency, :a_get_inactive_currency
True, '', False # :a_get_all_discount, :a_ids_discount, :a_get_inactive_discount
)) # product_ids=product_ids, get_all_category=False, get_all_product=False)
# print(f'categories = {categories}')
self.basket = Basket()
if len(category_list.categories) > 0: # not (categories is None):
for category in category_list.categories:
for product in category.products:
# product = Product.make_from_json(items[index_item])
product.form_basket_edit = Form_Basket_Edit()
# key_index_product = Basket.get_key_product_index_from_ids_product_permutation(product.id_product, product.get_id_permutation())
permutation = product.get_permutation_selected()
self.basket.add_item(Basket_Item.make_from_product_and_quantity_and_VAT_included(product, items[item_index_dict[str(permutation.id_permutation)]][self.key_quantity], self.app.is_included_VAT))
"""
if len(items) > 0:
for index_item in range(len(items)):
"""
print(f'basket data: {json_data}')
print(f'basket: {self.basket}')
# ids_permutation_unavailable_region_or_currency = []
# id_permutation_unavailable_otherwise = []
if len(errors) > 0:
for error in errors:
if error[1] == 'PRODUCT_AVAILABILITY':
ids_permutation = DataStore_Store.get_ids_permutation_from_error_availability(error[2])
for id_permutation in ids_permutation:
for item in self.basket.items:
permutation = item.product.get_permutation_selected()
if id_permutation == permutation.id_permutation:
item.is_available = False
if 'region' in error[2] or 'currency' in error[2]:
item.is_unavailable_in_currency_or_region = True
# ids_permutation_unavailable_region_or_currency.append(id_permutation)
# else:
# for id_permutation in ids_permutation:
# id_permutation_unavailable_otherwise.append(id_permutation)
"""
ids_permutation_unavailable = self.basket.get_ids_permutation_unavailable()
if len(ids_permutation_unavailable) > 0:
category_list_unavailable, errors_unavailable = DataStore_Store(self.db, self.info_user, self.app).get_many_product_category(Product_Filters(
self.id_user, # :a_id_user
True, '', False, # :a_get_all_category, :a_ids_category, :a_get_inactive_category
False, '', False, False, # :a_get_all_product, :a_ids_product, :a_get_inactive_product, :a_get_first_product_only
False, ','.join(ids_permutation_unavailable), False, # :a_get_all_permutation, :a_ids_permutation, :a_get_inactive_permutation
False, '', False, True, # :a_get_all_image, :a_ids_image, :a_get_inactive_image, :a_get_first_image_only
True, '', False, # :a_get_all_delivery_region, :a_ids_delivery_region, :a_get_inactive_delivery_region
True, '', False, # :a_get_all_currency, :a_ids_currency, :a_get_inactive_currency
True, '', False # :a_get_all_discount, :a_ids_discount, :a_get_inactive_discount
)) # product_ids=product_ids, get_all_category=False, get_all_product=False)
else:
category_list_unavailable = None
errors_unavailable = []
"""
def import_JSON_basket_item(self, json_data, form_basket = None):
_m = 'Model_View_Store.import_JSON_basket_item'
print(f'starting {_m}')
# print('getting product id')
# product_id = av.input_int(json_data[self.key_id_product], self.key_id_product, _m)
# print(f'product id: {product_id}, type: {str(type(product_id))}')
try:
permutation_id = json_data[self.key_id_permutation]
av.full_val_int(permutation_id, self.key_id_permutation, _m)
permutation_id = int(permutation_id)
except:
permutation_id = None
if not permutation_id == 'None':
print(f'permutation_id invalid: {permutation_id}')
raise ValueError("Invalid permutation id")
print(f'permutation_id: {permutation_id}')
try:
print(f'form_basket: {form_basket}')
print('getting quantity')
print(f'form_basket.quantity: {form_basket.quantity}')
print(f'form_basket.quantity.data: {form_basket.quantity.data}')
quantity = int(form_basket.quantity.data)
except:
quantity = 0
print(f'quantity: {quantity}')
print(f'permutation_id: {permutation_id}\nquantity: {quantity}')
return permutation_id, quantity
def output_basket_total(self):
return self.basket.output_total()
def init_forms_basket_add(self):
for cat in self.categories:
c = self.category_index
def get_many_user_order(self, ids_order, n_order_max, id_checkout_session):
# _m = 'Model_View_Store.get_many_user_order'
# av.val_str(id_user)
# validation conducted by server
return DataStore_Store(self.db, self.info_user, self.app).get_many_user_order(self.info_user['sub'], ids_order, n_order_max, id_checkout_session)
def get_regions_and_currencies(self):
regions, currencies = DataStore_Store(self.db, self.info_user, self.app).get_regions_and_currencies()
return regions, currencies

View File

@@ -0,0 +1,93 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Basket View Model
Description:
Data model for store basket view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_store import Model_View_Store
# from routes import bp_home
from business_objects.product import Product
from forms import Form_Billing # Form_Product
# external
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store_Basket(Model_View_Store):
# Attributes
# product_categories: list # (str)
form_delivery: Form_Billing
form_billing: Form_Billing
forms_delivery_method: list # []
is_collapsed_info_billing: bool
is_collapsed_info_delivery: bool
# Global constants
# category_products: dict { category_enum_id: List[Product] }
hash_page_store_checkout = '/store/checkout'
hash_page_store_checkout_session = '/store/checkout_session'
hash_store_basket_info = '/store/basket_info'
id_container_info_billing = 'containerInfoBilling'
id_container_info_delivery = 'containerInfoDelivery'
id_overlay_info_delivery = 'overlayInfoDelivery'
id_overlay_info_billing = 'overlayInfoBilling'
key_address1 = 'address_1'
key_address2 = 'address_2'
key_city = 'city'
key_county = 'county'
key_id_checkout = 'checkout-session-id'
key_info_billing = 'billing-info'
key_info_delivery = 'delivery-info'
key_info_identical = 'identical'
key_info_type = 'type-info'
key_is_subscription = 'is-subscription'
key_name_full = 'name_full'
key_phone_number = 'phone_number'
key_postcode = 'postcode'
key_region = 'region'
key_url_checkout = 'checkout-url'
# Attributes
@property
def title(self):
return 'Store Basket'
def __new__(cls, db, id_user, app, id_currency, id_region_delivery):
# Initialiser - validation
return super(Model_View_Store_Basket, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
def __init__(self, db, id_user, app, id_currency, id_region_delivery):
# Constructor
super().__init__(db, id_user, app, id_currency, id_region_delivery)
# self.product_categories = Model_View_Store_Basket.get_many_product_category(get_all_category = True, get_all_product = True)
self.form_billing = Form_Billing()
self.form_billing.form_type_billing_not_delivery = True
self.form_delivery = Form_Billing()
# if logged in:
# else:
self.is_collapsed_info_billing = False
self.is_collapsed_info_delivery = False
"""
self.forms_product = {}
for cat in self.product_categories:
for product in cat:
if len(list(product.variations.keys())) == 0:
new_form = Form_Product()
if new_form.validate_on_submit():
# Handle form submission
self.add_2_basket(product.id, )
self.forms[str(product.id)] = new_form
"""

View File

@@ -0,0 +1,75 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Checkout View Model
Description:
Data model for store checkout view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_store import Model_View_Store
from models.model_view_store_basket import Model_View_Store_Basket
# from routes import bp_home
from business_objects.product import Product
from forms import Form_Billing # Form_Product
import lib.argument_validation as av
from datastores.datastore_store import DataStore_Store
# external
import os
import stripe
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store_Checkout(Model_View_Store_Basket):
# Attributes
key_secret_stripe: str
key_public_stripe: str
# Global constants
key_id_price = 'price_id'
@property
def title(self):
return 'Store Checkout'
def __new__(cls, db, id_user, app):
# Initialiser - validation
return super(Model_View_Store_Checkout, cls).__new__(cls, db, id_user, app)
def __init__(self, db, id_user, app):
# Constructor
super().__init__(db, id_user, app)
self.key_secret_stripe = os.environ.get("KEY_SECRET_STRIPE")
self.key_public_stripe = os.environ.get("KEY_PUBLIC_STRIPE")
# For sample support and debugging, not required for production:
stripe.set_app_info(
'stripe-samples/checkout-one-time-payments',
version='0.0.1',
url='https://github.com/stripe-samples/checkout-one-time-payments')
stripe.api_key = self.key_secret_stripe
def create_product(self, product): # _name, product_description):
return DataStore_Store(self.db, self.info_user).create_product(product) # _name, product_description)
def create_price(self, product, currency):
return DataStore_Store(self.db, self.info_user).create_price(product, currency)
def get_many_product_new(self):
return DataStore_Store(self.db, self.info_user).get_many_product_new()
"""
def get_price_id(product_ids):
return DataStore_Store().get_many_id_price(product_ids)
"""

View File

@@ -0,0 +1,55 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Checkout Success View Model
Description:
Data model for store checkout success view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_store import Model_View_Store
from models.model_view_store_checkout import Model_View_Store_Checkout
# from routes import bp_home
from business_objects.product import Product
from forms import Form_Billing # Form_Product
import lib.argument_validation as av
from datastores.datastore_store import DataStore_Store
# external
import os
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store_Checkout_Success(Model_View_Store_Checkout):
# Attributes
key_secret_stripe: str
key_public_stripe: str
# Global constants
key_id_price = 'price_id'
@property
def title(self):
return 'Store Checkout Success'
def __new__(cls, db, id_user, app, id_checkout_session, checkout_items = None):
# Initialiser - validation
_m = 'Model_View_Store_Checkout_Success.__new__'
# av.val_list(checkout_items, 'checkout_items', _m)
av.val_str(id_checkout_session, 'id_checkout_session', _m)
return super(Model_View_Store_Checkout_Success, cls).__new__(cls, db, id_user, app)
def __init__(self, db, id_user, app, id_checkout_session, checkout_items = None):
# Constructor
super().__init__(db, id_user, app)
self.checkout_items = checkout_items
self.id_checkout_session = id_checkout_session
self.order = self.get_many_user_order('', 1, id_checkout_session)

View File

@@ -0,0 +1,63 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Home View Model
Description:
Data model for store home view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_store import Model_View_Store
# from routes import bp_home
from business_objects.product import Product
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
# external
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store_Home(Model_View_Store):
# Attributes
product_categories: list # (str)
forms_product: dict
forms_basket: dict
# Global constants
# category_products: dict { category_enum_id: List[Product] }
# Attributes
@property
def title(self):
return 'Store Home'
max_products_per_category = -1
def __new__(cls, db, id_user, app, id_currency, id_region_delivery):
# Initialiser - validation
return super(Model_View_Store_Home, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
def __init__(self, db, id_user, app, id_currency, id_region_delivery):
# Constructor
super().__init__(db, id_user, app, id_currency, id_region_delivery)
# self.categories = Model_View_Store_Home.get_many_product_category(self.db, get_all_category = True, get_all_product = True)
# self.get_many_product_category(get_all_category = True, get_all_product = True)
"""
self.forms_product = {}
for cat in self.product_categories:
for product in cat:
if len(list(product.variations.keys())) == 0:
new_form = Form_Product()
if new_form.validate_on_submit():
# Handle form submission
self.add_2_basket(product.id, )
self.forms[str(product.id)] = new_form
"""

View File

@@ -0,0 +1,82 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: Store Product View Model
Description:
Data model for store product view
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
# internal
from models.model_view_store import Model_View_Store
from datastores.datastore_store import DataStore_Store
# from routes import bp_home
from business_objects.product import Product, Product_Filters
import lib.argument_validation as av
# external
# VARIABLE INSTANTIATION
# CLASSES
class Model_View_Store_Product(Model_View_Store):
# categories: list # (str)
# category_products: dict { category_enum_id: List[Product] }
# Attributes
@property
def title(self):
return 'Store Home'
def __new__(cls, db, id_user, app, id_permutation, id_currency, id_region_delivery): # *args, **kwargs
# Initialiser - validation
_m = 'Model_View_Store_Product.__new__'
print(f'{_m}\nstarting...')
v_arg_type = 'class attribute'
# av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
# av.val_int(id_product, 'id_product', _m, v_arg_type=v_arg_type)
# av.val_int(id_permutation, 'id_permutation', _m, v_arg_type=v_arg_type)
print(f'user id: {id_user.get("sub")}')
print(f'ending')
# return super().__new__(cls, *args, **kwargs) # Model_View_Store_Product, cls # , db, id_user, id_product) # , db, id_user)
return super(Model_View_Store_Product, cls).__new__(cls, db, id_user, app, id_currency, id_region_delivery)
def __init__(self, db, id_user, app, id_permutation, id_currency, id_region_delivery):
# Constructor
_m = 'Model_View_Store_Product.__init__'
print(f'{_m}\nstarting...')
super().__init__(db, id_user, app, id_currency, id_region_delivery)
print('supered')
print(f'user info: {self.info_user}')
# print(f'user id: {self.info_user.get("sub")}')
category_list = DataStore_Store(self.db, self.info_user).get_many_product_category(Product_Filters(
self.info_user['sub'],
True, '', False,
True, '', False, False,
False, str(id_permutation), False,
True, '', False, False,
False, str(id_region_delivery), False,
False, str(id_currency), False,
True, '', False
)) # product_ids=str(id_product), permutation_ids=str(id_permutation))
print('connection to db successful')
# self.categories = categories
# self.category_index = category_index
if (category_list.get_count_categories() > 0):
self.product = category_list.get_permutation_first()
else:
self.product = None
print('selected permutation selected')

182
pay_stripe.py Normal file
View File

@@ -0,0 +1,182 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App General
Feature: App
Description:
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
import os
import stripe
import json
from flask import Flask, render_template, render_template_string, jsonify, request, send_from_directory, redirect
from dotenv import load_dotenv, find_dotenv
from config import app_config
# VARIABLE INSTANTIATION
key_secret = os.environ.get("KEY_SECRET_STRIPE")
key_public = os.environ.get("KEY_PUBLIC_STRIPE") # 'pk_test_51OGrxlL7BuLKjoMpfpfw7bSmZZK1MhqMoQ5VhW2jUj7YtoEejO4vqnxKPiqTHHuh9U4qqkywbPCSI9TpFKtr4SYH007KHMWs68'
# METHODS
def create_product_price():
print(f'stripe.api_key = {stripe.api_key}')
starter_subscription = stripe.Product.create(
name="Starter Subscription",
description="$12/Month subscription",
)
starter_subscription_price = stripe.Price.create(
unit_amount=1200,
currency="usd",
recurring={"interval": "month"},
product=starter_subscription['id'],
)
# Save these identifiers
print(f"Success! Here is your starter subscription product id: {starter_subscription.id}")
print(f"Success! Here is your starter subscription price id: {starter_subscription_price.id}")
return starter_subscription_price.id
def get_file_str(f_address):
f = open(f_address)
return f.read()
# Ensure environment variables are set.
price = os.getenv('PRICE')
if price is None or price == 'price_12345' or price == '':
print('You must set a Price ID in .env. Please see the README.')
exit(0)
# For sample support and debugging, not required for production:
stripe.set_app_info(
'stripe-samples/checkout-one-time-payments',
version='0.0.1',
url='https://github.com/stripe-samples/checkout-one-time-payments')
# stripe.api_version = '2020-08-27'
stripe.api_key = key_secret # os.getenv('KEY_SECRET_STRIPE')
# app_dir = str(os.path.abspath(os.path.join(
# __file__, "..", "..")))
# static_dir = str(os.path.abspath(os.path.join(
# app_dir, os.getenv("STATIC_DIR"))))
# template_dir = str(os.path.abspath(os.path.join(
# app_dir, os.getenv("TEMPLATE_DIR"))))
app = Flask(__name__) # , static_folder=static_dir,
# static_url_path="", template_folder=template_dir)
app.config.from_object(app_config)
@app.route('/', methods=['GET'])
def home():
# return render_template(f'{app_dir}\\templates\\_home.html') # f'{app_dir}\\templates\\layout.html')
# return render_template_string(get_file_str(f'{app_dir}\\templates\\_home.html')) # f'{app_dir}\\templates\\layout.html')
return render_template('_home.html', title='Home')
@app.route('/store', methods=['GET'])
def store_home():
return render_template('_store_home.html', title='Store Home')
@app.route('/contact')
def contact():
return render_template('_contact.html', title='Contact Us')
@app.route('/config', methods=['GET'])
def get_publishable_key():
price = stripe.Price.retrieve(os.getenv('PRICE'))
return jsonify({
'publicKey': key_public, # os.getenv('KEY_PUBLIC_STRIPE'),
'unitAmount': price['unit_amount'],
'currency': price['currency']
})
# Fetch the Checkout Session to display the JSON result on the success page
@app.route('/checkout-session', methods=['GET'])
def get_checkout_session():
id = request.args.get('sessionId')
print(f'checkout session id: {id}')
checkout_session = stripe.checkout.Session.retrieve(id)
return jsonify(checkout_session)
@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
quantity = request.form.get('quantity', 1)
domain_url = os.getenv('DOMAIN')
try:
# Create new Checkout Session for the order
# Other optional params include:
# [billing_address_collection] - to display billing address details on the page
# [customer] - if you have an existing Stripe Customer ID
# [payment_intent_data] - lets capture the payment later
# [customer_email] - lets you prefill the email input in the form
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
# For full details see https://stripe.com/docs/api/checkout/sessions/create
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
checkout_session = stripe.checkout.Session.create(
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
cancel_url=domain_url + '/canceled.html',
mode='subscription', # 'payment',
# automatic_tax={'enabled': True},
line_items=[{
'price': os.getenv('PRICE'),
'quantity': quantity,
}]
)
return redirect(checkout_session.url, code=303)
except Exception as e:
return jsonify(error=str(e)), 403
@app.route('/webhook', methods=['POST'])
def webhook_received():
# You can use webhooks to receive information about asynchronous payment events.
# For more about our webhook events check out https://stripe.com/docs/webhooks.
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
request_data = json.loads(request.data)
if webhook_secret:
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
signature = request.headers.get('stripe-signature')
try:
event = stripe.Webhook.construct_event(
payload=request.data, sig_header=signature, secret=webhook_secret)
data = event['data']
except Exception as e:
return e
# Get the type of webhook event sent - used to check the status of PaymentIntents.
event_type = event['type']
else:
data = request_data['data']
event_type = request_data['type']
data_object = data['object']
print('event ' + event_type)
if event_type == 'checkout.session.completed':
print('🔔 Payment succeeded!')
return jsonify({'status': 'success'})
if __name__ == '__main__':
# stripe.api_key = key_secret
# create_product_price()
# Setup Stripe python client library.
load_dotenv(find_dotenv())
app.run(port=4242, debug=True)

Binary file not shown.

170
payments/pay_stripe.py Normal file
View File

@@ -0,0 +1,170 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: App General
Feature: App
Description:
Initializes the Flask application, sets the configuration based on the environment, and defines two routes (/ and /about) that render templates with the specified titles.
"""
# IMPORTS
# VARIABLE INSTANTIATION
# METHODS
# IMPORTS
import os
import stripe
import json
from flask import Flask, render_template, render_template_string, jsonify, request, send_from_directory, redirect
from dotenv import load_dotenv, find_dotenv
# VARIABLE INSTANTIATION
key_secret = os.environ.get("KEY_SECRET_STRIPE")
key_public = os.environ.get("KEY_PUBLIC_STRIPE") # 'pk_test_51OGrxlL7BuLKjoMpfpfw7bSmZZK1MhqMoQ5VhW2jUj7YtoEejO4vqnxKPiqTHHuh9U4qqkywbPCSI9TpFKtr4SYH007KHMWs68'
# METHODS
def create_product_price():
print(f'stripe.api_key = {stripe.api_key}')
starter_subscription = stripe.Product.create(
name="Starter Subscription",
description="$12/Month subscription",
)
starter_subscription_price = stripe.Price.create(
unit_amount=1200,
currency="usd",
recurring={"interval": "month"},
product=starter_subscription['id'],
)
# Save these identifiers
print(f"Success! Here is your starter subscription product id: {starter_subscription.id}")
print(f"Success! Here is your starter subscription price id: {starter_subscription_price.id}")
return starter_subscription_price.id
def get_file_str(f_address):
f = open(f_address)
return f.read()
# Ensure environment variables are set.
price = os.getenv('PRICE')
if price is None or price == 'price_12345' or price == '':
print('You must set a Price ID in .env. Please see the README.')
exit(0)
# For sample support and debugging, not required for production:
stripe.set_app_info(
'stripe-samples/checkout-one-time-payments',
version='0.0.1',
url='https://github.com/stripe-samples/checkout-one-time-payments')
# stripe.api_version = '2020-08-27'
stripe.api_key = key_secret # os.getenv('KEY_SECRET_STRIPE')
app_dir = str(os.path.abspath(os.path.join(
__file__, "..", "..")))
static_dir = str(os.path.abspath(os.path.join(
app_dir, os.getenv("STATIC_DIR"))))
app = Flask(__name__, static_folder=static_dir,
static_url_path="", template_folder=static_dir)
@app.route('/', methods=['GET'])
def get_example():
# return render_template(f'{app_dir}\\templates\\_home.html') # f'{app_dir}\\templates\\layout.html')
# return render_template_string(get_file_str(f'{app_dir}\\templates\\_home.html')) # f'{app_dir}\\templates\\layout.html')
return render_template('../templates/_home.html')
@app.route('/config', methods=['GET'])
def get_publishable_key():
price = stripe.Price.retrieve(os.getenv('PRICE'))
return jsonify({
'publicKey': key_public, # os.getenv('KEY_PUBLIC_STRIPE'),
'unitAmount': price['unit_amount'],
'currency': price['currency']
})
# Fetch the Checkout Session to display the JSON result on the success page
@app.route('/checkout-session', methods=['GET'])
def get_checkout_session():
id = request.args.get('sessionId')
checkout_session = stripe.checkout.Session.retrieve(id)
return jsonify(checkout_session)
@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
quantity = request.form.get('quantity', 1)
domain_url = os.getenv('DOMAIN')
try:
# Create new Checkout Session for the order
# Other optional params include:
# [billing_address_collection] - to display billing address details on the page
# [customer] - if you have an existing Stripe Customer ID
# [payment_intent_data] - lets capture the payment later
# [customer_email] - lets you prefill the email input in the form
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
# For full details see https://stripe.com/docs/api/checkout/sessions/create
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
checkout_session = stripe.checkout.Session.create(
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
cancel_url=domain_url + '/canceled.html',
mode='payment',
# automatic_tax={'enabled': True},
line_items=[{
'price': os.getenv('PRICE'),
'quantity': quantity,
}]
)
return redirect(checkout_session.url, code=303)
except Exception as e:
return jsonify(error=str(e)), 403
@app.route('/webhook', methods=['POST'])
def webhook_received():
# You can use webhooks to receive information about asynchronous payment events.
# For more about our webhook events check out https://stripe.com/docs/webhooks.
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
request_data = json.loads(request.data)
if webhook_secret:
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
signature = request.headers.get('stripe-signature')
try:
event = stripe.Webhook.construct_event(
payload=request.data, sig_header=signature, secret=webhook_secret)
data = event['data']
except Exception as e:
return e
# Get the type of webhook event sent - used to check the status of PaymentIntents.
event_type = event['type']
else:
data = request_data['data']
event_type = request_data['type']
data_object = data['object']
print('event ' + event_type)
if event_type == 'checkout.session.completed':
print('🔔 Payment succeeded!')
return jsonify({'status': 'success'})
if __name__ == '__main__':
# stripe.api_key = key_secret
# create_product_price()
# Setup Stripe python client library.
load_dotenv(find_dotenv())
app.run(port=4242, debug=True)

24
requirements.txt Normal file
View File

@@ -0,0 +1,24 @@
flask
flask_wtf
flask_sqlalchemy
flask_cors
authlib
jwt
mysqlclient
# environment variables
# KEY_SECRET_FLASK = 'random secret key'
# # KEY_PUBLIC_FLASK = ''
# ID_AUTH0_CLIENT = ''
# ID_AUTH0_CLIENT_SECRET = ''
# DOMAIN_AUTH0=dev-nwak2066ef6h8ixn.us.auth0.com
# SQLALCHEMY_DATABASE_URI = 'mysql://username:password@localhost/dbname'
# Replace 'username', 'password', 'localhost', and 'dbname' with your actual database credentials
# run with:
# python -m flask run
# not env var:
# auth0 recovery key: S92HY59VU5J9YDVYWCRHSKJ8

54
routes.py Normal file
View File

@@ -0,0 +1,54 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Controller - Webpage routing
Description:
Defines the routes and view functions for each page.
Manages the interaction between the frontend and backend.
"""
from flask import render_template, url_for, Blueprint
from app import app
from app.forms import Form_Contact
# from forms import MyForm
# from app import MyForm
from model_view_contact import Model_View_Contact
"""
@app.route('/', methods=['GET'])
def home():
return render_template('_home.html', title='Home')
@app.route('/store', methods=['GET'])
def store_home():
return render_template('_store_home.html', title='Store Home')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = Form_Contact()
if form.validate_on_submit():
# Handle form submission
email = form.sender_email.data
CC = form.sender_CC.data
name = form.sender_name.data
msg = form.sender_message.data
# return render_template('contact.html', form=form)
# return render_template('_contact.html', title='Contact Us')
return render_template('contact.html', model=Model_View_Contact(form))
@app.route('/about')
def about():
return render_template('about.html')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = MyForm()
if form.validate_on_submit():
# Handle form submission
pass
return render_template('contact.html', form=form)
"""

16
run.py Normal file
View File

@@ -0,0 +1,16 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Backend
Feature: Launcher
Description:
Runs project.
"""
from app import app
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -0,0 +1,96 @@
@echo off
setlocal enabledelayedexpansion
:: set "test=C:\C:\ \"
set dir_parent=C:\Users\edwar\OneDrive\Documents\Programming\Visual Studio 2022\PARTS_Web\app\static\sql
set "f_list=file_list.txt"
set dir_current=%cd%
set "f_tmp=temp.txt"
set "f_combine=000_combine.sql"
set verbose=0
set "strs_delete_0=920_edit_permissions.sql"
set "strs_delete_1=910_anal.sql"
set "strs_delete_2=deprecated"
set "strs_delete_3=%f_list%"
set "strs_delete_4=%f_tmp%"
set "strs_delete_5=701_p_shop_get_many_role_permission.sql"
set "strs_delete_6=600_p_shop_save_product.sql"
set "strs_delete_7=170_ish_tbl_ERP_Order.sql"
set strs_n_max=7
set strs_list =%strs_delete_0% %strs_delete_1% %strs_delete_2% %strs_delete_3% %strs_delete_4%
set "str_true=true"
set "str_replace="
set "str_list="
:: report constants
echo temp = %f_tmp%
echo dir_parent = !%dir_parent%!
echo dir_current = !%dir_current%!
echo file_list = %f_list%
echo file = %f_combine%
echo n strings = !strs_n_max!
:: begin
cd %dir_parent%
::echo current directory: %cd%
del %f_tmp%
del %f_list%
del %f_combine%
:: colate dir files
dir /b > %f_list%
::type %f_list%
echo loopy
:: Remove blacklist files
:: grep -v '920_edit_permissions.sql' %f_list% > %f_list%
:: grep -v '910_anal.sql' %f_list% > %f_list%
(for /f "delims=" %%a in (%f_list%) do (
::if %verbose% gtr 0 ( echo new line )
set "line=%%a"
::if %verbose% gtr 0 ( echo new line = !line! )
::if !line! neq %strs_delete_0% if !line! neq %strs_delete_1% if !line! neq %strs_delete_2% if !line! neq %strs_delete_3% if !line! neq %strs_delete_4% if !line! neq %strs_delete_5% if !line! neq %strs_delete_6% (
:: echo !line!
:: set "str_list=!str_list! !line!"
::)
set include_line=1
for /L %%i in (0, 1, %strs_n_max%) do (
::set "str_delete=!strs_delete_%%i!"
if !line! equ !strs_delete_%%i! (
set include_line=0
)
::set "include_line=!include_line!"
set "line=!line!"
)
if !include_line! gtr 0 (
echo !line!
set "str_list=!str_list! !line!"
)
set "line=!line!"
)) > %f_tmp%
:: Combine files
echo output list:
type %f_tmp%
del %f_list%
echo file_tmp: %f_tmp%
echo file_list: %f_list%
echo combining files
::type %f_tmp% | type > %f_list%
::
::type %f_list%
echo cmd:
echo "type !str_list! > %f_combine%"
type !str_list! > %f_combine%
cd %dir_current%
echo Current Time: %TIME%
endlocal

15
static/css/contact.css Normal file
View File

@@ -0,0 +1,15 @@
/*
.content > a {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
text-align: center;
}
.content > a > img, .content > a > h4 {
flex: content;
margin: 0px;
}
*/

14
static/css/home.css Normal file
View File

@@ -0,0 +1,14 @@
.img-demo {
max-width: 50%;
min-width: 500px;
}
.img-featured {
width: 100%;
}
/*
img {
background-image: url("/static/images/Tag_Molly1.png");
}
*/

302
static/css/shared.css Normal file
View File

@@ -0,0 +1,302 @@
:root {
/* Declare global variables */
--c_purple: #5B29FF;
--c_purple_pastel: #D1D1FF;
--c_purple_light: #C6BDFF;
--c_purple_dark: #4700B3;
--c_blue: #0044FF;
--c_blue_pastel: #B8E0FF;
--c_blue_light: #73E8FF;
--c_blue_dark: #003ADB;
}
*{
box-sizing: border-box;
}
body {
font-family: Arial;
padding: 10px;
background: var(--c_purple_pastel);
}
h1 {
font-size: 4vh;
}
h2 {
font-size: 2.4vh;
}
h3 {
font-size: 2vh;
margin: 1vh;
}
h4 {
font-size: 1.5vh;
margin: 1vh;
}
h5 {
font-size: 1.25vh;
margin: 1vh;
}
/* Header/Blog Title */
.header {
padding: 1vh;
text-align: center;
background-color: var(--c_purple_light);
}
/* Style the top navigation bar */
.topnav {
overflow: hidden;
background-color: var(--c_purple);
border-bottom-left-radius: 2.5vh;
border-bottom-right-radius: 2.5vh;
flex-wrap: wrap;
}
/* Style the topnav links */
.topnav a, .topnav label {
float: left;
display: block;
color: white;
text-align: center;
/* padding: 14px 16px; */
text-decoration: none;
}
.topnav a {
padding: 3vh 2vw;
}
/* Change color on hover */
.topnav a:hover {
background-color: var(--c_purple_light);
color: var(--c_purple_dark);
}
.topnav .container {
max-width: 20%;
height: 100%;
}
/* Create two unequal columns that floats next to each other */
/* Left column */
.leftcolumn {
float: left;
width: 70%;
display: flex;
flex-wrap: wrap;
/* min-width: fit-content; */
align-items: center;
justify-content: center;
}
/* Right column */
.rightcolumn {
float: left;
width: 30%;
display: flex;
flex-wrap: wrap;
/* min-width: fit-content; only on store?? */
/* background-color: #f1f1f1; */
padding-left: 20px;
height: fit-content;
align-items: center;
justify-content: center;
}
/* Fake image */
.fakeimg {
background-color: var(--c_purple_light);
width: 100%;
padding: 20px;
}
img, video {
border-radius: 3vh;
}
/* header image */
img.header-logo {
max-height: 15vh;
}
/* icon images */
.img-icon {
max-width: 5vh;
max-height: 5vh;
border-radius: 1vh;
}
.container-icon-label {
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
text-align: center;
}
.container-icon-label > * {
display: inline-flex;
margin-left: 1vh;
margin-right: 1vh;
}
.header > .container:first-of-type {
max-width: 25%;
justify-self: left;
}
.header > .container:last-of-type {
max-width: 75%;
justify-self: left;
}
/* Add a card effect for articles */
.card {
background-color: white;
padding: 1vh;
margin-top: 3vh;
display: flex !important;
flex-wrap: wrap;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 4vh;
min-width: fit-content;
}
.card.subcard {
margin-top: 0;
}
.header.card {
border-radius: 2.5vh;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.container {
flex: 1;
margin: 0px;
align-items: center;
justify-content: center;
text-align: center;
max-width: 100%;
min-width: fit-content;
}
.column {
display: flex;
flex-direction: column;
}
.row {
display: flex;
flex-direction: row;
width: 100%;
min-width: fit-content;
}
.container > .card:first-of-type {
margin-top: none;
}
/* Clear floats after the columns
.row:after {
content: "";
display: table;
clear: both;
}
*/
/* Footer */
.footer {
padding: 1vh;
text-align: center;
background: var(--c_purple_light);
margin-top: 20px;
border-radius: 2.5vh;
}
.footer > h4, h5 {
padding: 0;
margin: 1vh;
}
/* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */
@media screen and (max-width: 800px) {
.leftcolumn, .rightcolumn {
width: 100%;
/* padding: 0; */
}
}
/* Responsive layout - when the screen is less than 400px wide, make the navigation links stack on top of each other instead of next to each other */
@media screen and (max-width: 400px) {
.topnav a {
float: none;
width: 100%;
}
}
/* input container
margin-top: 3vh;
*/
.container-input {
padding: 1vh;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
text-align: center;
}
.container-input > label {
width: 100%;
margin-bottom: 1vh;
margin-top: 0;
}
.container-input:not(:nth-child(3)) > label {
margin-top: 1vh;
}
.container-input > input, .container-input > textarea {
border: 2px solid var(--c_purple);
max-width: 50%;
min-width: 40%;
padding: 1vh;
}
.label-title {
width: 100%;
}
button, .btn-submit, input[type="submit"] {
font-size: 20px;
font-weight: bold;
border: 4px solid;
border-radius: 2vh;
padding: 1vh 2vh 1vh 2vh;
margin: 0.5vh;
background-color: var(--c_blue_pastel);
color: var(--c_blue_dark);
border-color: var(--c_blue_dark);
}
/* Overlay modal */
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 999;
}

View File

View File

View File

@@ -0,0 +1,55 @@
.img-product {
max-width: 20vh;
max-height: 20vh;
border-radius: 3vh;
justify-self: left;
}
.img-thumbnail {
max-width: 10vh;
max-height: 10vh;
border-radius: 3vh;
justify-self: left;
}
.btnAdd2Basket {
background-color: var(--c_blue_pastel);
color: var(--c_blue_dark);
border-color: var(--c_blue_dark);
}
#btnCheckout, .btnBuyNow {
background-color: var(--c_purple_pastel);
color: var(--c_purple_dark);
border-color: var(--c_purple_dark);
}
.btn-increment, .btn-decrement {
border: 2px solid darkgrey;
background-color: lightgray;
margin: 1vh 1vh;
width: 2.5vh;
height: 2.5vh;
border-radius: 1.25vh;
font-size: 2vh;
}
.container-input > input {
padding: 0vh 1vh;
border-radius: 0.5vh;
max-width: 7vh;
}
#basket {
max-width: 100%;
}
.basket-item-delete {
text-decoration: underline;
}
/* Right column */
.rightcolumn {
min-width: fit-content;
}

86
static/css/stylesheet.css Normal file
View File

@@ -0,0 +1,86 @@
* {
box-sizing: border-box;
}
body {
padding: 10px;
display: block;
background-color: grey;
}
.banner {
background-color: black;
color: white;
width: 100%;
position: fixed;
padding-top: 5vh;
padding-bottom: 10vh;
}
.banner.top {
background-color: black;
color: white;
}
.banner.bottom {
background-color: black;
color: white;
bottom: 0;
}
.row:after {
content: "";
display: table;
clear: both;
}
.column {
float: left;
padding: 5vw;
}
.column.side {
width: 25%;
}
.column.middle {
width: 50%;
}
.midbod {
background-color: white;
color: black;
}
.panel {
float: left;
}
img {
height: 100px;
width: 100px;
}
.panel.labelcontainer {
background-color: black;
color: white;
text-align: center;
}
.label {
font-family: Arial, Helvetica, sans-serif;
text-decoration: none;
}
.label.bodytext {
background-color: black;
color: white;
text-decoration: none;
font-style: normal;
font-size: 12;
}
.label.title {
font-style: bold;
font-size: 18;
}

BIN
static/docs/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

124
static/docs/server.py Normal file
View File

@@ -0,0 +1,124 @@
#! /usr/bin/env python3.6
"""
server.py
Stripe Sample.
Python 3.6 or newer required.
"""
import stripe
import json
import os
from flask import Flask, render_template, jsonify, request, send_from_directory, redirect
from dotenv import load_dotenv, find_dotenv
# Setup Stripe python client library.
load_dotenv(find_dotenv())
# Ensure environment variables are set.
price = os.getenv('PRICE')
if price is None or price == 'price_12345' or price == '':
print('You must set a Price ID in .env. Please see the README.')
exit(0)
# For sample support and debugging, not required for production:
stripe.set_app_info(
'stripe-samples/checkout-one-time-payments',
version='0.0.1',
url='https://github.com/stripe-samples/checkout-one-time-payments')
stripe.api_version = '2020-08-27'
stripe.api_key = os.getenv('STRIPE_KEY_SECRET')
static_dir = str(os.path.abspath(os.path.join(
__file__, "..", os.getenv("STATIC_DIR"))))
app = Flask(__name__, static_folder=static_dir,
static_url_path="", template_folder=static_dir)
@app.route('/', methods=['GET'])
def get_example():
return render_template('index.html')
@app.route('/config', methods=['GET'])
def get_publishable_key():
price = stripe.Price.retrieve(os.getenv('PRICE'))
return jsonify({
'publicKey': os.getenv('STRIPE_PUBLISHABLE_KEY'),
'unitAmount': price['unit_amount'],
'currency': price['currency']
})
# Fetch the Checkout Session to display the JSON result on the success page
@app.route('/checkout-session', methods=['GET'])
def get_checkout_session():
id = request.args.get('sessionId')
checkout_session = stripe.checkout.Session.retrieve(id)
return jsonify(checkout_session)
@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
quantity = request.form.get('quantity', 1)
domain_url = os.getenv('DOMAIN')
try:
# Create new Checkout Session for the order
# Other optional params include:
# [billing_address_collection] - to display billing address details on the page
# [customer] - if you have an existing Stripe Customer ID
# [payment_intent_data] - lets capture the payment later
# [customer_email] - lets you prefill the email input in the form
# [automatic_tax] - to automatically calculate sales tax, VAT and GST in the checkout page
# For full details see https://stripe.com/docs/api/checkout/sessions/create
# ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
checkout_session = stripe.checkout.Session.create(
success_url=domain_url + '/success.html?session_id={CHECKOUT_SESSION_ID}',
cancel_url=domain_url + '/canceled.html',
mode='payment',
# automatic_tax={'enabled': True},
line_items=[{
'price': os.getenv('PRICE'),
'quantity': quantity,
}]
)
return redirect(checkout_session.url, code=303)
except Exception as e:
return jsonify(error=str(e)), 403
@app.route('/webhook', methods=['POST'])
def webhook_received():
# You can use webhooks to receive information about asynchronous payment events.
# For more about our webhook events check out https://stripe.com/docs/webhooks.
webhook_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
request_data = json.loads(request.data)
if webhook_secret:
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
signature = request.headers.get('stripe-signature')
try:
event = stripe.Webhook.construct_event(
payload=request.data, sig_header=signature, secret=webhook_secret)
data = event['data']
except Exception as e:
return e
# Get the type of webhook event sent - used to check the status of PaymentIntents.
event_type = event['type']
else:
data = request_data['data']
event_type = request_data['type']
data_object = data['object']
print('event ' + event_type)
if event_type == 'checkout.session.completed':
print('🔔 Payment succeeded!')
return jsonify({'status': 'success'})
if __name__ == '__main__':
app.run(port=4242, debug=True)

View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<link href="../css/copiedstyles.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div class="header">
<h1>My Website</h1>
<p>Resize the browser window to see the effect.</p>
</div>
<div class="topnav">
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#" style="float:right">Link</a>
</div>
<div class="row">
<div class="leftcolumn">
<div class="card">
<h2>TITLE HEADING</h2>
<h5>Title description, Dec 7, 2017</h5>
<div class="fakeimg" style="height:200px;">Image</div>
<p>Some text..</p>
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
</div>
<div class="card">
<h2>TITLE HEADING</h2>
<h5>Title description, Sep 2, 2017</h5>
<div class="fakeimg" style="height:200px;">Image</div>
<p>Some text..</p>
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
</div>
</div>
<div class="rightcolumn">
<div class="card">
<h2>About Me</h2>
<div class="fakeimg" style="height:100px;">Image</div>
<p>Some text about me in culpa qui officia deserunt mollit anim..</p>
</div>
<div class="card">
<h3>Popular Post</h3>
<div class="fakeimg"><p>Image</p></div>
<div class="fakeimg"><p>Image</p></div>
<div class="fakeimg"><p>Image</p></div>
</div>
<div class="card">
<h3>Follow Me</h3>
<p>Some text..</p>
</div>
</div>
</div>
<div class="footer">
<h2>Footer</h2>
</div>
</body>
</html>

1
static/docs/test.py Normal file
View File

@@ -0,0 +1 @@
print(not None)

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
static/images/CAD.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Binary file not shown.

BIN
static/images/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

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