Store app.py deprecated.\nAdmin pages added: privacy notice, accessibility, data retention, license. Improvements to accessibility of those pages pending.
This commit is contained in:
726
DEPRECATED_app.py
Normal file
726
DEPRECATED_app.py
Normal file
@@ -0,0 +1,726 @@
|
||||
"""
|
||||
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
|
||||
from flask_mail import Mail, Message
|
||||
import stripe
|
||||
import json
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
import os
|
||||
import sys
|
||||
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='')
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
try:
|
||||
# db = SQLAlchemy(app)
|
||||
db = SQLAlchemy()
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
db.engine.url = app.config.SQLALCHEMY_DATABASE_URI
|
||||
app.errors = 'none'
|
||||
except Exception as e:
|
||||
app.errors = str(e)
|
||||
|
||||
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': ''}}
|
||||
|
||||
mail = Mail(app)
|
||||
|
||||
|
||||
# METHODS
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
|
||||
def application(environ, start_response):
|
||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||
message = 'It works!\n'
|
||||
version = 'Python %s\n' % sys.version.split()[0]
|
||||
response = '\n'.join([message, version])
|
||||
return [response.encode()]
|
||||
|
||||
|
||||
# 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'])
|
||||
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
|
||||
mailItem = Message("PARTS Website Contact Us Message", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
mailItem.body = f"Dear Lord Edward Middleton-Smith,\n\n{msg}\n\nKind regards,\n{name}\n{email}"
|
||||
mail.send(mailItem)
|
||||
return render_template('_page_contact.html', model=Model_View_Contact(db, get_info_user(), app, form))
|
||||
|
||||
@app.route('/services', methods=['GET', 'POST'])
|
||||
@app.route('/public_html/services', methods=['GET', 'POST'])
|
||||
def services():
|
||||
return render_template('_page_services.html', model=Model_View_Home(db, get_info_user(), app))
|
||||
|
||||
|
||||
# Store
|
||||
@app.route('/store', methods=['GET', 'POST'])
|
||||
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
|
||||
try:
|
||||
id_region_delivery = data.id_region_delivery
|
||||
except:
|
||||
id_region_delivery = Model_View_Store.ID_REGION_DELIVERY_DEFAULT
|
||||
"""
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
print(f"id_currency = {id_currency}")
|
||||
print(f"id_region_delivery = {id_region_delivery}")
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
# model.get_regions_and_currencies()
|
||||
# model.categories = Model_View_Store_Home.get_many_product_category(db)
|
||||
# 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()}')
|
||||
if request.method == 'GET':
|
||||
return render_template('_page_store_home.html', model = model) # "<html><body><h1>Boobs</h1></html></body>"
|
||||
else: # POST request
|
||||
html_block = render_template('_block_store_home_body.html', model = model)
|
||||
print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success=True, data={'html_block': html_block})
|
||||
|
||||
# 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_View_Store.KEY_BASKET)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
# 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, is_included_VAT)
|
||||
# model.is_included_VAT = is_included_VAT
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
"""
|
||||
try:
|
||||
form_data[Model_View_Store.KEY_VALUE_DEFAULT]
|
||||
except KeyError:
|
||||
form_data[Model_View_Store.KEY_VALUE_DEFAULT] =
|
||||
"""
|
||||
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, is_included_VAT)
|
||||
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, is_included_VAT)
|
||||
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, is_included_VAT)
|
||||
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}')
|
||||
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, is_included_VAT)
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
form = Form_Billing(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
print('valid form')
|
||||
# model = input_JSON_basket(model, data)
|
||||
# if not logged in:
|
||||
data_info = {}
|
||||
try:
|
||||
info_type = data[model.key_info_type]
|
||||
print('importing address information')
|
||||
data_info[model.key_region] = form.region.data
|
||||
print(f'region: {data_info[model.key_region]}')
|
||||
data_info[model.key_name_full] = form.name_full.data
|
||||
print(f'full name: {data_info[model.key_name_full]}')
|
||||
data_info[model.key_phone_number] = form.phone_number.data
|
||||
print(f'phone number: {data_info[model.key_phone_number]}')
|
||||
data_info[model.key_postcode] = form.postcode.data
|
||||
print(f'postcode: {data_info[model.key_postcode]}')
|
||||
data_info[model.key_address1] = form.address_1.data
|
||||
print(f'address line 1: {data_info[model.key_address1]}')
|
||||
data_info[model.key_address2] = form.address_2.data
|
||||
print(f'address line 2: {data_info[model.key_address2]}')
|
||||
data_info[model.key_city] = form.city.data
|
||||
print(f'city: {data_info[model.key_city]}')
|
||||
data_info[model.key_county] = form.county.data
|
||||
print(f'county: {data_info[model.key_county]}')
|
||||
data_info[model.key_info_identical] = form.identical
|
||||
print(f'identical: {data_info[model.key_info_identical]}')
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
# html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
data = {}
|
||||
data[model.key_info_type] = model.key_info_billing if (info_type == model.key_info_billing) else model.key_info_delivery
|
||||
data[info_type] = data_info
|
||||
return jsonify(Success = True, data = data)
|
||||
return jsonify({'status': 'failure', 'Message': f'Invalid address information\n{form.errors}'})
|
||||
|
||||
|
||||
@app.route('/store/product?permutationId=<permutation_id>regionId=&<region_id>¤cyId=<currency_id>&isIncludedVAT=<is_included_VAT>', methods=['GET']) # <product_id>&
|
||||
def store_product(permutation_id, region_id, currency_id, is_included_VAT):
|
||||
_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, is_included_VAT)
|
||||
print('model reached')
|
||||
# model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(request.json)
|
||||
# model.get_many_product_category(product_ids = str(product_id))
|
||||
# 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')
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Checkout(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
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
|
||||
"""
|
||||
id_region_delivery = form_data[Model_View_Store.KEY_BASKET][Model_View_Base.KEY_ID_REGION_DELIVERY]
|
||||
print(f'id_region_delivery: {id_region_delivery}')
|
||||
return jsonify(Success=True, data={Model_View_Base.KEY_ID_REGION_DELIVERY: 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': ''}
|
||||
|
||||
"""
|
||||
@app.route('/send-email', methods=['GET'])
|
||||
def send_email():
|
||||
try:
|
||||
msg = Message("Flask Mail test", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
msg.body = "Dear Lord Edward Middleton-Smith,\n\nThis is a test email sent from Flask.\n\nKind regards,\nBot"
|
||||
mail.send(msg)
|
||||
except:
|
||||
return "<html>Error</html>"
|
||||
return "<html>Email sent</html>"
|
||||
"""
|
||||
|
||||
"""
|
||||
@app.route('/test-mysql', methods=['GET'])
|
||||
def test_mysql():
|
||||
model = Model_View_Store(db, get_info_user(), app)
|
||||
|
||||
_m = 'test_mysql'
|
||||
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 "<html>MySQL test</html>"
|
||||
"""
|
||||
|
||||
@app.route('/public_html/403.shtml', methods=['GET'])
|
||||
def error_403():
|
||||
return "<html>Error 403</html>"
|
||||
|
||||
|
||||
# Onload
|
||||
if True or __name__ == '__main__':
|
||||
app.run()
|
||||
# app.run(debug=True, host="0.0.0.0", port=5000)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
787
app.py
787
app.py
@@ -18,26 +18,18 @@ Initializes the Flask application, sets the configuration based on the environme
|
||||
# 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 forms import Form_Contact
|
||||
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
|
||||
from flask_mail import Mail, Message
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
import stripe
|
||||
import json
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
@@ -50,42 +42,75 @@ 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}')
|
||||
csrf = CSRFProtect(app)
|
||||
CORS(app)
|
||||
# app.register_blueprint(bp_home, url_prefix=''
|
||||
|
||||
# app.config.from_object(app_config)
|
||||
# app.app_config = app_config
|
||||
app.DEBUG = Config.DEBUG
|
||||
app.TESTING = Config.TESTING
|
||||
app.SECRET_KEY = "007cfbdaaf6d1eb27209720e8a5fc8ba0a334ae0be6fcac132b0a471549cde7c" # Config.SECRET_KEY
|
||||
app.config['SECRET_KEY'] = app.SECRET_KEY
|
||||
|
||||
app.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
|
||||
"""
|
||||
|
||||
try:
|
||||
# db = SQLAlchemy(app)
|
||||
db = SQLAlchemy()
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
db.engine.url = app.config.SQLALCHEMY_DATABASE_URI
|
||||
app.errors = 'none'
|
||||
except Exception as e:
|
||||
app.errors = str(e)
|
||||
app.config.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_DATABASE_URI
|
||||
app.config.SQLALCHEMY_TRACK_MODIFICATIONS = Config.SQLALCHEMY_TRACK_MODIFICATIONS
|
||||
app.config.ID_AUTH0_CLIENT = Config.ID_AUTH0_CLIENT
|
||||
app.config.ID_AUTH0_CLIENT_SECRET = Config.ID_AUTH0_CLIENT_SECRET
|
||||
app.config.DOMAIN_AUTH0 = Config.DOMAIN_AUTH0
|
||||
app.config.ID_TOKEN_USER = Config.ID_TOKEN_USER
|
||||
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = Config.SQLALCHEMY_DATABASE_URI
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Config.SQLALCHEMY_TRACK_MODIFICATIONS
|
||||
app.config['ID_AUTH0_CLIENT'] = Config.ID_AUTH0_CLIENT
|
||||
app.config['ID_AUTH0_CLIENT_SECRET'] = Config.ID_AUTH0_CLIENT_SECRET
|
||||
app.config['DOMAIN_AUTH0'] = Config.DOMAIN_AUTH0
|
||||
app.config['ID_TOKEN_USER'] = Config.ID_TOKEN_USER
|
||||
|
||||
app.MAIL_SERVER = Config.MAIL_SERVER
|
||||
app.MAIL_PORT = Config.MAIL_PORT
|
||||
app.MAIL_USE_TLS = Config.MAIL_USE_TLS
|
||||
app.MAIL_USERNAME = Config.MAIL_USERNAME
|
||||
app.MAIL_PASSWORD = Config.MAIL_PASSWORD
|
||||
app.MAIL_DEFAULT_SENDER = Config.MAIL_DEFAULT_SENDER
|
||||
|
||||
app.config.MAIL_SERVER = Config.MAIL_SERVER
|
||||
app.config.MAIL_PORT = Config.MAIL_PORT
|
||||
app.config.MAIL_USE_TLS = Config.MAIL_USE_TLS
|
||||
app.config.MAIL_USERNAME = Config.MAIL_USERNAME
|
||||
app.config.MAIL_PASSWORD = Config.MAIL_PASSWORD
|
||||
app.config.MAIL_DEFAULT_SENDER = Config.MAIL_DEFAULT_SENDER
|
||||
|
||||
app.config['MAIL_SERVER'] = Config.MAIL_SERVER
|
||||
app.config['MAIL_PORT'] = Config.MAIL_PORT
|
||||
app.config['MAIL_USE_TLS'] = Config.MAIL_USE_TLS
|
||||
app.config['MAIL_USERNAME'] = Config.MAIL_USERNAME
|
||||
app.config['MAIL_PASSWORD'] = Config.MAIL_PASSWORD
|
||||
app.config['MAIL_DEFAULT_SENDER'] = Config.MAIL_DEFAULT_SENDER
|
||||
app.config['MAIL_CONTACT_PUBLIC'] = Config.MAIL_CONTACT_PUBLIC
|
||||
|
||||
# app.RECAPTCHA_PUBLIC_KEY = Config.RECAPTCHA_PUBLIC_KEY
|
||||
app.config['RECAPTCHA_PUBLIC_KEY'] = Config.RECAPTCHA_PUBLIC_KEY
|
||||
# app.RECAPTCHA_PRIVATE_KEY = Config.RECAPTCHA_PRIVATE_KEY
|
||||
app.config['RECAPTCHA_PRIVATE_KEY'] = Config.RECAPTCHA_PRIVATE_KEY
|
||||
|
||||
# db = SQLAlchemy(app)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = Config.SQLALCHEMY_DATABASE_URI
|
||||
db = SQLAlchemy()
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
db.engine.url = app.config.SQLALCHEMY_DATABASE_URI
|
||||
|
||||
"""
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
"auth0",
|
||||
@@ -97,13 +122,27 @@ oauth.register(
|
||||
server_metadata_url=f'https://{app.DOMAIN_AUTH0}/.well-known/openid-configuration'
|
||||
)
|
||||
# session[app.ID_TOKEN_USER] = {'userinfo': {'sub': ''}}
|
||||
"""
|
||||
|
||||
mail = Mail(app)
|
||||
|
||||
|
||||
# METHODS
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
|
||||
return "Hello, World!" + f"{app.errors}\n{app.config.SQLALCHEMY_DATABASE_URI == app.SQLALCHEMY_DATABASE_URI}"
|
||||
|
||||
@app.route('/goodbye')
|
||||
def goodbye():
|
||||
return "Goodbye, cruel World!"
|
||||
|
||||
"""
|
||||
@app.route('/public_html/403.shtml', methods=['GET'])
|
||||
def forbidden():
|
||||
return send_from_directory('/home/partsltd/public_html', '403.shtml')
|
||||
"""
|
||||
|
||||
def application(environ, start_response):
|
||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||
@@ -116,563 +155,94 @@ def application(environ, start_response):
|
||||
# ROUTING
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
return render_template('_page_home.html', model=Model_View_Home(db, get_info_user(), app))
|
||||
|
||||
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_home.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
|
||||
@app.route('/contact', methods=['GET'])
|
||||
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
|
||||
mailItem = Message("PARTS Website Contact Us Message", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
mailItem.body = f"Dear Lord Edward Middleton-Smith,\n\n{msg}\n\nKind regards,\n{name}\n{email}"
|
||||
mail.send(mailItem)
|
||||
return render_template('_page_contact.html', model=Model_View_Contact(db, get_info_user(), app, form))
|
||||
|
||||
@app.route('/services', methods=['GET', 'POST'])
|
||||
@app.route('/public_html/services', methods=['GET', 'POST'])
|
||||
def services():
|
||||
return render_template('_page_services.html', model=Model_View_Home(db, get_info_user(), app))
|
||||
|
||||
|
||||
# Store
|
||||
@app.route('/store', methods=['GET', 'POST'])
|
||||
def store_home():
|
||||
print("store home")
|
||||
try:
|
||||
data = request.json
|
||||
except:
|
||||
data = {}
|
||||
print(f'data={data}')
|
||||
"""
|
||||
form = Form_Contact()
|
||||
model = Model_View_Contact(db, get_info_user(), app, form)
|
||||
html_body = render_template('_page_contact.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
return html_body
|
||||
|
||||
@app.route('/contact', methods=['POST'])
|
||||
def contact_post():
|
||||
try:
|
||||
id_currency = data.id_currency
|
||||
except:
|
||||
id_currency = Model_View_Store.ID_CURRENCY_DEFAULT
|
||||
try:
|
||||
id_region_delivery = data.id_region_delivery
|
||||
except:
|
||||
id_region_delivery = Model_View_Store.ID_REGION_DELIVERY_DEFAULT
|
||||
"""
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
print(f"id_currency = {id_currency}")
|
||||
print(f"id_region_delivery = {id_region_delivery}")
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
# model.get_regions_and_currencies()
|
||||
# model.categories = Model_View_Store_Home.get_many_product_category(db)
|
||||
# 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()}')
|
||||
if request.method == 'GET':
|
||||
return render_template('_page_store_home.html', model = model) # "<html><body><h1>Boobs</h1></html></body>"
|
||||
else: # POST request
|
||||
html_block = render_template('_block_store_home_body.html', model = model)
|
||||
print(f'html_block:\n{html_block}')
|
||||
return jsonify(Success=True, data={'html_block': html_block})
|
||||
|
||||
# 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_View_Store.KEY_BASKET)
|
||||
model = Model_View_Store_Home(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
# 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, is_included_VAT)
|
||||
# model.is_included_VAT = is_included_VAT
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
"""
|
||||
try:
|
||||
form_data[Model_View_Store.KEY_VALUE_DEFAULT]
|
||||
except KeyError:
|
||||
form_data[Model_View_Store.KEY_VALUE_DEFAULT] =
|
||||
"""
|
||||
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, is_included_VAT)
|
||||
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, is_included_VAT)
|
||||
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, is_included_VAT)
|
||||
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}')
|
||||
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, is_included_VAT)
|
||||
form_data = data[Model_View_Store.key_form]
|
||||
print(f'form_data: {form_data}')
|
||||
form = Form_Billing(**form_data)
|
||||
print('form acquired')
|
||||
print(form.__repr__)
|
||||
if form.validate_on_submit():
|
||||
print('valid form')
|
||||
# model = input_JSON_basket(model, data)
|
||||
# if not logged in:
|
||||
data_info = {}
|
||||
try:
|
||||
info_type = data[model.key_info_type]
|
||||
print('importing address information')
|
||||
data_info[model.key_region] = form.region.data
|
||||
print(f'region: {data_info[model.key_region]}')
|
||||
data_info[model.key_name_full] = form.name_full.data
|
||||
print(f'full name: {data_info[model.key_name_full]}')
|
||||
data_info[model.key_phone_number] = form.phone_number.data
|
||||
print(f'phone number: {data_info[model.key_phone_number]}')
|
||||
data_info[model.key_postcode] = form.postcode.data
|
||||
print(f'postcode: {data_info[model.key_postcode]}')
|
||||
data_info[model.key_address1] = form.address_1.data
|
||||
print(f'address line 1: {data_info[model.key_address1]}')
|
||||
data_info[model.key_address2] = form.address_2.data
|
||||
print(f'address line 2: {data_info[model.key_address2]}')
|
||||
data_info[model.key_city] = form.city.data
|
||||
print(f'city: {data_info[model.key_city]}')
|
||||
data_info[model.key_county] = form.county.data
|
||||
print(f'county: {data_info[model.key_county]}')
|
||||
data_info[model.key_info_identical] = form.identical
|
||||
print(f'identical: {data_info[model.key_info_identical]}')
|
||||
except:
|
||||
return jsonify({'status': 'failure', 'Message': 'Bad form data received by controller'})
|
||||
# return jsonify(Success = True, data = { html_block: render_template(), Model_View_Store.key_basket: new_basket })
|
||||
# html_block = render_template('_block_store_basket.html', model = model)
|
||||
# print(f'html_block:\n{html_block}')
|
||||
data = {}
|
||||
data[model.key_info_type] = model.key_info_billing if (info_type == model.key_info_billing) else model.key_info_delivery
|
||||
data[info_type] = data_info
|
||||
return jsonify(Success = True, data = data)
|
||||
return jsonify({'status': 'failure', 'Message': f'Invalid address information\n{form.errors}'})
|
||||
|
||||
|
||||
@app.route('/store/product?permutationId=<permutation_id>regionId=&<region_id>¤cyId=<currency_id>&isIncludedVAT=<is_included_VAT>', methods=['GET']) # <product_id>&
|
||||
def store_product(permutation_id, region_id, currency_id, is_included_VAT):
|
||||
_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, is_included_VAT)
|
||||
print('model reached')
|
||||
# model.id_currency, model.id_region_delivery, model.is_included_VAT = DataStore_Store.get_metadata_basket(request.json)
|
||||
# model.get_many_product_category(product_ids = str(product_id))
|
||||
# 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')
|
||||
id_currency, id_region_delivery, is_included_VAT = DataStore_Store.get_metadata_basket(data)
|
||||
model = Model_View_Store_Checkout(db, get_info_user(), app, id_currency, id_region_delivery, is_included_VAT)
|
||||
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)
|
||||
form = Form_Contact()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
email = form.email.data
|
||||
CC = form.CC.data # not in use
|
||||
name = form.name.data
|
||||
message = form.message.data
|
||||
# send email
|
||||
mailItem = Message("PARTS Website Contact Us Message", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
mailItem.body = f"Dear Lord Edward Middleton-Smith,\n\n{message}\n\nKind regards,\n{name}\n{email}"
|
||||
mail.send(mailItem)
|
||||
return "Submitted."
|
||||
return "Invalid. Failed to submit."
|
||||
# html_body = render_template('_page_contact.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
@app.route('/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}')
|
||||
@app.route('/services', methods=['GET', 'POST'])
|
||||
# @app.route('/public_html/services', methods=['GET', 'POST'])
|
||||
def services():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_services.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
return html_body
|
||||
|
||||
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
|
||||
"""
|
||||
id_region_delivery = form_data[Model_View_Store.KEY_BASKET][Model_View_Base.KEY_ID_REGION_DELIVERY]
|
||||
print(f'id_region_delivery: {id_region_delivery}')
|
||||
return jsonify(Success=True, data={Model_View_Base.KEY_ID_REGION_DELIVERY: 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,
|
||||
)
|
||||
)
|
||||
# snore
|
||||
@app.route('/license', methods=['GET'])
|
||||
def license():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_license.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
@app.route('/accessibility-statement', methods=['GET'])
|
||||
def accessibility_statement():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_accessibility_statement.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
@app.route('/accessibility-report', methods=['GET'])
|
||||
def accessibility_report():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_accessibility_report.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
@app.route('/retention-schedule', methods=['GET'])
|
||||
def retention_schedule():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_retention_schedule.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
@app.route('/privacy-notice', methods=['GET'])
|
||||
def privacy_notice():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_privacy_notice.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
|
||||
def get_info_user():
|
||||
try:
|
||||
@@ -680,48 +250,7 @@ def get_info_user():
|
||||
except:
|
||||
return {'sub': ''}
|
||||
|
||||
"""
|
||||
@app.route('/send-email', methods=['GET'])
|
||||
def send_email():
|
||||
try:
|
||||
msg = Message("Flask Mail test", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
msg.body = "Dear Lord Edward Middleton-Smith,\n\nThis is a test email sent from Flask.\n\nKind regards,\nBot"
|
||||
mail.send(msg)
|
||||
except:
|
||||
return "<html>Error</html>"
|
||||
return "<html>Email sent</html>"
|
||||
"""
|
||||
|
||||
"""
|
||||
@app.route('/test-mysql', methods=['GET'])
|
||||
def test_mysql():
|
||||
model = Model_View_Store(db, get_info_user(), app)
|
||||
|
||||
_m = 'test_mysql'
|
||||
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 "<html>MySQL test</html>"
|
||||
"""
|
||||
|
||||
@app.route('/public_html/403.shtml', methods=['GET'])
|
||||
def error_403():
|
||||
return "<html>Error 403</html>"
|
||||
|
||||
|
||||
# Onload
|
||||
if True or __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
# app.run(debug=True, host="0.0.0.0", port=5000)
|
||||
205
app2.py
205
app2.py
@@ -1,205 +0,0 @@
|
||||
"""
|
||||
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 forms import Form_Contact
|
||||
from models.model_view_base import Model_View_Base
|
||||
from models.model_view_home import Model_View_Home
|
||||
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
|
||||
# external
|
||||
from flask import Flask, render_template, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
|
||||
from flask_cors import CORS
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_mail import Mail, Message
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
import stripe
|
||||
import json
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
import jwt
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
app = Flask(__name__)
|
||||
|
||||
csrf = CSRFProtect(app)
|
||||
CORS(app)
|
||||
# app.register_blueprint(bp_home, url_prefix='')
|
||||
email_recipient = 'edward.middletonsmith@gmail.com' # 'noreply'
|
||||
|
||||
# app.config.from_object(app_config)
|
||||
# app.app_config = app_config
|
||||
app.DEBUG = Config.DEBUG
|
||||
app.TESTING = Config.TESTING
|
||||
app.SECRET_KEY = "007cfbdaaf6d1eb27209720e8a5fc8ba0a334ae0be6fcac132b0a471549cde7c" # Config.SECRET_KEY
|
||||
app.config['SECRET_KEY'] = app.SECRET_KEY
|
||||
app.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.config.SQLALCHEMY_DATABASE_URI = Config.SQLALCHEMY_DATABASE_URI
|
||||
app.config.SQLALCHEMY_TRACK_MODIFICATIONS = Config.SQLALCHEMY_TRACK_MODIFICATIONS
|
||||
app.config.ID_AUTH0_CLIENT = Config.ID_AUTH0_CLIENT
|
||||
app.config.ID_AUTH0_CLIENT_SECRET = Config.ID_AUTH0_CLIENT_SECRET
|
||||
app.config.DOMAIN_AUTH0 = Config.DOMAIN_AUTH0
|
||||
app.config.ID_TOKEN_USER = Config.ID_TOKEN_USER
|
||||
app.MAIL_SERVER = Config.MAIL_SERVER
|
||||
app.MAIL_PORT = Config.MAIL_PORT
|
||||
app.MAIL_USE_TLS = Config.MAIL_USE_TLS
|
||||
app.MAIL_USERNAME = Config.MAIL_USERNAME
|
||||
app.MAIL_PASSWORD = Config.MAIL_PASSWORD
|
||||
app.MAIL_DEFAULT_SENDER = Config.MAIL_DEFAULT_SENDER
|
||||
app.config['MAIL_SERVER'] = Config.MAIL_SERVER
|
||||
app.config['MAIL_PORT'] = Config.MAIL_PORT
|
||||
app.config['MAIL_USE_TLS'] = Config.MAIL_USE_TLS
|
||||
app.config['MAIL_USERNAME'] = Config.MAIL_USERNAME
|
||||
app.config['MAIL_PASSWORD'] = Config.MAIL_PASSWORD
|
||||
app.config['MAIL_DEFAULT_SENDER'] = Config.MAIL_DEFAULT_SENDER
|
||||
|
||||
app.RECAPTCHA_PUBLIC_KEY = Config.RECAPTCHA_PUBLIC_KEY
|
||||
app.config['RECAPTCHA_PUBLIC_KEY'] = Config.RECAPTCHA_PUBLIC_KEY
|
||||
app.RECAPTCHA_PRIVATE_KEY = Config.RECAPTCHA_PRIVATE_KEY
|
||||
app.config['RECAPTCHA_PRIVATE_KEY'] = Config.RECAPTCHA_PRIVATE_KEY
|
||||
|
||||
# db = SQLAlchemy(app)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = app.SQLALCHEMY_DATABASE_URI
|
||||
db = SQLAlchemy()
|
||||
db.init_app(app)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
db.engine.url = app.config.SQLALCHEMY_DATABASE_URI
|
||||
|
||||
"""
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
"auth0",
|
||||
client_id = app.ID_AUTH0_CLIENT,
|
||||
client_secret = app.ID_AUTH0_CLIENT_SECRET, # =env.get("AUTH0_CLIENT_SECRET"),
|
||||
client_kwargs={
|
||||
"scope": "openid profile email",
|
||||
},
|
||||
server_metadata_url=f'https://{app.DOMAIN_AUTH0}/.well-known/openid-configuration'
|
||||
)
|
||||
# session[app.ID_TOKEN_USER] = {'userinfo': {'sub': ''}}
|
||||
"""
|
||||
|
||||
mail = Mail(app)
|
||||
|
||||
# METHODS
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
|
||||
return "Hello, World!" + f"{app.errors}\n{app.config.SQLALCHEMY_DATABASE_URI == app.SQLALCHEMY_DATABASE_URI}"
|
||||
|
||||
@app.route('/goodbye')
|
||||
def goodbye():
|
||||
return "Goodbye, cruel World!"
|
||||
|
||||
"""
|
||||
@app.route('/public_html/403.shtml', methods=['GET'])
|
||||
def forbidden():
|
||||
return send_from_directory('/home/partsltd/public_html', '403.shtml')
|
||||
"""
|
||||
|
||||
def application(environ, start_response):
|
||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||
message = 'It works!\n'
|
||||
version = 'Python %s\n' % sys.version.split()[0]
|
||||
response = '\n'.join([message, version])
|
||||
return [response.encode()]
|
||||
|
||||
|
||||
# ROUTING
|
||||
@app.route('/', methods=['GET'])
|
||||
def home():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_home.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
|
||||
@app.route('/contact', methods=['GET'])
|
||||
def contact():
|
||||
try:
|
||||
form = Form_Contact()
|
||||
model = Model_View_Contact(db, get_info_user(), app, form)
|
||||
html_body = render_template('_page_contact.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
return html_body
|
||||
|
||||
@app.route('/contact', methods=['POST'])
|
||||
def contact_post():
|
||||
try:
|
||||
form = Form_Contact()
|
||||
if form.validate_on_submit():
|
||||
# Handle form submission
|
||||
email = form.email.data
|
||||
CC = form.CC.data # not in use
|
||||
name = form.name.data
|
||||
msg = form.msg.data
|
||||
# send email
|
||||
mailItem = Message("PARTS Website Contact Us Message", recipients=[app.config['MAIL_DEFAULT_SENDER']])
|
||||
mailItem.body = f"Dear Lord Edward Middleton-Smith,\n\n{msg}\n\nKind regards,\n{name}\n{email}"
|
||||
mail.send(mailItem)
|
||||
return "Submitted."
|
||||
return "Invalid. Failed to submit."
|
||||
# html_body = render_template('_page_contact.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
|
||||
@app.route('/services', methods=['GET', 'POST'])
|
||||
# @app.route('/public_html/services', methods=['GET', 'POST'])
|
||||
def services():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_services.html', model = model)
|
||||
except Exception as e:
|
||||
return jsonify(error=str(e)), 403
|
||||
return html_body
|
||||
|
||||
@app.route('/license', methods=['GET'])
|
||||
def license():
|
||||
try:
|
||||
model = Model_View_Home(db, get_info_user(), app)
|
||||
html_body = render_template('_page_license.html', model = model)
|
||||
except Exception as e:
|
||||
return str(e)
|
||||
return html_body
|
||||
|
||||
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)
|
||||
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.
@@ -40,10 +40,10 @@ class Config:
|
||||
MAIL_SERVER = 'smtp.gmail.com'
|
||||
MAIL_PORT = 587
|
||||
MAIL_USE_TLS = True
|
||||
MAIL_USERNAME = 'edward.middletonsmith@gmail.com'
|
||||
MAIL_USERNAME = os.getenv('MAIL_DEFAULT_SENDER')
|
||||
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')
|
||||
MAIL_DEFAULT_SENDER = 'edward.middletonsmith@gmail.com'
|
||||
|
||||
MAIL_DEFAULT_SENDER = os.getenv('MAIL_DEFAULT_SENDER')
|
||||
MAIL_CONTACT_PUBLIC = os.getenv('MAIL_CONTACT_PUBLIC')
|
||||
RECAPTCHA_PUBLIC_KEY = os.getenv('RECAPTCHA_PUBLIC_KEY')
|
||||
RECAPTCHA_PRIVATE_KEY = os.getenv('RECAPTCHA_PRIVATE_KEY')
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
4
forms.py
4
forms.py
@@ -24,7 +24,7 @@ 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')
|
||||
message = TextAreaField('Message')
|
||||
recaptcha = RecaptchaField()
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
@@ -32,7 +32,7 @@ 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')
|
||||
message = TextAreaField('Message')
|
||||
submit = SubmitField('Submit')
|
||||
|
||||
|
||||
|
||||
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.
@@ -57,6 +57,7 @@ class Model_View_Base(ABC):
|
||||
FLAG_SCROLLABLE = 'scrollable'
|
||||
FLAG_SUBMITTED = 'submitted'
|
||||
# flagIsDatePicker = 'is-date-picker'
|
||||
HASH_PAGE_ACCESSIBILITY_STATEMENT = '/accessibility-statement'
|
||||
HASH_PAGE_CONTACT = '/contact'
|
||||
HASH_PAGE_ERROR_NO_PERMISSION = '/error'
|
||||
HASH_PAGE_HOME = '/'
|
||||
@@ -78,6 +79,7 @@ class Model_View_Base(ABC):
|
||||
ID_NAV_STORE_PRODUCT = 'navStoreProduct'
|
||||
ID_OVERLAY_HAMBURGER = 'overlayHamburger'
|
||||
ID_PAGE_BODY = 'pageBody'
|
||||
NAME_COMPANY = 'Precision And Research Technology Systems Limited'
|
||||
URL_HOST = 'https://www.partsltd.co.uk' # 'http://127.0.0.1:5000'
|
||||
URL_GITHUB = 'https://github.com/Teddy-1024'
|
||||
URL_LINKEDIN = 'https://uk.linkedin.com/in/lordteddyms'
|
||||
|
||||
@@ -12,4 +12,4 @@ def application(environ, start_response):
|
||||
response = '\n'.join([message, version])
|
||||
return [response.encode()]
|
||||
|
||||
from app2 import app as application # Import the Flask application from app.py
|
||||
from app import app as application # Import the Flask application from app.py
|
||||
@@ -464,7 +464,7 @@ button, .btn-submit, input[type="submit"] {
|
||||
color: var(--c_purple_dark);
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
height: 27px;
|
||||
height: 18px;
|
||||
}
|
||||
.hamburger > :hover {
|
||||
background-color: var(--c_purple_light);
|
||||
@@ -472,8 +472,14 @@ button, .btn-submit, input[type="submit"] {
|
||||
}
|
||||
.hamburger > * > * {
|
||||
width: 100%;
|
||||
/*
|
||||
margin-top: 4.5px;
|
||||
margin-bottom: 4.5px;
|
||||
*/
|
||||
}
|
||||
.hamburger > .container {
|
||||
padding-top: 4.5px;
|
||||
padding-bottom: 4.5px;
|
||||
}
|
||||
|
||||
li {
|
||||
|
||||
BIN
static/docs/generated-privacy-notice-general-business.docx
Normal file
BIN
static/docs/generated-privacy-notice-general-business.docx
Normal file
Binary file not shown.
BIN
static/docs/generated-privacy-notice-general-business.odt
Normal file
BIN
static/docs/generated-privacy-notice-general-business.odt
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
|
||||
<!doctype HTML><html lang="en"><head><title>public-website-of-precision-and-research-technology-systems-limited-report.html</title><meta charset="utf-8"><style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #3b3b3b;
|
||||
}
|
||||
td:not(:last-child),
|
||||
th {
|
||||
padding: 1em;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
td:not([class]):last-child {
|
||||
padding: 0 1em;
|
||||
}
|
||||
</style></head><body><div><main> <h1>Report</h1> <div><h2>About the Evaluation</h2> <dl><dt>Report Creator </dt><dd>Lord Edward Middleton-Smith </dd><dt>Evaluation Commissioner </dt><dd>Lord Edward Middleton-Smith </dd><dt>Evaluation date </dt><dd>Tue Apr 30 2024</dd></dl> </div> <div><h2>Executive Summary</h2> <div><span>Not provided</span></div> </div> <div><h2>Scope of the Evaluation</h2> <dl><dt>Website name </dt><dd>Public Website of Precision And Research Technology Systems Limited </dd><dt>Scope of the website </dt><dd>'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/ </dd><dt>WCAG Version </dt><dd>2.2 </dd><dt>Conformance target </dt><dd>AA </dd><dt>Accessibility support baseline </dt><dd>Google Chrome with NVDA, FireFox with NVDA, Ecosia mobile browser with TalkBack. </dd><dt>Additional evaluation requirements </dt><dd><span>Not provided</span></dd></dl> </div> <h2>Detailed Audit Results</h2> <h3>Summary</h3> <p>Reported on 55 of 55 WCAG 2.2 AA
|
||||
Success Criteria.</p> <ul><li><span>41</span> <span>Passed</span></li><li><span>0</span> <span>Failed</span></li><li><span>0</span> <span>Cannot tell</span></li><li><span>14</span> <span>Not present</span></li><li><span>0</span> <span>Not checked</span></li></ul> <h3>All Results</h3> <h4>1 Perceivable</h4> <h5 id="guideline-11">1.1 Text Alternatives</h5> <table aria-labelledby="guideline-11"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-111">1.1.1: Non-text Content</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>All user input controls have descriptive names and v2 Google reCaptcha used with text label to identify to user that they must check the box to prove they are not a bot.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-12">1.2 Time-based Media</h5> <table aria-labelledby="guideline-12"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-121">1.2.1: Audio-only and Video-only (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-122">1.2.2: Captions (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-123">1.2.3: Audio Description or Media Alternative (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-124">1.2.4: Captions (Live)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-125">1.2.5: Audio Description (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-13">1.3 Adaptable</h5> <table aria-labelledby="guideline-13"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-131">1.3.1: Info and Relationships</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Elements change in response to zoom and viewport dimensions properly. Aria-label provided for all images.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-132">1.3.2: Meaningful Sequence</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Flow layout keeps associated sections together but allows dynamic structure depending on size of elements relative to screen.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-133">1.3.3: Sensory Characteristics</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Content reads properly as raw text in default order. Aria-label provided for all images and names for all form input components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-134">1.3.4: Orientation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Orientation of content is not locked.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-135">1.3.5: Identify Input Purpose</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Appropriate visible labels used alongside name attributes for form input elements.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-14">1.4 Distinguishable</h5> <table aria-labelledby="guideline-14"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-141">1.4.1: Use of Color</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Colour only used to convey meaning for text hyperlinks, which have alt-text attributes to identify their purpose and presence.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-142">1.4.2: Audio Control</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-143">1.4.3: Contrast (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Webpage text contrast assessed with tool at this website: <a href="https://accessibleweb.com/color-contrast-checker/">https://accessibleweb.com/color-contrast-checker/</a></p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-144">1.4.4: Resize text</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>All webpages can be scaled to up to 500% while maintaining structure and visible components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-145">1.4.5: Images of Text</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-1410">1.4.10: Reflow</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Flex layout forces only vertical scrolling at required viewport dimensions.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1411">1.4.11: Non-text Contrast</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Strong borders used for form input components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1412">1.4.12: Text Spacing</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Content becomes vertically scrollable as necessary.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1413">1.4.13: Content on Hover or Focus</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Hamburger menu button for navigation overlay used. The button remains stationary with no elements above it, throughout use of navigation.</p>
|
||||
</td> </tr></tbody> </table><h4>2 Operable</h4> <h5 id="guideline-21">2.1 Keyboard Accessible</h5> <table aria-labelledby="guideline-21"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-211">2.1.1: Keyboard</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-212">2.1.2: No Keyboard Trap</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-214">2.1.4: Character Key Shortcuts</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed, and can be set by user's device settings.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-22">2.2 Enough Time</h5> <table aria-labelledby="guideline-22"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-221">2.2.1: Timing Adjustable</th> <td> <p><span>Result:</span> Not present</p> </td> <td> <p>Observations:</p> <p>No session time limits imposed on user.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-222">2.2.2: Pause, Stop, Hide</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-23">2.3 Seizures and Physical Reactions</h5> <table aria-labelledby="guideline-23"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-231">2.3.1: Three Flashes or Below Threshold</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-24">2.4 Navigable</h5> <table aria-labelledby="guideline-24"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-241">2.4.1: Bypass Blocks</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-242">2.4.2: Page Titled</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Descriptive titles used on all webpages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-243">2.4.3: Focus Order</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Keyboard tab indices set for logical navigation around pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-244">2.4.4: Link Purpose (In Context)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Descriptive aria-label provided for all text hyperlinks.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-245">2.4.5: Multiple Ways</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Navigation on all webpages and company logo links to home page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-246">2.4.6: Headings and Labels</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-247">2.4.7: Focus Visible</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-2411">2.4.11: Focus Not Obscured (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-25">2.5 Input Modalities</h5> <table aria-labelledby="guideline-25"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-251">2.5.1: Pointer Gestures</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-252">2.5.2: Pointer Cancellation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Up event used to trigger events across website.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-253">2.5.3: Label in Name</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Input label for attributes used to associate with input elements.
|
||||
Aria-label attributes used for text hyperlinks.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-254">2.5.4: Motion Actuation</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-257">2.5.7: Dragging Movements</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-258">2.5.8: Target Size (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Minimum control dimension is 27 CSS pixels.</p>
|
||||
</td> </tr></tbody> </table><h4>3 Understandable</h4> <h5 id="guideline-31">3.1 Readable</h5> <table aria-labelledby="guideline-31"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-311">3.1.1: Language of Page</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>English - Great Britain on all pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-312">3.1.2: Language of Parts</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No language changes across website.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-32">3.2 Predictable</h5> <table aria-labelledby="guideline-32"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-321">3.2.1: On Focus</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-322">3.2.2: On Input</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-323">3.2.3: Consistent Navigation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Navigation component and mechanisms shared across all pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-324">3.2.4: Consistent Identification</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Classes and CSS styles used to group collections of elements by purpose and functionality.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-326">3.2.6: Consistent Help</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Contact us button provided in a consistent format.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-33">3.3 Input Assistance</h5> <table aria-labelledby="guideline-33"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-331">3.3.1: Error Identification</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Each input element has an associated error display label which are triggered when input validation is not met.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-332">3.3.2: Labels or Instructions</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Each user input has a descriptive label.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-333">3.3.3: Error Suggestion</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Text description of incomplete required fields is provided. Description of what caused error is issued when it involves simple regular expression validation.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-334">3.3.4: Error Prevention (Legal, Financial, Data)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-337">3.3.7: Redundant Entry</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No redundant data entry required. User's browser cache stores form data for rapid re-entry on crash or any other occasion.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-338">3.3.8: Accessible Authentication (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Alternative sign in methods provided, including recovery by code by phone and email.</p>
|
||||
</td> </tr></tbody> </table><h4>4 Robust</h4> <h5 id="guideline-41">4.1 Compatible</h5> <table aria-labelledby="guideline-41"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-412">4.1.2: Name, Role, Value</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Aria-label attribute maintained for all images and text hyperlinks. Label for attribute used to associate input elements with descriptive labels.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-413">4.1.3: Status Messages</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Success feedback provided on form submission, with descriptive errors on failure.</p>
|
||||
</td> </tr></tbody> </table> <h2>Sample of Audited Web Pages</h2> <ol><li><span>Home</span> - <span>https://www.partsltd.co.uk/</span> </li><li><span>Contact us</span> - <span>https://www.partsltd.co.uk/contact</span> </li><li><span>Services</span> - <span>https://www.partsltd.co.uk/services</span> </li><li><span></span> - <span></span> </li></ol> <h2>Web Technology</h2> <p>HTML,CSS,JavaScript,python Flask</p> <h2>Recording of Evaluation Specifics</h2> <p>Not provided</p> </main></div></body></html>
|
||||
5
static/js/accessibility_statement.js
Normal file
5
static/js/accessibility_statement.js
Normal file
@@ -0,0 +1,5 @@
|
||||
var _loading = true;
|
||||
|
||||
function hookupPageAccessibilityStatement() {
|
||||
_loading = false;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
var _loading = true;
|
||||
|
||||
function hookupPageHome() {
|
||||
function hookupPageLicense() {
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
60
templates/_page_accessibility_report.html
Normal file
60
templates/_page_accessibility_report.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!doctype HTML><html lang="en"><head><title>public-website-of-precision-and-research-technology-systems-limited-report.html</title><meta charset="utf-8"><style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #3b3b3b;
|
||||
}
|
||||
td:not(:last-child),
|
||||
th {
|
||||
padding: 1em;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
td:not([class]):last-child {
|
||||
padding: 0 1em;
|
||||
}
|
||||
</style></head><body><div><main> <h1>Report</h1> <div><h2>About the Evaluation</h2> <dl><dt>Report Creator </dt><dd>Lord Edward Middleton-Smith </dd><dt>Evaluation Commissioner </dt><dd>Lord Edward Middleton-Smith </dd><dt>Evaluation date </dt><dd>Tue Apr 30 2024</dd></dl> </div> <div><h2>Executive Summary</h2> <div><span>Not provided</span></div> </div> <div><h2>Scope of the Evaluation</h2> <dl><dt>Website name </dt><dd>Public Website of Precision And Research Technology Systems Limited </dd><dt>Scope of the website </dt><dd>'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/ </dd><dt>WCAG Version </dt><dd>2.2 </dd><dt>Conformance target </dt><dd>AA </dd><dt>Accessibility support baseline </dt><dd>Google Chrome with NVDA, FireFox with NVDA, Ecosia mobile browser with TalkBack. </dd><dt>Additional evaluation requirements </dt><dd><span>Not provided</span></dd></dl> </div> <h2>Detailed Audit Results</h2> <h3>Summary</h3> <p>Reported on 55 of 55 WCAG 2.2 AA
|
||||
Success Criteria.</p> <ul><li><span>41</span> <span>Passed</span></li><li><span>0</span> <span>Failed</span></li><li><span>0</span> <span>Cannot tell</span></li><li><span>14</span> <span>Not present</span></li><li><span>0</span> <span>Not checked</span></li></ul> <h3>All Results</h3> <h4>1 Perceivable</h4> <h5 id="guideline-11">1.1 Text Alternatives</h5> <table aria-labelledby="guideline-11"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-111">1.1.1: Non-text Content</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>All user input controls have descriptive names and v2 Google reCaptcha used with text label to identify to user that they must check the box to prove they are not a bot.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-12">1.2 Time-based Media</h5> <table aria-labelledby="guideline-12"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-121">1.2.1: Audio-only and Video-only (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-122">1.2.2: Captions (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-123">1.2.3: Audio Description or Media Alternative (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-124">1.2.4: Captions (Live)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-125">1.2.5: Audio Description (Prerecorded)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-13">1.3 Adaptable</h5> <table aria-labelledby="guideline-13"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-131">1.3.1: Info and Relationships</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Elements change in response to zoom and viewport dimensions properly. Aria-label provided for all images.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-132">1.3.2: Meaningful Sequence</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Flow layout keeps associated sections together but allows dynamic structure depending on size of elements relative to screen.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-133">1.3.3: Sensory Characteristics</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Content reads properly as raw text in default order. Aria-label provided for all images and names for all form input components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-134">1.3.4: Orientation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Orientation of content is not locked.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-135">1.3.5: Identify Input Purpose</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Appropriate visible labels used alongside name attributes for form input elements.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-14">1.4 Distinguishable</h5> <table aria-labelledby="guideline-14"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-141">1.4.1: Use of Color</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Colour only used to convey meaning for text hyperlinks, which have alt-text attributes to identify their purpose and presence.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-142">1.4.2: Audio Control</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-143">1.4.3: Contrast (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Webpage text contrast assessed with tool at this website: <a href="https://accessibleweb.com/color-contrast-checker/">https://accessibleweb.com/color-contrast-checker/</a></p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-144">1.4.4: Resize text</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>All webpages can be scaled to up to 500% while maintaining structure and visible components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-145">1.4.5: Images of Text</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-1410">1.4.10: Reflow</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Flex layout forces only vertical scrolling at required viewport dimensions.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1411">1.4.11: Non-text Contrast</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Strong borders used for form input components.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1412">1.4.12: Text Spacing</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Content becomes vertically scrollable as necessary.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-1413">1.4.13: Content on Hover or Focus</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Hamburger menu button for navigation overlay used. The button remains stationary with no elements above it, throughout use of navigation.</p>
|
||||
</td> </tr></tbody> </table><h4>2 Operable</h4> <h5 id="guideline-21">2.1 Keyboard Accessible</h5> <table aria-labelledby="guideline-21"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-211">2.1.1: Keyboard</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-212">2.1.2: No Keyboard Trap</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-214">2.1.4: Character Key Shortcuts</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed, and can be set by user's device settings.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-22">2.2 Enough Time</h5> <table aria-labelledby="guideline-22"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-221">2.2.1: Timing Adjustable</th> <td> <p><span>Result:</span> Not present</p> </td> <td> <p>Observations:</p> <p>No session time limits imposed on user.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-222">2.2.2: Pause, Stop, Hide</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-23">2.3 Seizures and Physical Reactions</h5> <table aria-labelledby="guideline-23"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-231">2.3.1: Three Flashes or Below Threshold</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-24">2.4 Navigable</h5> <table aria-labelledby="guideline-24"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-241">2.4.1: Bypass Blocks</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-242">2.4.2: Page Titled</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Descriptive titles used on all webpages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-243">2.4.3: Focus Order</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Keyboard tab indices set for logical navigation around pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-244">2.4.4: Link Purpose (In Context)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Descriptive aria-label provided for all text hyperlinks.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-245">2.4.5: Multiple Ways</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Navigation on all webpages and company logo links to home page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-246">2.4.6: Headings and Labels</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-247">2.4.7: Focus Visible</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-2411">2.4.11: Focus Not Obscured (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> </td> </tr></tbody> </table><h5 id="guideline-25">2.5 Input Modalities</h5> <table aria-labelledby="guideline-25"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-251">2.5.1: Pointer Gestures</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-252">2.5.2: Pointer Cancellation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Up event used to trigger events across website.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-253">2.5.3: Label in Name</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Input label for attributes used to associate with input elements.
|
||||
Aria-label attributes used for text hyperlinks.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-254">2.5.4: Motion Actuation</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-257">2.5.7: Dragging Movements</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-258">2.5.8: Target Size (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Minimum control dimension is 27 CSS pixels.</p>
|
||||
</td> </tr></tbody> </table><h4>3 Understandable</h4> <h5 id="guideline-31">3.1 Readable</h5> <table aria-labelledby="guideline-31"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-311">3.1.1: Language of Page</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>English - Great Britain on all pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-312">3.1.2: Language of Parts</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No language changes across website.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-32">3.2 Predictable</h5> <table aria-labelledby="guideline-32"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-321">3.2.1: On Focus</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-322">3.2.2: On Input</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-323">3.2.3: Consistent Navigation</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Navigation component and mechanisms shared across all pages.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-324">3.2.4: Consistent Identification</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Classes and CSS styles used to group collections of elements by purpose and functionality.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-326">3.2.6: Consistent Help</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Contact us button provided in a consistent format.</p>
|
||||
</td> </tr></tbody> </table><h5 id="guideline-33">3.3 Input Assistance</h5> <table aria-labelledby="guideline-33"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-331">3.3.1: Error Identification</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Each input element has an associated error display label which are triggered when input validation is not met.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-332">3.3.2: Labels or Instructions</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Each user input has a descriptive label.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-333">3.3.3: Error Suggestion</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Text description of incomplete required fields is provided. Description of what caused error is issued when it involves simple regular expression validation.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-334">3.3.4: Error Prevention (Legal, Financial, Data)</th> <td> <p><span>Result:</span> Not present</p> </td> <td> </td> </tr><tr><th scope="row" id="criterion-337">3.3.7: Redundant Entry</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>No redundant data entry required. User's browser cache stores form data for rapid re-entry on crash or any other occasion.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-338">3.3.8: Accessible Authentication (Minimum)</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Alternative sign in methods provided, including recovery by code by phone and email.</p>
|
||||
</td> </tr></tbody> </table><h4>4 Robust</h4> <h5 id="guideline-41">4.1 Compatible</h5> <table aria-labelledby="guideline-41"><tbody><tr><th scope="col">Success Criterion</th> <th scope="col">Result</th> <th scope="col">Observations</th> </tr> <tr><th scope="row" id="criterion-412">4.1.2: Name, Role, Value</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Aria-label attribute maintained for all images and text hyperlinks. Label for attribute used to associate input elements with descriptive labels.</p>
|
||||
</td> </tr><tr><th scope="row" id="criterion-413">4.1.3: Status Messages</th> <td> <p><span>Result:</span> Passed</p> </td> <td> <p>Observations:</p> <p>Success feedback provided on form submission, with descriptive errors on failure.</p>
|
||||
</td> </tr></tbody> </table> <h2>Sample of Audited Web Pages</h2> <ol><li><span>Home</span> - <span>https://www.partsltd.co.uk/</span> </li><li><span>Contact us</span> - <span>https://www.partsltd.co.uk/contact</span> </li><li><span>Services</span> - <span>https://www.partsltd.co.uk/services</span> </li><li><span></span> - <span></span> </li></ol> <h2>Web Technology</h2> <p>HTML,CSS,JavaScript,python Flask</p> <h2>Recording of Evaluation Specifics</h2> <p>Not provided</p> </main></div></body></html>
|
||||
211
templates/_page_accessibility_statement.html
Normal file
211
templates/_page_accessibility_statement.html
Normal file
@@ -0,0 +1,211 @@
|
||||
{% extends 'layout.html' %}
|
||||
|
||||
{% block title %}{{ model.title }}{% endblock %}
|
||||
|
||||
{% block page_body %}
|
||||
<!-- Include Stylesheet -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/services.css') }}">
|
||||
|
||||
<!-- HTML content -->
|
||||
<div class="{{ model.FLAG_CARD }} {{ model.FLAG_COLUMN }}">
|
||||
<h4>Accessibility statement for {{ model.NAME_COMPANY }}</h4>
|
||||
<!--
|
||||
[Note: you can have a single accessibility statement that covers multiple domains, or a separate statement for each domain or subdomain. As long as the user can access relevant accessibility information easily from any page on your website.
|
||||
|
||||
For mobile apps also include the application’s version information and publication date.
|
||||
|
||||
This wording is legally required and must not be changed.]
|
||||
-->
|
||||
<p>This accessibility statement applies to the public website of {{ model.NAME_COMPANY }}.</p>
|
||||
<!--
|
||||
[Note: use this section to make a brief, general statement about what the website allows disabled users to do. Base it on the evaluation covered in detail in the ‘Technical information about this website’s accessibility’ section.
|
||||
|
||||
It must be specific to your own website. If you’re not confident that something is accurate, leave it out. If you’re not confident enough to say anything specific here, leave this section out completely.]
|
||||
-->
|
||||
<p>This website is run by {{ model.NAME_COMPANY }}. We want as many people as possible to be able to use this website. For example, that means you should be able to:
|
||||
<ul>
|
||||
<li>zoom in up to 400% without the text spilling off the screen</li>
|
||||
<li>navigate most of the website using just a keyboard or speech recognition software</li>
|
||||
<li>listen to most of the website using a screen reader (including the most recent versions of NVDA and TalkBack)</li>
|
||||
<li>We've also made the website text as simple as possible to understand.</li>
|
||||
|
||||
<a href="https://mcmw.abilitynet.org.uk/">AbilityNet</a> has advice on making your device easier to use if you have a disability.
|
||||
</p>
|
||||
|
||||
<h5>How accessible this website is</h5>
|
||||
<!--
|
||||
[Note: use this section to provide a summary of accessibility issues that a disabled user can act on - for example, avoid a particular section of the website, or request an alternative version rather than waste time trying to make it work with their assistive technology. Try to list in order of most impact to least impact.]
|
||||
-->
|
||||
<p>We know some parts of this website are not fully accessible:
|
||||
<ul>
|
||||
<li>you cannot modify the line height or spacing of text</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h5>Feedback and contact information</h5>
|
||||
<p>If you find any problems not listed on this page or think we’re not meeting accessibility requirements, contact: {{ model.app.MAIL_CONTACT_PUBLIC }} or complete our Contact Us form.
|
||||
|
||||
If you need information on this website in a different format like accessible PDF, large print, easy read, audio recording or braille:
|
||||
{% set block_id = 'button_get_in_touch' %}
|
||||
{% include '_shared.html' %}
|
||||
We’ll consider your request and get back to you in 7 days.
|
||||
|
||||
<!-- If you cannot view the map on our ‘contact us’ page, email us for directions. -->
|
||||
</p>
|
||||
|
||||
<h5>Enforcement procedure</h5>
|
||||
<p>The Equality and Human Rights Commission (EHRC) is responsible for enforcing the Public Sector Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018 (the ‘accessibility regulations’). If you’re not happy with how we respond to your complaint, contact the <a href="https://www.equalityadvisoryservice.com/">Equality Advisory and Support Service (EASS).</a>
|
||||
</p>
|
||||
<!--
|
||||
[Note: if your organisation is based in Northern Ireland, refer users who want to complain to the Equality Commission for Northern Ireland (ECNI) instead of the EASS and EHRC.
|
||||
|
||||
You must link to either the EASS or ECNI websites.]
|
||||
-->
|
||||
|
||||
|
||||
<h4>Technical information about this website’s accessibility</h4>
|
||||
<!--
|
||||
[Note: this wording is legally required and must not be changed.]
|
||||
-->
|
||||
<p>{{ model.NAME_COMPANY }} is committed to making its website accessible, in accordance with the Public Sector Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018.
|
||||
</p>
|
||||
|
||||
<h5>Compliance status</h5>
|
||||
<p>The website has been tested against the Web Content Accessibility Guidelines (WCAG) 2.2 AA standard.
|
||||
</p>
|
||||
<!--
|
||||
[Note: say that the website is fully compliant if the website meets the Web Content Accessibility Guidelines (WCAG) [2.1 or 2.2] AA standard in full.
|
||||
|
||||
Say that it’s partially compliant if it meets most requirements of the WCAG [2.1 or 2.2] AA standard.
|
||||
|
||||
If it does not meet most requirements of the WCAG [2.1 or 2.2] AA standard, say that it’s not compliant.
|
||||
|
||||
If your website is either partially compliant or not compliant with the WCAG [2.1 or 2.2] AA standard, you’ll need to explain why. This will be due to one or both of the following:
|
||||
<ul>
|
||||
<li>non-compliances - this means the content in question is in scope of the regulations, but there’s an accessibility problem with it</li>
|
||||
<li>an exemption - this means the inaccessible content is out of scope of the regulations, or it’d be a disproportionate burden for you to make it accessible</li>
|
||||
</ul>
|
||||
There’s a legally required way of expressing the compliance status of your website, so do not change it. Choose one of the options below, for example (a), (b) or (c), and delete those not applicable.
|
||||
-->
|
||||
<p><!-- [Select (a) only if all requirements of the technical specification are fully met without exceptions for WCAG 2.1 or WCAG 2.2.] -->
|
||||
|
||||
(a) This website is fully compliant with the <!--[Web Content Accessibility Guidelines version 2.1 AA standard or -->Web Content Accessibility Guidelines version 2.2 AA standard.
|
||||
<!--
|
||||
[Select (b) if most requirements of the technical specification are met, but with some exceptions. This means not yet fully compliant and that the necessary measures are to be taken in order to reach full compliance.]
|
||||
|
||||
(b) This website is partially compliant with the [Web Content Accessibility Guidelines version 2.1 AA standard or Web Content Accessibility Guidelines version 2.2 AA standard], due to [insert one of the following: ‘the non-compliances’, ‘the exemptions’ or ‘the non-compliances and exemptions’] listed below.
|
||||
|
||||
[Select (c) if most requirements of the technical specification are not met.]
|
||||
|
||||
(c) This website is not compliant with the [Web Content Accessibility Guidelines version 2.1 AA standard or Web Content Accessibility Guidelines version 2.2 AA standard]. The [insert one of the following: ‘non-compliances’, ‘exemptions’ or ‘non-compliances and exemptions’] are listed below.
|
||||
-->
|
||||
</p>
|
||||
|
||||
<!--
|
||||
<h4>Non-accessible content</h4>
|
||||
<!--
|
||||
[Note: if the website is fully compliant with the standard, you can leave the ‘Non-accessible content’ section out.
|
||||
|
||||
Otherwise, do not change the ‘Non-accessible content’ heading or the ‘The content listed below is non-accessible for the following reasons’ sentence - they’re legally required.
|
||||
|
||||
Do not change the ‘Non-compliance with the accessibility regulations’, ‘Disproportionate burden’ and ‘Content that’s not within the scope of the accessibility regulations’ subheadings: they’re also legally required.
|
||||
|
||||
But if you need to list a lot of problems, you can break these subsections up with further subheadings - for example, ‘Navigation and accessing information’ or ‘Interactive tools and transactions’.]
|
||||
--
|
||||
<p>The content listed below is non-accessible for the following reasons.
|
||||
</p>
|
||||
|
||||
<h5>Non-compliance with the accessibility regulations</h5>
|
||||
<!--
|
||||
[Note: In this subsection, list:
|
||||
<ul>
|
||||
<li>accessibility problems</li>
|
||||
<li>which of the WCAG success criteria the problem fails on</li>
|
||||
<li>when you plan to fix the problem</li>
|
||||
</ul>
|
||||
Do not include any problems where you’re claiming disproportionate burden, or where the problem is outside the scope of the accessibility regulations (those should go in the subsections below).
|
||||
|
||||
These are examples only. Your statement must be specific to your website.]
|
||||
--
|
||||
<p>Some images do not have a text alternative, so people using a screen reader cannot access the information. This fails WCAG 2.2 success criterion 1.1.1 (non-text content).
|
||||
|
||||
We plan to add text alternatives for all images by September 2022. When we publish new content we’ll make sure our use of images meets accessibility standards.
|
||||
</p>
|
||||
|
||||
<h5>Disproportionate burden</h5>
|
||||
<!--
|
||||
[Note: in this subsection list accessibility problems you’re claiming would be a disproportionate burden to fix.
|
||||
|
||||
You must carry out an assessment before claiming disproportionate burden.
|
||||
|
||||
Bear in mind that something which is a disproportionate burden now will not necessarily be a disproportionate burden forever. If the circumstances change, your ability to claim disproportionate burden may change too.
|
||||
|
||||
These are examples only. Your statement should be specific to your website.]
|
||||
--
|
||||
<p><strong>Navigation and accessing information</strong>
|
||||
There’s no way to skip the repeated content in the page header (for example, a ‘skip to main content’ option).
|
||||
|
||||
It’s not always possible to change the device orientation from horizontal to vertical without making it more difficult to view the content.
|
||||
|
||||
It’s not possible for users to change text size without some of the content overlapping.
|
||||
|
||||
We’ve assessed the cost of fixing the issues with navigation and accessing information. We believe that doing so now would be a disproportionate burden within the meaning of the accessibility regulations. We will make another assessment when the supplier contract is up for renewal, likely to be in [rough timing].
|
||||
</p>
|
||||
|
||||
<h5>Content that’s not within the scope of the accessibility regulations</h5>
|
||||
<!--
|
||||
[Note: in this subsection list accessibility problems that fall outside the scope of the accessibility regulations.]
|
||||
--
|
||||
<p><strong>PDFs and other documents</strong>
|
||||
The accessibility regulations do not require us to fix PDFs or other documents published before 23 September 2018 if they’re not essential to providing our services. For example, we do not plan to fix [example of non-essential document].
|
||||
|
||||
Any new PDFs or Word documents we publish will meet accessibility standards.
|
||||
|
||||
<strong>Live video</strong>
|
||||
We do not plan to add captions to live video streams because live video is exempt from meeting the accessibility regulations.
|
||||
</p>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<h4>What we’re doing to improve accessibility</h4>
|
||||
<!--
|
||||
[Note: publishing an accessibility roadmap is optional. It’s a good idea to publish one if you want to be specific about the order you’re planning to tackle accessibility issues, and there’s no space to do so in the accessibility statement itself.]
|
||||
--
|
||||
<p>Our accessibility roadmap [add link to roadmap] shows how and when we plan to improve accessibility on this website.
|
||||
</p>
|
||||
-->
|
||||
|
||||
<h4>Preparation of this accessibility statement</h4>
|
||||
<!--
|
||||
[Note: the wording about when the statement was prepared and reviewed is required.
|
||||
|
||||
It is recommended that an audit be carried out following a substantial revision to your website. The statement must also be updated.
|
||||
|
||||
The statement must be reviewed at least once a year, even if there have not been significant changes to the website. Include the date of the last review.]
|
||||
-->
|
||||
<p>This statement was prepared on 30/04/2024<!--[date when it was first published]-->. It was last reviewed on 30/04/2024<!--[date when it was last reviewed]-->.
|
||||
|
||||
This website was last tested on 30/04/2024 against the WCAG 2.2 AA standard.
|
||||
</p>
|
||||
<!--
|
||||
[Note: describe how you tested the website to write this statement - such as a self-assessment done by the website team or an assessment carried out by a third party.]
|
||||
-->
|
||||
<p>The test was carried out by <!--[add name of organisation that carried out test, or indicate that you did your own testing]-->{{ model.NAME_COMPANY }}. All pages were tested using automated testing tools by our website team. A further audit of the website was carried out to the WCAG 2.2 AA standard.</p>
|
||||
<!--
|
||||
[Note: publishing the test report is optional but doing so may allow you to make your accessibility statement shorter and more focused.]
|
||||
https://www.w3.org/WAI/eval/report-tool/#/
|
||||
-->
|
||||
<p>You can read the full accessibility test report {{ url_for('accessibility_report') }}.</p>
|
||||
</div>
|
||||
|
||||
<!-- Include JavaScript -->
|
||||
<script src="{{ url_for('static', filename='js/accessibility_statement.js') }}"></script>
|
||||
|
||||
<script>
|
||||
var hashPageCurrent = "{{ model.HASH_PAGE_ACCESSIBILITY_STATEMENT }}";
|
||||
|
||||
$(document).ready(function() {
|
||||
hookupPageAccessibilityStatement();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -55,8 +55,8 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="container-input">
|
||||
{{ model.form.msg.label }}
|
||||
{{ model.form.msg(rows=4, cols=80) }}
|
||||
{{ model.form.message.label }}
|
||||
{{ model.form.message(rows=4, cols=80) }}
|
||||
{% for error in model.form.name.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}
|
||||
@@ -75,7 +75,7 @@
|
||||
<h3 class="label-title" style="padding-bottom: 0;">Where to find us</h3>
|
||||
</div>
|
||||
<div class="{{ model.FLAG_CONTAINER }} {{ model.FLAG_ROW }}">
|
||||
<h4>edward.middletonsmith@gmail.com</h4>
|
||||
<h4>{{ model.app.MAIL_CONTACT_PUBLIC }}</h4>
|
||||
</div>
|
||||
<div class="{{ model.FLAG_CONTAINER }} {{ model.FLAG_ROW }}" style="padding-top: 0; width: fit-content;">
|
||||
<a href="{{ model.url_LinkedIn }}" class="container-icon-label"><img class="img-icon" src="{{ url_for('static', filename='images/Logo_LinkedIn.png') }}" alt="LinkedIn"></img><!--<h4>LinkedIn</h4>--></a>
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<!-- HTML content -->
|
||||
<div>
|
||||
<p style="font-size: 24px; color: white;">Your software engineering solution</p>
|
||||
<button class="button-contact">Get in touch</button>
|
||||
{% set block_id = 'button_get_in_touch' %}
|
||||
{% include '_shared.html' %}
|
||||
</div>
|
||||
|
||||
<!-- Include JavaScript -->
|
||||
|
||||
92
templates/_page_privacy_notice.html
Normal file
92
templates/_page_privacy_notice.html
Normal file
@@ -0,0 +1,92 @@
|
||||
{% extends 'layout.html' %}
|
||||
|
||||
{% block title %}{{ model.title }}{% endblock %}
|
||||
|
||||
{% block page_body %}
|
||||
<!-- Include Stylesheet -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/services.css') }}">
|
||||
|
||||
<!-- HTML content -->
|
||||
<div class="{{ model.FLAG_CARD }} {{ model.FLAG_COLUMN }}">
|
||||
<h4>Generated privacy notice - general business</h4>
|
||||
<h5>Precision and Research Technology Systems Ltd customer privacy notice</h5>
|
||||
<p>This privacy notice tells you what to expect us to do with your personal information.</p>
|
||||
<h5>Our contact details</h5>
|
||||
<p>Email</p>
|
||||
<p>teddy@partsltd.co.uk</p>
|
||||
<h5>What information we collect, use, and why</h5>
|
||||
<p>We collect or use the following information to provide services and goods, including delivery:</p>
|
||||
<ul>
|
||||
<li>Names and contact details</li>
|
||||
</ul>
|
||||
|
||||
<h5>Lawful bases</h5>
|
||||
<p>Our lawful bases for collecting or using personal information to provide services and goods are:</p>
|
||||
<ul>
|
||||
<li>Legitimate interest:</li>
|
||||
<ul>
|
||||
<li>
|
||||
I collect the user's name and email address alongside a message they send on our 'Contact Us' form in order to respond to their request. Alternatively, the user has the option to contact us directly by email.
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<h5>Where we get personal information from</h5>
|
||||
<ul>
|
||||
<li>People directly</li>
|
||||
</ul>
|
||||
|
||||
<h5>How long we keep information</h5>
|
||||
<p>For information on how long we keep personal information, see our retention schedule at <a href="https://www.partsltd.co.uk/retention-schedule" alt="retention schedule" aria-label="retention schedule">https://www.partsltd.co.uk/retention-schedule</a></p>
|
||||
|
||||
<h5>Who we share information with</h5>
|
||||
<p>Data processors</p>
|
||||
<p><strong>Zume</strong></p>
|
||||
<p>This data processor does the following activities for us: They host our web and email server, including emails with personal data in body and database storage for database that holds user data.</p>
|
||||
|
||||
<h5>Sharing information outside the UK</h5>
|
||||
<p>Where necessary, we may transfer personal information outside of the UK. When doing so, we comply with the UK GDPR, making sure appropriate safeguards are in place. Please contact us for more information.</p>
|
||||
<p>Where necessary, our data processors may share personal information outside of the UK. When doing so, they comply with the UK GDPR, making sure appropriate safeguards are in place. Please contact us for more information.</p>
|
||||
|
||||
<h5>Your data protection rights</h5>
|
||||
<p>Under data protection law, you have rights including:</p>
|
||||
<ul>
|
||||
<li>Your right of access - You have the right to ask us for copies of your personal data.</li>
|
||||
<li>Your right to rectification - You have the right to ask us to rectify personal data you think is inaccurate. You also have the right to ask us to complete information you think is incomplete.</li>
|
||||
<li>Your right to erasure - You have the right to ask us to erase your personal data in certain circumstances.</li>
|
||||
<li>Your right to restriction of processing - You have the right to ask us to restrict the processing of your personal data in certain circumstances.</li>
|
||||
<li>Your right to object to processing - You have the right to object to the processing of your personal data in certain circumstances.</li>
|
||||
<li>Your right to data portability - You have the right to ask that we transfer the personal data you gave us to another organisation, or to you, in certain circumstances.</li>
|
||||
<li>Your right to withdraw consent – When we use consent as our lawful basis you have the right to withdraw your consent.</li>
|
||||
</ul>
|
||||
<p>You don’t usually need to pay a fee to exercise your rights. If you make a request, we have one calendar month to respond to you.</p>
|
||||
<p>To make a data protection rights request, please contact us using the contact details at the top of this privacy notice.</p>
|
||||
|
||||
<h5>How to complain</h5>
|
||||
<p>If you have any concerns about our use of your personal data, you can make a complaint to us using the contact details at the top of this privacy notice.</p>
|
||||
<p>If you remain unhappy with how we’ve used your data after raising a complaint with us, you can also complain to the ICO.</p>
|
||||
|
||||
<h5>The ICO’s address:</h5>
|
||||
<p>Information Commissioner’s Office</p>
|
||||
<p>Wycliffe House</p>
|
||||
<p>Water Lane</p>
|
||||
<p>Wilmslow</p>
|
||||
<p>Cheshire</p>
|
||||
<p>SK9 5AF</p>
|
||||
<p>Helpline number: 0303 123 1113</p>
|
||||
<p>Website: <a href="https://www.ico.org.uk/make-a-complaint" alt="ICO make a complaint" aria-label="ICO make a complaint">https://www.ico.org.uk/make-a-complaint</a></p>
|
||||
<p>Last updated</p>
|
||||
<p>1 May 2024</p>
|
||||
</div>
|
||||
|
||||
<!-- Include JavaScript -->
|
||||
<script src="{{ url_for('static', filename='js/accessibility_statement.js') }}"></script>
|
||||
|
||||
<script>
|
||||
var hashPageCurrent = "{{ model.HASH_PAGE_ACCESSIBILITY_STATEMENT }}";
|
||||
|
||||
$(document).ready(function() {
|
||||
hookupPageAccessibilityStatement();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
1959
templates/_page_retention_schedule.html
Normal file
1959
templates/_page_retention_schedule.html
Normal file
File diff suppressed because it is too large
Load Diff
4
templates/_shared.html
Normal file
4
templates/_shared.html
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
{% if block_id == 'button_get_in_touch' %}
|
||||
<button class="button-contact" alt="Get in touch" aria-label="Get in touch">Get in touch</button>
|
||||
{% endif %}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8"/>
|
||||
@@ -61,13 +61,13 @@
|
||||
|
||||
<div class="topnav">
|
||||
<div class="{{ model.FLAG_CONTAINER }}" style="width: 18vw; min-width: 18vw; max-width: 20vw;">
|
||||
<img class="header-logo" src="{{ url_for('static', filename='images/Logo.png') }}" alt="PARTS logo" tabindex="0">
|
||||
<img class="header-logo" src="{{ url_for('static', filename='images/Logo.png') }}" alt="{{ model.NAME_COMPANY }} logo" aria-label="{{ model.NAME_COMPANY }} logo" tabindex="0">
|
||||
</div>
|
||||
<div class="{{ model.FLAG_CONTAINER }}" style="width: 75vw; min-width: 65vw; max-width: 80vw;">
|
||||
<h4 class="company-name">Precision And Research Technology Systems</h4>
|
||||
</div>
|
||||
<div class="{{ model.FLAG_CONTAINER }}" style="width: 7vw; min-width: 7vw; max-width: 15vw; justify-content: flex-end; "> <!-- padding-left: 25%; -->
|
||||
<button id="{{ model.ID_BUTTON_HAMBURGER }}" tabindex="1">=</button>
|
||||
<button id="{{ model.ID_BUTTON_HAMBURGER }}" tabindex="1" alt="Hamburger menu button" aria-label="Hamburger menu button">=</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
|
||||
<div class="footer">
|
||||
<h4 style="padding-top: 1vh;">Copyright © Precision And Research Technology Systems Limited. All rights reserved.</h4>
|
||||
<h4 style="padding-top: 1vh;">Copyright © {{ model.NAME_COMPANY }}. <a href="{{ url_for('license') }}" alt="License" aria-label="License">All rights reserved.</a></h4>
|
||||
<h5>Company number 13587499</h5>
|
||||
</div>
|
||||
|
||||
@@ -162,3 +162,4 @@
|
||||
hookupShared();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user