From 46f2f730e2d0723ed305de681b26fb2645eaf96b Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 1 May 2024 20:45:20 +0100 Subject: [PATCH] Store app.py deprecated.\nAdmin pages added: privacy notice, accessibility, data retention, license. Improvements to accessibility of those pages pending. --- DEPRECATED_app.py | 726 ++++++ __pycache__/app.cpython-311.pyc | Bin 33424 -> 12878 bytes __pycache__/config.cpython-311.pyc | Bin 2189 -> 2374 bytes __pycache__/forms.cpython-311.pyc | Bin 5435 -> 5473 bytes app.py | 787 ++----- app2.py | 205 -- .../__pycache__/__init__.cpython-311.pyc | Bin 439 -> 477 bytes .../__pycache__/basket.cpython-311.pyc | Bin 10115 -> 10156 bytes .../__pycache__/category.cpython-311.pyc | Bin 18793 -> 18843 bytes .../__pycache__/currency.cpython-311.pyc | Bin 2768 -> 2806 bytes .../delivery_option.cpython-311.pyc | Bin 4677 -> 4715 bytes .../delivery_region.cpython-311.pyc | Bin 3262 -> 3300 bytes .../__pycache__/discount.cpython-311.pyc | Bin 5113 -> 5151 bytes .../__pycache__/image.cpython-311.pyc | Bin 5727 -> 5775 bytes .../__pycache__/order.cpython-311.pyc | Bin 3977 -> 4015 bytes .../__pycache__/product.cpython-311.pyc | Bin 39890 -> 39945 bytes .../__pycache__/sql_error.cpython-311.pyc | Bin 2238 -> 2276 bytes .../__pycache__/variation.cpython-311.pyc | Bin 3613 -> 3651 bytes config.py | 6 +- .../__pycache__/__init__.cpython-311.pyc | Bin 421 -> 459 bytes .../datastore_store.cpython-311.pyc | Bin 28809 -> 28893 bytes forms.py | 4 +- lib/__pycache__/__init__.cpython-311.pyc | Bin 408 -> 446 bytes .../argument_validation.cpython-311.pyc | Bin 43450 -> 43827 bytes lib/__pycache__/data_types.cpython-311.pyc | Bin 689 -> 729 bytes models/__pycache__/__init__.cpython-311.pyc | Bin 419 -> 457 bytes .../model_view_base.cpython-311.pyc | Bin 5556 -> 5739 bytes .../model_view_contact.cpython-311.pyc | Bin 2031 -> 2069 bytes .../model_view_home.cpython-311.pyc | Bin 1637 -> 1675 bytes models/model_view_base.py | 2 + passenger_wsgi.py | 2 +- static/css/shared.css | 8 +- ...rated-privacy-notice-general-business.docx | Bin 0 -> 11035 bytes ...erated-privacy-notice-general-business.odt | Bin 0 -> 8606 bytes ...technology-systems-limited-evaluation.json | 1 + ...rch-technology-systems-limited-report.html | 60 + static/js/accessibility_statement.js | 5 + static/js/license.js | 2 +- templates/_page_accessibility_report.html | 60 + templates/_page_accessibility_statement.html | 211 ++ templates/_page_contact.html | 6 +- templates/_page_home.html | 3 +- templates/_page_privacy_notice.html | 92 + templates/_page_retention_schedule.html | 1959 +++++++++++++++++ templates/_shared.html | 4 + templates/layout.html | 9 +- 46 files changed, 3302 insertions(+), 850 deletions(-) create mode 100644 DEPRECATED_app.py delete mode 100644 app2.py create mode 100644 static/docs/generated-privacy-notice-general-business.docx create mode 100644 static/docs/generated-privacy-notice-general-business.odt create mode 100644 static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-evaluation.json create mode 100644 static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-report.html create mode 100644 static/js/accessibility_statement.js create mode 100644 templates/_page_accessibility_report.html create mode 100644 templates/_page_accessibility_statement.html create mode 100644 templates/_page_privacy_notice.html create mode 100644 templates/_page_retention_schedule.html create mode 100644 templates/_shared.html diff --git a/DEPRECATED_app.py b/DEPRECATED_app.py new file mode 100644 index 00000000..9f2c094f --- /dev/null +++ b/DEPRECATED_app.py @@ -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) # "

Boobs

" + 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=regionId=&¤cyId=&isIncludedVAT=', methods=['GET']) # & +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)) == "": + 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"" + for product in products: + product.id_stripe_product = model.create_product(product) + html += f"

product id = {product.id}


id_stripe_product = {product.id_stripe_product}

" + 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"" + for product in products: + product.id_stripe_price = model.create_price(product, currencies[product.id]) + html += f"

product id = {product.id}


id_stripe_price = {product.id_stripe_price}


currency = {currencies[product.id]}

" + html += "" + + return html + +# Fetch the Checkout Session to display the JSON result on the success page +@app.route('/store/checkout_session?', 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?', 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 "Error" + return "Email sent" +""" + +""" +@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 "MySQL test" +""" + +@app.route('/public_html/403.shtml', methods=['GET']) +def error_403(): + return "Error 403" + + +# Onload +if True or __name__ == '__main__': + app.run() + # app.run(debug=True, host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/__pycache__/app.cpython-311.pyc b/__pycache__/app.cpython-311.pyc index 70fdd409370ee1f390c630fa582f28ecc01c2503..e50d830d3a75fc202a70c2199c7ef2e1650a4d22 100644 GIT binary patch literal 12878 zcmd@)TWlNGm3KINiIhZHqD0G*Y3gN}wqBNBvg0_RB-)ZAiH=CwG1r?Z#Tm(D_>k|6 z>`0YU+7J-62vDQ-B3U#6R3OPZNEX|VU7)~Xfu;qDeryXenB5Wscp+fX1^TnAA`5Jg zuRUjmWlme778N$9tZ7@+roxt#JzW#6QDJM!k#=C+bNbj2=w4MqAT|qK8yo zN2)E|9&J})XXBpnT;n$Tqk?xE3rTe4(D!m~!kRFTBRZ zEk%a`+a(O|z@7qZ`%WxF1U7lPTvHh4beK(^G2kmOe*n17>Xf7ruIRuwUIZ49eNN}( zbeK(!81OpGCeIu2I?N_781OpGCZ99lb(l@!=hXz->Ts1l$%?l7X=dQqM@Iw4KH3^M z_R-tGu}=#O9M5bE-&E>(UE^WBKJPA3b#0B*<`Pxc)JSb9QFSeiR8NVDKc${GeD`39 z`n*o%beK(EGT?QXO}=2j>oA*q(SX-sHW@JBb(l?#8t^*ICUqSQZP`yVy*=DMIvP0k z(bmARkKP83eOh4PcxGGp)>6;w8XH>5A1YCGZH?5n5>?mKNNq1sbuEq5!zHS&p^@q> zQO9(<=&j<9l&Ir-Qhgw#_y`517`iiyVaQGRK0|54bds#cyhX2dvm4?8Gm_3@EdqSMp_vbu2ruwJGByq zH(xIE8h`oD>Uw*;O}OL-?cXBpTUQMlXMCk@>`5tuKFkm!!FU zC7jq(Lf3{Bz)0uL>vmi?6BUkG6nJ z&gk7`xZ0y(&t?y+umAbjs|+EIYc!h_;?e~lq-%lMNZ5Bnm=_b0;0P>Bx3g3w_=5bs z80CG_37$_0QZ~~cPA8<>4y;s8*QgLrh>2{*7s&9wSwR$HG=AF`5#qNq*;ICM#TQ-? zB_S>PrV>zD;2jQ~cEJ}|TJl{HG6IdI9G8Wdv`hu{(|~t4#sx7>6H5|Ezu=h6B&0+v zl~@%-pLARBT~5Ws*L<<1rBoswLzX_DC`cMDp3N*I7MH1t_sz#df%k!iSWU>>P0(y6 zEo7uVUkp^{g@r^0W!}sBXm(iw@YtYA8H~+C*$>rXl8*`*UZ6f`$xz)I>Ios73>qUe^Y8%G)=j(riv{oW;2O}6~#t{JIjJ7DJ?r# zI8maB%%ajHLL<2anoV55{ff>uy$%QVF;WT|2k1rbdSYMIAEvtjskgkPQt zq~f=Q^a?cGJRM7<6x%eg#THR}=U905GUzISP@p?g*%;69P|Vy_oY4Q}0(RP+Wx#Vw zsbx{AfiDV~IP?$5u!PVxl^2JG&czqz`B-e>EPqm%A3ircbRJ$|Bz9&YJ~AI0ik&)j zIwlOw3uhPNvG~bT!}CM2)8|f}Iek9P3+LjXYjA8f7~x(D-c&rO_te-#aQY@U9*6`+ z1K}VyH#@0#4TQ*SVC*GsIy63cd2%cenGDT@71!iA7nqAo3~^&qlfjvY(y{}=XDAcHhYoXbQQmcZ%!bPUSN?o}$I}?}= zmWtN`;qZ;n?6}gTQpbar19MXmkTo+7vNYsaXeJUEi*VQGMyDpn6!&a!EO0F{HW4Uc z)GlZ?U}q<<2O>d4uUZFh3#n8#UY=3#*8(pK@8>*Z5D+sG{Falxh57;EO;wc2W+92S z)l@~VV6|!O2jo3=6}ZC0&(5sYO`slqz8hJZ;yb=%9DcLH!pKsr0;O3hDpqZX(IZgh zBl0)Y^(cF1yM%h{QCcBC!?v3i9Bz?x}| zT{Km1v+kSM%$um|u2j9uEn<(^W>&4kLqopMOMcU8El!nS?f%HhlCbKKgx94(n7bPzJXMPbDiQL)_xFM`QVv52tLNQya`O=Q6L zv}wtr*h(vkQZGs|Dsfa0m$DgAP;6;!`lA@K;xww^MI1!x>Z#ex39eOM!sr;#z7Ma6 zj*%lpb7OAi(MaBVF@N~^g87nczLYm#D%Q8t-+H3p>XTi4xrt(}`@5ml6p+1ads2hdfz0~>Gjrjcud@M6OQZdo8Bz-=j=`UsYoG%m#-*{BUfD<*zksaq5zE|FQta?3EA&>_H6ClYId zug3*-YXWW}(T`Y)(Uww!lDPRSzoIaLh^+)uYlb%Cm*^~@-i23u4KkQI%w|i=(}p7- zm_C^PVd(wP--Qbe=jDd;8@3F zC%=97t-Bj_`rj9jwprkV-z}#*Z+3s)2H$%)@*MelN5IQ|YN-#jvY$3F2)9}whdBwR zQQVTC>s1ok4O{WK8EpPGgmgyRSKAmYS+Oekg<-r(*qxV7 z44bR8>PPe%qA`9Iu|IISD9!MtkJ6if{s8H3L56;0u^fNuYWwyZZ@sZ$e%jFa!SDz7 zfB3EUzx5C7k0XVKQMqBXB+k%3hoKnJz;dI7sUtWLrdfwGt zaCOVB?#+1K<hE+POVCM=HB6EpTn`*;BdSIQ36C^ zix7~CeGYYrUUh_*=OL(ssB&QS&}8N=gopfq?=l1*7>#9pqDmg1bMRSu1Hav%j!C6a zWraA7%Vs!@3x;kQqZTtClVXZ#Y)mm{VlZI`ur3-w8@a;(DQ0zoH*5SDH0>PAHu?(I zconm6U>3s+MJaZyH3`v~HoS3a#NiD|8{xI35zZ}TVO?1O^54KK{s&|@%o_~Dyy$fO z+S&P;v$NprmYv<(#8Knhavywr^>Jt;SUk}D*z>r3W3<@hd3^ryrH%1o%b~|9xutjG z%5Oa0{E@&f#eZDQN3(KhDL->3@1X?`l|6K0qS&7O$(4M27N2kMCE#{i12!h!ntoc_ z@ZHJpOg_3@s68Ur9?91pDK;H>bg}3(Kl^DH69Cw6fgCnBu*DW&S6<~M zIN7US@^b+GBwq4I+1Qr0C#4n}oZu=na}TT9N}Mza^k2dI)%K?QZ6yBZds7;QkFFjZ zjKyIbi;4LJ#91r-5Kh4?32VgH!DgM>$gS!DIF>8*z_V@W*{3E=LD&8>+7y9j#v2p) z*&BKHD+Tu}vilX|Nd4a$wRwk8sj!r#Uki&K*~_9@^Pg=_ceOdSYBU2T{1>$4SBf2j zzkt|ruHc@N-E)5kQ#K7!0nbw4ETmtAlU{x~^|hEz4{L_3G@dIr{@G@1++B$-fdO$> z^3{wu_KUf^J6v#wWq0@wVMN#95*&lZ;w$}`tOQHq*I{mL)eKl_IHxuI*~Y7{u0Sam z?ccw$;a>PfbKV^)xI?l#^an6pm7OqnRrf78A*lNn_$?>n?p6%8nbpSPd?V%x-6+KLU$kLboX z^Y%m917{8D!S+LcANj<3npNx^2T@gqg?}yT zIiEP7{&08k<_#|iDTWQo9z^xzPCXNB-CeVMB7^5V%E9?%5w8!52ano;5gvWgdd%>si{i-x#e*)@ z5Q;;s12>OUq)mWT8Z?A>Cl%A~=%^dfB|Z#DX*1TwFjuM9nD0po196cqP!A%lctyA* z!09(!CMs6#>W&`7O6XdODJxR^$1SB6uI#XpOEC%^ipLKWH|I)63@-1GkbnbuxOc&2 zL>wq7n!=+1+Kzl|@}7kD#Mm2*ID|Me>Y-T1gQIg-6k8-1j!e#6p?J=u-uX};LLHcO zV%CKj`XTMX>?mfvm>t8+kJ)j|PGHuD8D8V45s_j~z%?_6w@Y{~kC(T2XP`LZ6fUO) zE|yB+(f=ZtSYe^1G!8Wt^HK~hCanpGFQ}y0;JOsG$5t%BNd(6!7WHCTy*5&>X4G?j z^(a_9$5W$xHKbCv;Od@DUF_AxLY;Wj;iIs0IkRJ2eQCc4E(uHVCHfxd!oVxO3a$#L zzYMc&axi8%7J#gqFh}#GXZN#3y!q;BenlA4_QS0s*{`d1_a z`MsVZIhC(|Dpj`a#8#UVHYOfq*Rwfx%hs6t=A%;un@6^Ja^@m2=ghhAgPL{t(6)or zHN7>JW7lhn_3pPa062>W!thgHZ1cW*2>{2&#R56F>HN*Ue*eJwdyIYaH!aQ zT<#8Ng^uDOpM0oaD|m`+9dg^CRyeqIY(PGC84G!5cY*X22hYfZ5v_cpK>CWlqq6U; zR&Fnl!^Pfyxp!PEcNIvt`pqFMZ(J&X%GkVNwfI7Tv~4vWlpDLD=qfg}$PGS#>h^Ax zp@FKhr?IV`t=1!Q>j@Mmla`|AuhpnP;xD|Z%17jXCo ztGnLf%0(aCTECSu6^ZFh=R@blSb;Qv_4D8W7W(kqoGLy>rJFN4cm5REE9S|({J1|<&UJLVDJF(a1q>Zr$ zE9)ycGcywrx7Vz_BZ2$nCFSj+SLVx7qx}y6HmxA(}M(%o!$53%#&VZAC{Tn zYW7nVjLzP^xxBfE>;p10P|ZGA!8Dkewrx_$>WR5_WA!t(6-``UG~3?1^6<)o$@R&c f`iE<3y-E^s!~09miJ5x#SM}zZM%%BN8OZ+|@t-T# literal 33424 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 Kd4a_e>hKG%-gZxTG{CGhe~Tz{n^j zz%eK!I6gczDJHR?AZGGDmJmKZ>5kkPo)`JluJEZfxNlZqZDB+co2<(!Ir%i(5!NCZ zpxZXjV6S7;&<3(@v4s|-B$lN573l+cMe-m*0YoT*2xBlY*^V=h(RlM(PDLh0oyq69 z(>Oq8XfhV*Y_{cD#whOyQsoRH(6pO@#LPA`@l9uBG@Cq&e-@h`NW^clm4H5@-{dTT zO;SNvv;~610w delta 393 zcmaE;wOfmKIWI340}w1es+Xp|kvEQog*mr4eR462s+OUCMt*LpenDbUNpVg|ihe<9 zQch-ad`3xb4n#1%JT<8}vm{l2@(Y#_J}&VN-5H!0xseL=&5QhwU!s zE%w~h;^M^g)Xn?Y>lk%(fOg+v3oS@VEJ^h%G63?5Q4U3oyMUJl-Fb|(%qcQvy4&R38dNuM4%aC4iYoptid;(kSC n#3CmL3t2KoPVN<&Aqw^Z*nY5eVB;buTM5fDCT)%o=41i@L9b#E diff --git a/app.py b/app.py index 10fa618f..a6c2b539 100644 --- a/app.py +++ b/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) # "

Boobs

" - 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=regionId=&¤cyId=&isIncludedVAT=', methods=['GET']) # & -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)) == "": - 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"" - for product in products: - product.id_stripe_product = model.create_product(product) - html += f"

product id = {product.id}


id_stripe_product = {product.id_stripe_product}

" - 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"" - for product in products: - product.id_stripe_price = model.create_price(product, currencies[product.id]) - html += f"

product id = {product.id}


id_stripe_price = {product.id_stripe_price}


currency = {currencies[product.id]}

" - html += "" - - return html - -# Fetch the Checkout Session to display the JSON result on the success page -@app.route('/store/checkout_session?', 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?', 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 "Error" - return "Email sent" -""" - -""" -@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 "MySQL test" -""" - -@app.route('/public_html/403.shtml', methods=['GET']) -def error_403(): - return "Error 403" - - # Onload -if True or __name__ == '__main__': +if __name__ == '__main__': app.run() # app.run(debug=True, host="0.0.0.0", port=5000) \ No newline at end of file diff --git a/app2.py b/app2.py deleted file mode 100644 index 21ae53dd..00000000 --- a/app2.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/business_objects/__pycache__/__init__.cpython-311.pyc b/business_objects/__pycache__/__init__.cpython-311.pyc index 909a08d87b4b5087ad231427515b287bfac26059..c98cc42e08c8d1766c83f48f021af89af43bda25 100644 GIT binary patch delta 125 zcmdnae3zMTIWI340}!k#&rW4z+{mZPn3(Ns6%$&VT2vg9no^!v6yu+l>Qao|;sgS(2)sR9c*wms(sLpP!VKnp{$>KY0$LBLL|F9^?Q3 diff --git a/business_objects/__pycache__/basket.cpython-311.pyc b/business_objects/__pycache__/basket.cpython-311.pyc index 60cc1273012c88de0005e9916cc8dff3a77ec069..c6901b7b4fb31a84207e4ebd85b1b656a249729b 100644 GIT binary patch delta 166 zcmZqnU*pfWoR^o20SGdrmC`z-HuBA8$;ouKiU}=FEh>&lO({<-it*1&bt%d$OO0{K zPcF?(%_}L62`I`>FG|eK&CE-W3Ck=lP0UdUE-6jP%vUfnFfxh>a106wjt@^wib*Ue zh)F6f&df_KE{@Mn%1TWJ>P|{5&Q2}SE2zAsKluT(+-6=@b}_~)lSQSySwAo^@#<|( Ql4fLO67=7ksvyS>0Gy6E8vp!n?h+{ibZ#oAdvBR@A)zaX)wq&TM}MZcglDJL^IKBFWz z2O=0>o|;sgS(2)sR9c*wms(sLpP!VKnp{$>znP2GK#cL?WNT?}rjGK>-O`M#OeXG| I`xNBZ0WNqYeEy!4o`%;M6-9EISL(v-}61tS9^qnH54ppfAB@YJN3 z#DapDq|)Ndywu|2`23`-)MTLU&GszE7#V#gv$1K)d|+VZl)fRXc0pL}2CwK&J1Zv}33Ocp!(9R42`*E7FA6AK5l~u?cu7G00|O(c?PPsbB_{s( z&B3a?jEo_Z6V=37LE@5|tJP$=8G9zrG_(~13h=7k5Ldk*07V~|fdZRf83qb6W=ytm URs(CXo1E$lG%8_pr}G0=00qZfE&u=k delta 217 zcmbO|nepW$M!w~|yj%=GFsEEEEyZvn-#2D!SN)9q+*JL7#G;booRSp%g3_d%%;flt zlH44KV0?LMQgLQUs(w;wab{j>adCWpQdVkmNwNOsRF-3mj2@G9*fb?BaNFGwR=Xgq zc7s>+s-)f(Ny|&TmKS&}Hy5&CQky3^wm} He!vOg*iBkymP#B67)n9$VIHx2+#=IWI340}#CIJDqx9Bd-ZtVv(~|OlWaxQE^OaN_k>YjDKFLOHpQ7YK%*M za%paAUP*CGKv8~rQDSaxW?p(sSY~l)Vva&^Noh)EzJif~kx@*5V^BzNe0XY7OkzPn aOj2oaW?pJ>aeRJKR%$X(_vRY5liUC^iY|r# delta 87 zcmaE@a#V$PIWI340}#w9*Gu!=$ZNu8?W3QOpPQ;*kXTewoKupbUr?HqlbIZ!QIeYj l5sWWSO)AbTN!3p(EzZnKEiR7FPs&P7E-BXEyol{2Hvsw@A7}so diff --git a/business_objects/__pycache__/delivery_region.cpython-311.pyc b/business_objects/__pycache__/delivery_region.cpython-311.pyc index e450138c3cbc407c1b4d63865126e6f451d92f50..c0f6e2b40f4500c347383bfdd3cfca1792c0fba4 100644 GIT binary patch delta 125 zcmdld`9zX;IWI340}y=3{E+%+BkyLW#3E;_n9$VIHx2YjDKFLOHpQ7YK%*M za%paAUP*CGKv8~rQDSaxW?p(sSY~l)Vva&^Noh)EzJif~kx@*5V^BzNe0XY7OkzPn aOj2oaW?pJ>aeRJKR%$X(_vS42ejWe|rY+$B delta 87 zcmbQQ@l&05IWI340}#w9*GmiC$g9L|?W&)VpPQ;*kXTewoKupbUr?HqlbIZ!QIeYj l5sWWSO)AbTN!3p(EzZnKEiR7FPs&P7E-BXEJb}HR2LSIQ9{m6S diff --git a/business_objects/__pycache__/image.cpython-311.pyc b/business_objects/__pycache__/image.cpython-311.pyc index c7a5870907f2a3192e69a600726cca64ea99a620..c04cd308cacacf9fe6436e4bf83500d31a6297fd 100644 GIT binary patch delta 242 zcmcbw)33|7oR^o20SFYuZ=|vcZshY|PRww&iU}=FEh>&lO({<-it*1&bt%d$OO0{K zPcF?(%_}L62`I`>FG|eK&CE-W3Ck=lP0UdUE-6jP%vUfnFfxh>a106wjt@^wib*Ue zh)F6f&df_KE{@Mn%1TWJ>fSt&S&)gbYVu-MP0tSuoSd>Zgykk!-{2L!Dyerx(&!Se z(FX>0kf`znVP%-G@g-j44-BjzVU-KQDyYKDoU)sF+4LA0-6q?x>k5BhVC0p%Auc!mdcY~=G`wsz9b$j?pHFGwsZDb6WL(Jv@X%E?TQ&nU^w zfe6NzrzRC=mZa(@l@@2_r4|>*=O<;QCYKcJZ{E%<$i!GQ`6R2R;{|TB8^UrEtZ(p& zUX|3lB58Dq*XRPT(G6kc3&P4ULE}rj#us>vZwRYg5LQ7G+-%LJ$H?e1Ig4Fa@B+Wm V4RP5Cyde02nL$8#^D6eId;n_bLNWjV diff --git a/business_objects/__pycache__/order.cpython-311.pyc b/business_objects/__pycache__/order.cpython-311.pyc index b5dd3130dd6e5c5255018ed62064a3cffead50d5..aebfe3f35e634b5d9338d8257ae2b8fd7835cc44 100644 GIT binary patch delta 125 zcmeB_UoX$QoR^o20SJO-olZTzkvEhjF~iv^CbT%Us5mAyr981H#y>CBr6{v3HO3`B zximL5ucSC8peR4RC^0uTGcP?REVH;YF-IY|q%WIYd-GhDH(UV3B`wwf delta 87 zcmZ24-zm?#oR^o20SM-l>!oFHb85U*8A)34gdXUme9&y)pO@-Vlv$P< z%`t3?c^D^660Q>Yz`!IZbwgfdg2xRJ$qB9>Sb^NlJ;G7^jB=BoD_vv* z%5#=6PTsBTzy)GJxSJVO92MolfSzk$xXaDgQF4V};Uc%<6>h}^ftR?|J}@wHN^XvG qjgn^KkKep2;fO4w_2l|GMPZ->uj~zRnF%}~_<b8m{YEomRi1%&z8m7ML#1yH&wqNv8bdtrzAzcpfo8bGdVt^ zBsT{l7+;>6RGe9os-IL^oSBzeTpXXDl$DxXQmnsu4a-zUM)%21Y?|U1xV3HwD^9Sx z!7F@KT;qzk&Lv)*3%oj;+t?QKFix1PC0r$RLD=SoyvhWR8zPbuTtBcfh}dr4Asofe zC_Y(O`6BZLZllSslpWX@c#MI}%_b_2iZUTUr#3L$<>u=sxx%k-kz4T!x8j1pOWbM~ wxYagKc8!u|k}}`?KH-Qgqvhl+b&7%)_>FIf%S_+_!4J#~0w$YZ)G5jW0Fa1PBLDyZ diff --git a/business_objects/__pycache__/sql_error.cpython-311.pyc b/business_objects/__pycache__/sql_error.cpython-311.pyc index bcddb3374cbb6186986a2d956407722aab0ece4a..98bcccb1fdf68e028ce297bcf70d71682f1d7540 100644 GIT binary patch delta 125 zcmdld_(YI*IWI340}!nG^(poKMqX~##2jaVIHx2xFw diff --git a/business_objects/__pycache__/variation.cpython-311.pyc b/business_objects/__pycache__/variation.cpython-311.pyc index 913ca769f92b56c198eb4d221a60aee96b9c1fef..99b191c2d14af55e4a2e4d87021dd1992015135d 100644 GIT binary patch delta 125 zcmbO$b6AFVIWI340}!axoJrlfk@q57Vve&_OlWaxQE^OaN_k>YjDKFLOHpQ7YK%*M za%paAUP*CGKv8~rQDSaxW?p(sSY~l)Vva&^Noh)EzJif~kx@*5V^BzNe0XY7OkzPn aOj2oaW?pJ>aeRJKR%$X(_hw0U7IpyL!7Q-= delta 87 zcmX>sGgpRpIWI340}#w9*GmiE$a|5^+D$(rKQ~psAhD>VIHx2CBr6{v3HO3`B zximL5ucSC8peR4RC^0uTGcP?REVH;YF-IY|q%VIHx2b82+UAQD{kA!=foPF<7^cZTAW%`9Fv+-o>&y)pO@-Vlv$P< zhT#{duS{$>vm9<5LiIrtCw^SV42L@J7cgD#%Qj#!chg6*E2L=|-V8*S= zTT?Drhg>uZy2M|>lbO?I^FmpFR>soF zAC=;n6rv^vDKCPEPhKb+yE#PV88c((WNVGZ%omg_Hb2lX6JXrGS==&~1+K_r@_t)2 zjSmdGoFR-iB;`La@bHE(LdXfMH-sgoSY1#wza(t_fq`2vgmE&P-2%}M42+T?j2pZT zs2Gpy={YWYNj514Jj^i>TeaJ^Ydc0Hu7E00000 delta 386 zcmccnkg@Y2Bj0jfUM>b8m{YEo*4w<1&xzI4O+O<)H&wqNv8bdtrzAzcpfo8bGdVt^ zBsT{l7+;>6RGe9os-Kcrl2}}lUzA#`zj-xliwM(K=E;Uqaq<_qHP$+BWw~JLanZo@ zih<`v9j_}oUKe~5FX<#+&`F%U1*}GItMb;A3)UeQ%|fr3ghZYBmSUU1nh{o~*7M&!ieWxkGsoR3>(FkIFM<#*oRy z8jG1$ux*yqG!tOlusOgomIbQ9WAZOsHN_758-p1nqLw& zzaVTr*~4xD{{?A-4PFOSk7!;n@dl#Jf9<9-GcKDv)hUqq0-xOEH%?w4db5r5K32v> wn}2&aGJ-7G?C4d^%2>B~ufHsq4|H(cX4}BsEI_fJp$M_bwt=FX`@;Pt0oTclzyJUM diff --git a/forms.py b/forms.py index 3efee6bb..42c29002 100644 --- a/forms.py +++ b/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') diff --git a/lib/__pycache__/__init__.cpython-311.pyc b/lib/__pycache__/__init__.cpython-311.pyc index 781787b482503841583de354304c1500d728d209..6733d0f1e0ce4d90afcc72a98e83a82d7905975c 100644 GIT binary patch delta 111 zcmbQiypNf8IWI340}xCp&rW^6kynN>B+l6?CbT%Us5mAyr981H#y>CBr6{v3HO3`B zximL5ucSC8peR4RC^0uTGcP?REVH;YF-IY|q%ABLFr}CbR$m delta 73 zcmdnTJcF5cIWI340}#w9*Gqe{kynOM&q6;VKQ~psAhD>VIHx2F7{~9qm(x<(LJOi&T6rrFkTSS{qN26POESs?i&ddcTPT$c%4=~k4l$b% zI@Id3#Vg5Nm5~`UmYO>W zbAHcx&N=tm_cVWxj{ih1kE6)1A#HX0Z_lp8tHezm+csKu_S^gVE%vs9);`O2r@gAL zW6*A?>TY#)*_{LZmWIA=N1wH;tHbHAm^=Di*3NX(fUB*eJDn|JSxbXqN0VvaF1yWQ z?dh>}cGxVuS-d|M-G?s{@DQR18)H70Dxb1V53+j|`4( z8r#Gv-Fgx&Q9-fN49^sK%ZK%+EzNXjR6nNY8Us=?Sx9wL*&7q0M3`q2e-P8LF(6SL zSuXDd)uH7>8sWH7v+VL;rFN3CMWpNkb({7@*vHaDkIlU~Ov&x?9t>y-Bg9kcexf9c z`y)&6h)0cM#t{^Z&Y>k0T%&i@cuR;P5^6L#3PGtBO%>E>Qu))G%a|51usQ2TffRdXkKW{EfouXt%%el!UqtS?JXVGMRZW6^i;d+N&h4V5S%V>CJBB6 z3d)i^cK3F{Z$J>IYYF?N7kv|g9^F1ZH|Khc0P-Y13UXs@-U8B%wV5C-D1W&We2;E7 zMo#`)_5o6ojZW4E)3Ue$ZXm=E4b!E0iGrIQOw)2M&Lz0nk0GP92-cK|8GjPX=ay}d zplPsfc}mVSjSUEldX|pe3d8!;sB=Ez1oX$q8W^jCQeoq^kiJXQaJb(5AMV3?11~b1 zV~`uXRae7_DiK(!NooUlsw1h1^I6#8f2#Ii>2;c-hN+N>lBK|0qwY+}tMFz?B7B#% z5`M1}Lzb~T3iP^hDWWgp{9+u7!X$cj3Kwq);-bqxtbfX&tMEpftCsT>!YY&_iBOCvyn?Wysv0wW>l%VsV zcHe;=Z=YR&-#fG!ZoXIid}O2L2DmjeOsY}ijG$EEVci=^36kyPs2wpU2_ht;;*_2Z z0f~B^O2!#bcS21{OOj`JYcAO?8p9HLPrLz@!xbcPygY-9ioiE4TyjZxlnVP#iFn=7 zKVs3ByxQH0o&zkwiIta)m`BXhD?m}8fkR{a{|B(C7oZwWTK^3Nx}#;_Jh>4*I9kCU zIpt%}$9xg&MkzucwU=^znZ9CQ=BI0CRDt|$VGnMpiupV5PsO2k_`@F!MN9e1+qO_t z+l9%Ta3sfYj&btj35nMG8ZU1O%bAkdRoBQHEPR(pDrma6G7|1_af4@uBH`07oFm~Sk=J;h65|#cZd}aDou{z8g`R4i*7^?4xIQ16V{7iRHFJfvcMEH8 z>jQ<&WGV>&dZJf2n}QsiIJO}9zbDcW(PHdGkUp8eN{DcxnX1|3>vgx;K!x!B5(8Q$ N+ZiJGuP6Ux{s9DrjB@}0 delta 1525 zcmb_cZA@EL81B8-TIjtOS_XslV;@ku@YPxv8|x^g<3pn@6DM_o1ZfAXS&@~Nk>M;I zj^xrYyz%wpu5qz47!7Vo$%#qDsU*he{+LUhvsuETGfU7I!~PgeO!RU~HZjpZ?*4h- z=eh6uJm)><qI)pl_Xds{PYk)d z9(UKVK)*Llb`AHq{GLFM(d%&;(P`O>8Q2%Fl3haZvNE6)`-Hd$o=~`jSO{@NF3wot zdsP7hm36Q~xnA}UxNiO z(5M>StT3_}w!$@~_CEz5)p&)LBA~R5@R4SxR>(KZzaw*4SJ=W>xaXq7Nv(m`8sJZj zU;m)uE|ty7ChU`T220&9ZjI(gI=BvqYV~rVcLSoE+Rvr<9ULweeTy?RoYIw~Lm^z( z)etf&lA-G-K@pp{u zdbOzUDHHXZ%0>OA@neXXJWIEWnC5Ktb#wRmADWdt5=J#wO)kd_2ZQAH$|=wT*uUb z+UDF$EVS6rXSNFjb{0CDJ`{U3&0_D<&1os02+?daDPdf(91C5?@P0`h?JAUJ%2tr>Id%D1k{F$elI8Wfh<7l4D)M+v^82i~8aRWT$-aD5S) zqhtHxt@8(B2NG3F`HPl>MLgWOOIuZpoE_p$z8++QvAuEIyg6o06ffx)4GDu#QY91L zq6rJ^nkgcdb4%gL8R5;A+P`d diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc index e89d6f77070fd77fda6343b5d700aa7e87fa17b6..53ba36d8a840252eee573b0f1394ea6bec78aa13 100644 GIT binary patch delta 114 zcmZ3?e3F@WIWI340}zy#XQzJO$g9E_p6F~96Iz^FR2-9B`0JVK68vpVIHx2n&Ma40xDdmYpG5&d}E=8GT zsWC43$)zCm#W4Xz`RPT8xw)Bn=`mrM#ifZk3c)3%DVg~SMg~SkF#(Q2A;IzCsYx-3 z1qCs=`6;P6#W9nk*$f!NH}|leU}TKm9LnLy!kDsoI*%a}W7*`Bd>V|_n?Lb2Fe)i} zI0k#f2RORB#ydJYy9Nh)I(hndhD62(hd73~`nvjsY%Ua7!N|ko=jiJi@9ggz;OIA5 zTquum{p2>GJ!~3I7g$tpOm-5kU=nuQyj@s}k+Em;Baw-Y5sZu~9~iKc6OuoIWxn8$ JFERrLGyn;seLMgF delta 282 zcmaE@vqhV4IWI340}$+2w6GEFgVVTdwIF#(e19SjwWQ5L}rnx>n5nC%%iFJ%#Aocxk?nxT_^Mt*LpenDbU zNpVg|ihe<9Qch-ad`3xb4n#1%JT<8}vm{kNH$Npcr&xdTH8umr(9P`ZCm0zcH(%gz zWMNF&EW~Tb#8@)fg{1Ghk1&4f*8PKNy-VIHx2!tZ^ :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 { diff --git a/static/docs/generated-privacy-notice-general-business.docx b/static/docs/generated-privacy-notice-general-business.docx new file mode 100644 index 0000000000000000000000000000000000000000..0e81a38c4675105e5e3614cb86cb6203f39da6eb GIT binary patch literal 11035 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 literal 0 HcmV?d00001 diff --git a/static/docs/generated-privacy-notice-general-business.odt b/static/docs/generated-privacy-notice-general-business.odt new file mode 100644 index 0000000000000000000000000000000000000000..cafb0af2242d439d8e24f82e227ceac6a822c44e GIT binary patch literal 8606 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

$2>j65oLm5E9`&QOH11xia#T-*R+MG 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 literal 0 HcmV?d00001 diff --git a/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-evaluation.json b/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-evaluation.json new file mode 100644 index 00000000..53c592b0 --- /dev/null +++ b/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-evaluation.json @@ -0,0 +1 @@ +{"@context":{"reporter":"http://github.com/w3c/wai-wcag-em-report-tool/","wcagem":"http://www.w3.org/TR/WCAG-EM/#","Evaluation":"wcagem:procedure","defineScope":"wcagem:step1","scope":"wcagem:step1a","step1b":{"@id":"wcagem:step1b","@type":"@id"},"conformanceTarget":"step1b","accessibilitySupportBaseline":"wcagem:step1c","additionalEvaluationRequirements":"wcagem:step1d","exploreTarget":"wcagem:step2","essentialFunctionality":"wcagem:step2b","pageTypeVariety":"wcagem:step2c","technologiesReliedUpon":"wcagem:step2d","selectSample":"wcagem:step3","structuredSample":"wcagem:step3a","randomSample":"wcagem:step3b","Website":"wcagem:website","Webpage":"wcagem:webpage","auditSample":"wcagem:step4","reportFindings":"wcagem:step5","documentSteps":"wcagem:step5a","commissioner":"wcagem:commissioner","evaluator":"wcagem:evaluator","evaluationSpecifics":"wcagem:step5b","WCAG":"http://www.w3.org/TR/WCAG/#","WCAG20":"http://www.w3.org/TR/WCAG20/#","WCAG21":"http://www.w3.org/TR/WCAG21/#","WAI":"http://www.w3.org/WAI/","A":"WAI:WCAG2A-Conformance","AA":"WAI:WCAG2AA-Conformance","AAA":"WAI:WCAG2AAA-Conformance","wcagVersion":"WAI:standards-guidelines/wcag/#versions","reportToolVersion":"wcagem:reportToolVersion","earl":"http://www.w3.org/ns/earl#","Assertion":"earl:Assertion","TestMode":"earl:TestMode","TestCriterion":"earl:TestCriterion","TestCase":"earl:TestCase","TestRequirement":"earl:TestRequirement","TestSubject":"earl:TestSubject","TestResult":"earl:TestResult","OutcomeValue":"earl:OutcomeValue","Pass":"earl:Pass","Fail":"earl:Fail","CannotTell":"earl:CannotTell","NotApplicable":"earl:NotApplicable","NotTested":"earl:NotTested","assertedBy":"earl:assertedBy","mode":"earl:mode","result":"earl:result","subject":"earl:subject","test":"earl:test","outcome":"earl:outcome","dcterms":"http://purl.org/dc/terms/","title":"dcterms:title","description":"dcterms:description","summary":"dcterms:summary","date":"dcterms:date","hasPart":"dcterms:hasPart","isPartOf":"dcterms:isPartOf","id":"@id","type":"@type","language":"@language"},"language":"en","type":"Evaluation","reportToolVersion":"3.0.3","defineScope":{"id":"_:defineScope","scope":{"description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"conformanceTarget":"AA","accessibilitySupportBaseline":"Google Chrome with NVDA, FireFox with NVDA, Ecosia mobile browser with TalkBack.","additionalEvaluationRequirements":"","wcagVersion":"2.2"},"exploreTarget":{"id":"_:exploreTarget","essentialFunctionality":"1. Navigation\n2. User input form\n3. Scripted text","pageTypeVariety":"1. Navigation\n2. User input form\n3. Scripted text","technologiesReliedUpon":["HTML","CSS","JavaScript","python Flask"]},"selectSample":{"id":"_:selectSample","structuredSample":[{"id":"_:subject_2","type":["TestSubject","Webpage"],"date":"2024-04-30T16:11:16.199Z","description":"https://www.partsltd.co.uk/","title":"Home"},{"id":"_:subject_3","type":["TestSubject","Webpage"],"date":"2024-04-30T16:11:32.890Z","description":"https://www.partsltd.co.uk/contact","title":"Contact us"},{"id":"_:subject_4","type":["TestSubject","Webpage"],"date":"2024-04-30T16:11:48.238Z","description":"https://www.partsltd.co.uk/services","title":"Services"}],"randomSample":{"id":"_:subject_5","type":["TestSubject","Webpage"],"date":"2024-04-30T16:12:19.332Z","description":"","title":""}},"auditSample":[{"type":"Assertion","date":"2024-04-30T15:48:00.008Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:19:30.651Z","description":"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.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:non-text-content","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:20:00.306Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:audio-only-and-video-only-prerecorded","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:20:04.748Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:captions-prerecorded","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:20:08.119Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:audio-description-or-media-alternative-prerecorded","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:20:10.538Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:captions-live","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:20:14.224Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:audio-description-prerecorded","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:11:01.128Z","description":"Elements change in response to zoom and viewport dimensions properly. Aria-label provided for all images.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:info-and-relationships","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:32:41.865Z","description":"Flow layout keeps associated sections together but allows dynamic structure depending on size of elements relative to screen.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:meaningful-sequence","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:11:04.668Z","description":"Content reads properly as raw text in default order. Aria-label provided for all images and names for all form input components.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:sensory-characteristics","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:35:31.858Z","description":"Orientation of content is not locked.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:orientation","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:38:55.478Z","description":"Appropriate visible labels used alongside name attributes for form input elements.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:identify-input-purpose","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:40:53.059Z","description":"Colour only used to convey meaning for text hyperlinks, which have alt-text attributes to identify their purpose and presence.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:use-of-color","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:39:24.368Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:audio-control","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:46:29.787Z","description":"Webpage text contrast assessed with tool at this website: https://accessibleweb.com/color-contrast-checker/","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:contrast-minimum","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:47:26.364Z","description":"All webpages can be scaled to up to 500% while maintaining structure and visible components.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:resize-text","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:47:30.067Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:images-of-text","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:50:44.949Z","description":"Flex layout forces only vertical scrolling at required viewport dimensions.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:reflow","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:51:34.482Z","description":"Strong borders used for form input components.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:non-text-contrast","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:55:05.024Z","description":"Content becomes vertically scrollable as necessary.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:text-spacing","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:57:32.588Z","description":"Hamburger menu button for navigation overlay used. The button remains stationary with no elements above it, throughout use of navigation.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:content-on-hover-or-focus","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:58:42.536Z","description":"Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:keyboard","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:58:59.976Z","description":"Tab indices set to enable correct transition around page by keyboard.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:no-keyboard-trap","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T16:59:38.989Z","description":"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.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:character-key-shortcuts","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:00:42.868Z","description":"No session time limits imposed on user.","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:timing-adjustable","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:01:05.985Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:pause-stop-hide","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:01:18.827Z","description":"","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:three-flashes-or-below-threshold","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:01:53.694Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:bypass-blocks","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:02:13.542Z","description":"Descriptive titles used on all webpages.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:page-titled","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:02:30.677Z","description":"Keyboard tab indices set for logical navigation around pages.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:focus-order","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:11:09.564Z","description":"Descriptive aria-label provided for all text hyperlinks.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:link-purpose-in-context","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:03:56.473Z","description":"Navigation on all webpages and company logo links to home page.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:multiple-ways","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:06:48.832Z","description":"","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:headings-and-labels","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:06:52.238Z","description":"","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:focus-visible","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:07:05.561Z","description":"","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:focus-not-obscured-minimum","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:07:25.308Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:pointer-gestures","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:08:50.776Z","description":"Up event used to trigger events across website.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:pointer-cancellation","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:13:48.959Z","description":"Input label for attributes used to associate with input elements.\nAria-label attributes used for text hyperlinks.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:label-in-name","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:13:54.759Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:motion-actuation","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:13:56.851Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:dragging-movements","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.865Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:18:22.901Z","description":"Minimum control dimension is 27 CSS pixels.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:target-size-minimum","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.009Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:19:45.079Z","description":"English - Great Britain on all pages.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:language-of-page","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:20:10.271Z","description":"No language changes across website.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:language-of-parts","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:23:41.854Z","description":"No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:on-focus","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:23:56.660Z","description":"No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:on-input","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:24:11.721Z","description":"Navigation component and mechanisms shared across all pages.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:consistent-navigation","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:25:13.874Z","description":"Classes and CSS styles used to group collections of elements by purpose and functionality.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:consistent-identification","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:26:06.583Z","description":"Contact us button provided in a consistent format.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:consistent-help","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:26:47.172Z","description":"Each input element has an associated error display label which are triggered when input validation is not met.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:error-identification","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:27:08.142Z","description":"Each user input has a descriptive label.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:labels-or-instructions","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:30:48.127Z","description":"Text description of incomplete required fields is provided. Description of what caused error is issued when it involves simple regular expression validation.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:error-suggestion","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:31:12.544Z","description":"","outcome":{"id":"earl:inapplicable","type":["OutcomeValue","NotApplicable"],"title":"Not present"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:error-prevention-legal-financial-data","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:33:08.124Z","description":"No redundant data entry required. User's browser cache stores form data for rapid re-entry on crash or any other occasion.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:redundant-entry","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:34:17.347Z","description":"Alternative sign in methods provided, including recovery by code by phone and email.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:accessible-authentication-minimum","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:37:16.977Z","description":"Aria-label attribute maintained for all images and text hyperlinks. Label for attribute used to associate input elements with descriptive labels.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:name-role-value","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}},{"type":"Assertion","date":"2024-04-30T15:48:00.010Z","mode":{"type":"TestMode","@value":"earl:manual"},"result":{"type":"TestResult","date":"2024-04-30T17:38:13.227Z","description":"Success feedback provided on form submission, with descriptive errors on failure.","outcome":{"id":"earl:passed","type":["OutcomeValue","Pass"],"title":"Passed"}},"subject":{"id":"_:subject_1","type":["TestSubject","Website"],"date":"2024-04-30T15:47:59.864Z","description":"'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/","title":"Public Website of Precision And Research Technology Systems Limited"},"test":{"id":"WCAG22:status-messages","type":["TestCriterion","TestRequirement"],"date":"2024-04-30T15:47:59.866Z"}}],"reportFindings":{"date":{"type":"http://www.w3.org/TR/NOTE-datetime","@value":"Tue Apr 30 2024"},"summary":"","title":"","commissioner":"Lord Edward Middleton-Smith","evaluator":"Lord Edward Middleton-Smith","documentSteps":[{"id":"_:about"},{"id":"_:defineScope"},{"id":"_:exploreTarget"},{"id":"_:selectSample"}],"evaluationSpecifics":""}} \ No newline at end of file diff --git a/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-report.html b/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-report.html new file mode 100644 index 00000000..527d4245 --- /dev/null +++ b/static/docs/wcag_2.2AA_public-website-of-precision-and-research-technology-systems-limited-report.html @@ -0,0 +1,60 @@ +public-website-of-precision-and-research-technology-systems-limited-report.html

Report

About the Evaluation

Report Creator
Lord Edward Middleton-Smith
Evaluation Commissioner
Lord Edward Middleton-Smith
Evaluation date
Tue Apr 30 2024

Executive Summary

Not provided

Scope of the Evaluation

Website name
Public Website of Precision And Research Technology Systems Limited
Scope of the website
'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/
WCAG Version
2.2
Conformance target
AA
Accessibility support baseline
Google Chrome with NVDA, FireFox with NVDA, Ecosia mobile browser with TalkBack.
Additional evaluation requirements
Not provided

Detailed Audit Results

Summary

Reported on 55 of 55 WCAG 2.2 AA + Success Criteria.

  • 41 Passed
  • 0 Failed
  • 0 Cannot tell
  • 14 Not present
  • 0 Not checked

All Results

1 Perceivable

1.1 Text Alternatives
Success Criterion Result Observations
1.1.1: Non-text Content

Result: Passed

Observations:

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.

+
1.2 Time-based Media
Success Criterion Result Observations
1.2.1: Audio-only and Video-only (Prerecorded)

Result: Not present

1.2.2: Captions (Prerecorded)

Result: Not present

1.2.3: Audio Description or Media Alternative (Prerecorded)

Result: Not present

1.2.4: Captions (Live)

Result: Not present

1.2.5: Audio Description (Prerecorded)

Result: Not present

1.3 Adaptable
Success Criterion Result Observations
1.3.1: Info and Relationships

Result: Passed

Observations:

Elements change in response to zoom and viewport dimensions properly. Aria-label provided for all images.

+
1.3.2: Meaningful Sequence

Result: Passed

Observations:

Flow layout keeps associated sections together but allows dynamic structure depending on size of elements relative to screen.

+
1.3.3: Sensory Characteristics

Result: Passed

Observations:

Content reads properly as raw text in default order. Aria-label provided for all images and names for all form input components.

+
1.3.4: Orientation

Result: Passed

Observations:

Orientation of content is not locked.

+
1.3.5: Identify Input Purpose

Result: Passed

Observations:

Appropriate visible labels used alongside name attributes for form input elements.

+
1.4 Distinguishable
Success Criterion Result Observations
1.4.1: Use of Color

Result: Passed

Observations:

Colour only used to convey meaning for text hyperlinks, which have alt-text attributes to identify their purpose and presence.

+
1.4.2: Audio Control

Result: Not present

1.4.3: Contrast (Minimum)

Result: Passed

Observations:

Webpage text contrast assessed with tool at this website: https://accessibleweb.com/color-contrast-checker/

+
1.4.4: Resize text

Result: Passed

Observations:

All webpages can be scaled to up to 500% while maintaining structure and visible components.

+
1.4.5: Images of Text

Result: Not present

1.4.10: Reflow

Result: Passed

Observations:

Flex layout forces only vertical scrolling at required viewport dimensions.

+
1.4.11: Non-text Contrast

Result: Passed

Observations:

Strong borders used for form input components.

+
1.4.12: Text Spacing

Result: Passed

Observations:

Content becomes vertically scrollable as necessary.

+
1.4.13: Content on Hover or Focus

Result: Passed

Observations:

Hamburger menu button for navigation overlay used. The button remains stationary with no elements above it, throughout use of navigation.

+

2 Operable

2.1 Keyboard Accessible
Success Criterion Result Observations
2.1.1: Keyboard

Result: Passed

Observations:

Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed.

+
2.1.2: No Keyboard Trap

Result: Passed

Observations:

Tab indices set to enable correct transition around page by keyboard.

+
2.1.4: Character Key Shortcuts

Result: Passed

Observations:

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.

+
2.2 Enough Time
Success Criterion Result Observations
2.2.1: Timing Adjustable

Result: Not present

Observations:

No session time limits imposed on user.

+
2.2.2: Pause, Stop, Hide

Result: Not present

2.3 Seizures and Physical Reactions
Success Criterion Result Observations
2.3.1: Three Flashes or Below Threshold

Result: Passed

2.4 Navigable
Success Criterion Result Observations
2.4.1: Bypass Blocks

Result: Not present

2.4.2: Page Titled

Result: Passed

Observations:

Descriptive titles used on all webpages.

+
2.4.3: Focus Order

Result: Passed

Observations:

Keyboard tab indices set for logical navigation around pages.

+
2.4.4: Link Purpose (In Context)

Result: Passed

Observations:

Descriptive aria-label provided for all text hyperlinks.

+
2.4.5: Multiple Ways

Result: Passed

Observations:

Navigation on all webpages and company logo links to home page.

+
2.4.6: Headings and Labels

Result: Passed

2.4.7: Focus Visible

Result: Passed

2.4.11: Focus Not Obscured (Minimum)

Result: Passed

2.5 Input Modalities
Success Criterion Result Observations
2.5.1: Pointer Gestures

Result: Not present

2.5.2: Pointer Cancellation

Result: Passed

Observations:

Up event used to trigger events across website.

+
2.5.3: Label in Name

Result: Passed

Observations:

Input label for attributes used to associate with input elements. +Aria-label attributes used for text hyperlinks.

+
2.5.4: Motion Actuation

Result: Not present

2.5.7: Dragging Movements

Result: Not present

2.5.8: Target Size (Minimum)

Result: Passed

Observations:

Minimum control dimension is 27 CSS pixels.

+

3 Understandable

3.1 Readable
Success Criterion Result Observations
3.1.1: Language of Page

Result: Passed

Observations:

English - Great Britain on all pages.

+
3.1.2: Language of Parts

Result: Passed

Observations:

No language changes across website.

+
3.2 Predictable
Success Criterion Result Observations
3.2.1: On Focus

Result: Passed

Observations:

No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.

+
3.2.2: On Input

Result: Passed

Observations:

No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.

+
3.2.3: Consistent Navigation

Result: Passed

Observations:

Navigation component and mechanisms shared across all pages.

+
3.2.4: Consistent Identification

Result: Passed

Observations:

Classes and CSS styles used to group collections of elements by purpose and functionality.

+
3.2.6: Consistent Help

Result: Passed

Observations:

Contact us button provided in a consistent format.

+
3.3 Input Assistance
Success Criterion Result Observations
3.3.1: Error Identification

Result: Passed

Observations:

Each input element has an associated error display label which are triggered when input validation is not met.

+
3.3.2: Labels or Instructions

Result: Passed

Observations:

Each user input has a descriptive label.

+
3.3.3: Error Suggestion

Result: Passed

Observations:

Text description of incomplete required fields is provided. Description of what caused error is issued when it involves simple regular expression validation.

+
3.3.4: Error Prevention (Legal, Financial, Data)

Result: Not present

3.3.7: Redundant Entry

Result: Passed

Observations:

No redundant data entry required. User's browser cache stores form data for rapid re-entry on crash or any other occasion.

+
3.3.8: Accessible Authentication (Minimum)

Result: Passed

Observations:

Alternative sign in methods provided, including recovery by code by phone and email.

+

4 Robust

4.1 Compatible
Success Criterion Result Observations
4.1.2: Name, Role, Value

Result: Passed

Observations:

Aria-label attribute maintained for all images and text hyperlinks. Label for attribute used to associate input elements with descriptive labels.

+
4.1.3: Status Messages

Result: Passed

Observations:

Success feedback provided on form submission, with descriptive errors on failure.

+

Sample of Audited Web Pages

  1. Home - https://www.partsltd.co.uk/
  2. Contact us - https://www.partsltd.co.uk/contact
  3. Services - https://www.partsltd.co.uk/services
  4. -

Web Technology

HTML,CSS,JavaScript,python Flask

Recording of Evaluation Specifics

Not provided

\ No newline at end of file diff --git a/static/js/accessibility_statement.js b/static/js/accessibility_statement.js new file mode 100644 index 00000000..1a4f23ab --- /dev/null +++ b/static/js/accessibility_statement.js @@ -0,0 +1,5 @@ +var _loading = true; + +function hookupPageAccessibilityStatement() { + _loading = false; +} diff --git a/static/js/license.js b/static/js/license.js index 6ea04d1a..cf82f4e8 100644 --- a/static/js/license.js +++ b/static/js/license.js @@ -1,5 +1,5 @@ var _loading = true; -function hookupPageHome() { +function hookupPageLicense() { _loading = false; } diff --git a/templates/_page_accessibility_report.html b/templates/_page_accessibility_report.html new file mode 100644 index 00000000..527d4245 --- /dev/null +++ b/templates/_page_accessibility_report.html @@ -0,0 +1,60 @@ +public-website-of-precision-and-research-technology-systems-limited-report.html

Report

About the Evaluation

Report Creator
Lord Edward Middleton-Smith
Evaluation Commissioner
Lord Edward Middleton-Smith
Evaluation date
Tue Apr 30 2024

Executive Summary

Not provided

Scope of the Evaluation

Website name
Public Website of Precision And Research Technology Systems Limited
Scope of the website
'All web content of the public mobile and desktop website of Precision And Research Technology Systems Limited located at https://www.partsltd.co.uk/
WCAG Version
2.2
Conformance target
AA
Accessibility support baseline
Google Chrome with NVDA, FireFox with NVDA, Ecosia mobile browser with TalkBack.
Additional evaluation requirements
Not provided

Detailed Audit Results

Summary

Reported on 55 of 55 WCAG 2.2 AA + Success Criteria.

  • 41 Passed
  • 0 Failed
  • 0 Cannot tell
  • 14 Not present
  • 0 Not checked

All Results

1 Perceivable

1.1 Text Alternatives
Success Criterion Result Observations
1.1.1: Non-text Content

Result: Passed

Observations:

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.

+
1.2 Time-based Media
Success Criterion Result Observations
1.2.1: Audio-only and Video-only (Prerecorded)

Result: Not present

1.2.2: Captions (Prerecorded)

Result: Not present

1.2.3: Audio Description or Media Alternative (Prerecorded)

Result: Not present

1.2.4: Captions (Live)

Result: Not present

1.2.5: Audio Description (Prerecorded)

Result: Not present

1.3 Adaptable
Success Criterion Result Observations
1.3.1: Info and Relationships

Result: Passed

Observations:

Elements change in response to zoom and viewport dimensions properly. Aria-label provided for all images.

+
1.3.2: Meaningful Sequence

Result: Passed

Observations:

Flow layout keeps associated sections together but allows dynamic structure depending on size of elements relative to screen.

+
1.3.3: Sensory Characteristics

Result: Passed

Observations:

Content reads properly as raw text in default order. Aria-label provided for all images and names for all form input components.

+
1.3.4: Orientation

Result: Passed

Observations:

Orientation of content is not locked.

+
1.3.5: Identify Input Purpose

Result: Passed

Observations:

Appropriate visible labels used alongside name attributes for form input elements.

+
1.4 Distinguishable
Success Criterion Result Observations
1.4.1: Use of Color

Result: Passed

Observations:

Colour only used to convey meaning for text hyperlinks, which have alt-text attributes to identify their purpose and presence.

+
1.4.2: Audio Control

Result: Not present

1.4.3: Contrast (Minimum)

Result: Passed

Observations:

Webpage text contrast assessed with tool at this website: https://accessibleweb.com/color-contrast-checker/

+
1.4.4: Resize text

Result: Passed

Observations:

All webpages can be scaled to up to 500% while maintaining structure and visible components.

+
1.4.5: Images of Text

Result: Not present

1.4.10: Reflow

Result: Passed

Observations:

Flex layout forces only vertical scrolling at required viewport dimensions.

+
1.4.11: Non-text Contrast

Result: Passed

Observations:

Strong borders used for form input components.

+
1.4.12: Text Spacing

Result: Passed

Observations:

Content becomes vertically scrollable as necessary.

+
1.4.13: Content on Hover or Focus

Result: Passed

Observations:

Hamburger menu button for navigation overlay used. The button remains stationary with no elements above it, throughout use of navigation.

+

2 Operable

2.1 Keyboard Accessible
Success Criterion Result Observations
2.1.1: Keyboard

Result: Passed

Observations:

Tab indices set to enable correct transition around page by keyboard. Other keyboard shortcuts not changed.

+
2.1.2: No Keyboard Trap

Result: Passed

Observations:

Tab indices set to enable correct transition around page by keyboard.

+
2.1.4: Character Key Shortcuts

Result: Passed

Observations:

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.

+
2.2 Enough Time
Success Criterion Result Observations
2.2.1: Timing Adjustable

Result: Not present

Observations:

No session time limits imposed on user.

+
2.2.2: Pause, Stop, Hide

Result: Not present

2.3 Seizures and Physical Reactions
Success Criterion Result Observations
2.3.1: Three Flashes or Below Threshold

Result: Passed

2.4 Navigable
Success Criterion Result Observations
2.4.1: Bypass Blocks

Result: Not present

2.4.2: Page Titled

Result: Passed

Observations:

Descriptive titles used on all webpages.

+
2.4.3: Focus Order

Result: Passed

Observations:

Keyboard tab indices set for logical navigation around pages.

+
2.4.4: Link Purpose (In Context)

Result: Passed

Observations:

Descriptive aria-label provided for all text hyperlinks.

+
2.4.5: Multiple Ways

Result: Passed

Observations:

Navigation on all webpages and company logo links to home page.

+
2.4.6: Headings and Labels

Result: Passed

2.4.7: Focus Visible

Result: Passed

2.4.11: Focus Not Obscured (Minimum)

Result: Passed

2.5 Input Modalities
Success Criterion Result Observations
2.5.1: Pointer Gestures

Result: Not present

2.5.2: Pointer Cancellation

Result: Passed

Observations:

Up event used to trigger events across website.

+
2.5.3: Label in Name

Result: Passed

Observations:

Input label for attributes used to associate with input elements. +Aria-label attributes used for text hyperlinks.

+
2.5.4: Motion Actuation

Result: Not present

2.5.7: Dragging Movements

Result: Not present

2.5.8: Target Size (Minimum)

Result: Passed

Observations:

Minimum control dimension is 27 CSS pixels.

+

3 Understandable

3.1 Readable
Success Criterion Result Observations
3.1.1: Language of Page

Result: Passed

Observations:

English - Great Britain on all pages.

+
3.1.2: Language of Parts

Result: Passed

Observations:

No language changes across website.

+
3.2 Predictable
Success Criterion Result Observations
3.2.1: On Focus

Result: Passed

Observations:

No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.

+
3.2.2: On Input

Result: Passed

Observations:

No change of context initiated except by button click to navigate to submit a form and/or navigate to a new page.

+
3.2.3: Consistent Navigation

Result: Passed

Observations:

Navigation component and mechanisms shared across all pages.

+
3.2.4: Consistent Identification

Result: Passed

Observations:

Classes and CSS styles used to group collections of elements by purpose and functionality.

+
3.2.6: Consistent Help

Result: Passed

Observations:

Contact us button provided in a consistent format.

+
3.3 Input Assistance
Success Criterion Result Observations
3.3.1: Error Identification

Result: Passed

Observations:

Each input element has an associated error display label which are triggered when input validation is not met.

+
3.3.2: Labels or Instructions

Result: Passed

Observations:

Each user input has a descriptive label.

+
3.3.3: Error Suggestion

Result: Passed

Observations:

Text description of incomplete required fields is provided. Description of what caused error is issued when it involves simple regular expression validation.

+
3.3.4: Error Prevention (Legal, Financial, Data)

Result: Not present

3.3.7: Redundant Entry

Result: Passed

Observations:

No redundant data entry required. User's browser cache stores form data for rapid re-entry on crash or any other occasion.

+
3.3.8: Accessible Authentication (Minimum)

Result: Passed

Observations:

Alternative sign in methods provided, including recovery by code by phone and email.

+

4 Robust

4.1 Compatible
Success Criterion Result Observations
4.1.2: Name, Role, Value

Result: Passed

Observations:

Aria-label attribute maintained for all images and text hyperlinks. Label for attribute used to associate input elements with descriptive labels.

+
4.1.3: Status Messages

Result: Passed

Observations:

Success feedback provided on form submission, with descriptive errors on failure.

+

Sample of Audited Web Pages

  1. Home - https://www.partsltd.co.uk/
  2. Contact us - https://www.partsltd.co.uk/contact
  3. Services - https://www.partsltd.co.uk/services
  4. -

Web Technology

HTML,CSS,JavaScript,python Flask

Recording of Evaluation Specifics

Not provided

\ No newline at end of file diff --git a/templates/_page_accessibility_statement.html b/templates/_page_accessibility_statement.html new file mode 100644 index 00000000..92c4347f --- /dev/null +++ b/templates/_page_accessibility_statement.html @@ -0,0 +1,211 @@ +{% extends 'layout.html' %} + +{% block title %}{{ model.title }}{% endblock %} + +{% block page_body %} + + + + +
+

Accessibility statement for {{ model.NAME_COMPANY }}

+ +

This accessibility statement applies to the public website of {{ model.NAME_COMPANY }}.

+ +

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: +

    +
  • zoom in up to 400% without the text spilling off the screen
  • +
  • navigate most of the website using just a keyboard or speech recognition software
  • +
  • listen to most of the website using a screen reader (including the most recent versions of NVDA and TalkBack)
  • +
  • We've also made the website text as simple as possible to understand.
  • + + AbilityNet has advice on making your device easier to use if you have a disability. +

    + +
    How accessible this website is
    + +

    We know some parts of this website are not fully accessible: +

      +
    • you cannot modify the line height or spacing of text
    • +
    +

    + +
    Feedback and contact information
    +

    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. + + +

    + +
    Enforcement procedure
    +

    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 Equality Advisory and Support Service (EASS). +

    + + + +

    Technical information about this website’s accessibility

    + +

    {{ 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. +

    + +
    Compliance status
    +

    The website has been tested against the Web Content Accessibility Guidelines (WCAG) 2.2 AA standard. +

    + +

    + + (a) This website is fully compliant with the Web Content Accessibility Guidelines version 2.2 AA standard. + +

    + + + + + +

    Preparation of this accessibility statement

    + +

    This statement was prepared on 30/04/2024. It was last reviewed on 30/04/2024. + + This website was last tested on 30/04/2024 against the WCAG 2.2 AA standard. +

    + +

    The test was carried out by {{ 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.

    + +

    You can read the full accessibility test report {{ url_for('accessibility_report') }}.

    +
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/_page_contact.html b/templates/_page_contact.html index cf0dfe24..9f553130 100644 --- a/templates/_page_contact.html +++ b/templates/_page_contact.html @@ -55,8 +55,8 @@ {% endfor %}
- {{ 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 %}

{{ error }}

{% endfor %} @@ -75,7 +75,7 @@

Where to find us

-

edward.middletonsmith@gmail.com

+

{{ model.app.MAIL_CONTACT_PUBLIC }}

LinkedIn diff --git a/templates/_page_home.html b/templates/_page_home.html index 661d483a..c9344b89 100644 --- a/templates/_page_home.html +++ b/templates/_page_home.html @@ -11,7 +11,8 @@

Your software engineering solution

- + {% set block_id = 'button_get_in_touch' %} + {% include '_shared.html' %}
diff --git a/templates/_page_privacy_notice.html b/templates/_page_privacy_notice.html new file mode 100644 index 00000000..ac6fe609 --- /dev/null +++ b/templates/_page_privacy_notice.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% block title %}{{ model.title }}{% endblock %} + +{% block page_body %} + + + + +
+

Generated privacy notice - general business

+
Precision and Research Technology Systems Ltd customer privacy notice
+

This privacy notice tells you what to expect us to do with your personal information.

+
Our contact details
+

Email

+

teddy@partsltd.co.uk

+
What information we collect, use, and why
+

We collect or use the following information to provide services and goods, including delivery:

+
    +
  • Names and contact details
  • +
+ +
Lawful bases
+

Our lawful bases for collecting or using personal information to provide services and goods are:

+
    +
  • Legitimate interest:
  • +
      +
    • + 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. +
    • +
    +
+ +
Where we get personal information from
+
    +
  • People directly
  • +
+ +
How long we keep information
+

For information on how long we keep personal information, see our retention schedule at https://www.partsltd.co.uk/retention-schedule

+ +
Who we share information with
+

Data processors

+

Zume

+

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.

+ +
Sharing information outside the UK
+

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.

+

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.

+ +
Your data protection rights
+

Under data protection law, you have rights including:

+
    +
  • Your right of access - You have the right to ask us for copies of your personal data.
  • +
  • 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.
  • +
  • Your right to erasure - You have the right to ask us to erase your personal data in certain circumstances.
  • +
  • Your right to restriction of processing - You have the right to ask us to restrict the processing of your personal data in certain circumstances.
  • +
  • Your right to object to processing - You have the right to object to the processing of your personal data in certain circumstances.
  • +
  • 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.
  • +
  • Your right to withdraw consent – When we use consent as our lawful basis you have the right to withdraw your consent.
  • +
+

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.

+

To make a data protection rights request, please contact us using the contact details at the top of this privacy notice.

+ +
How to complain
+

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.

+

If you remain unhappy with how we’ve used your data after raising a complaint with us, you can also complain to the ICO.

+ +
The ICO’s address:
+

Information Commissioner’s Office

+

Wycliffe House

+

Water Lane

+

Wilmslow

+

Cheshire

+

SK9 5AF

+

Helpline number: 0303 123 1113

+

Website: https://www.ico.org.uk/make-a-complaint

+

Last updated

+

1 May 2024

+
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/_page_retention_schedule.html b/templates/_page_retention_schedule.html new file mode 100644 index 00000000..b1090a92 --- /dev/null +++ b/templates/_page_retention_schedule.html @@ -0,0 +1,1959 @@ +{% extends 'layout.html' %} + +{% block title %}{{ model.title }}{% endblock %} + +{% block page_body %} + + + + +
+

Retention periods for {{ model.NAME_COMPANY }}

+

Accounting records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Bank account recordsCheques and associated recordsCheque book / butts for all accounts2 years
Cancelled cheques 2 years
Dishonoured cheques2 years
Fresh cheques6 years
Paid / presented cheques 6 years
Stoppage of cheque payment notices2 years
Record of cheques opened books2 years
Cheque registers 2 years
Record of cheques drawn for payment6 years
Bank depositsBank deposit books / slips / butts2 years
Bank deposit summary sheets + Summaries of daily banking + Cheques schedules + 2 years
Register of cheques lodged for collection2 years
Bank reconciliationsReconciliation files / sheets 2 years
Daily list of paid cheques2 years
Unpaid cheque records2 years
Bank statementsBank statements, periodic reconciliations2 years
Bank certificates of balance2 years
Electronic banking and electronic funds transferCash transactions + Payment instructions + Deposits and/or withdrawals + Disposal action in line wiht paper records
Audit trailsRetain for the same period as the base transaction record
Expenditure recordsCash books / sheetsExpenditure sheets6 years
Cash books / sheets6 years
Petty cash recordsPetty cash records / books / sheets 2 years
Petty cash receipts2 years
Postal cash book/sheets + Postage/courier account/cash records + Register of postage expenditure + Postage paid record + Postage books sheets + 2 years
Summary cash books 2 years
CreditorsCreditors’ history records + Lists/reports + 6 years
StatementsStatements of accounts outstanding + Outstanding orders + 2 years
Statements of accounts – rendered / payable2 years
Subsidiary records Copies of extracts and expenditure dissections1 year
Credit note books2 years
Credit notes2 years
Debit note books2 years
VouchersVouchers – claims for payment, purchase orders, requisition for goods and services, accounts payable, invoices and so on6 years
Wages / salaries vouchers6 years
Copies of vouchers1 year
Voucher registers2 years
Voucher registration cards and payment cards6 years
Voucher summaries1 year
Advice/schedule of vouchers despatched + Delivery advice1 year
Costing recordsCost cards 2 years
Costing records, dissection sheets and so on2 years
Ledger recordsGeneral and subsidiary ledgersGeneral and subsidiary ledgers produced for the purposes of preparing certified financial statements or published information6 years
Cash books / sheets6 years
Other ledgers (such as contracts, costs, purchases)2 years
Related journalsAudit sheets – ledger posting2 years
JournalsJournals – prime records for the raising of charges6 years
Journals – routine adjustments2 years
Trial balances and reconciliations/td> + Year-end balances, reconciliations and variations to support ledger balances and published accounts6 years
Receipts and revenue recordsBooks / buttsReceipt books/butts + Office copies of receipts – cashiers’, cash + register, fines and costs, sale of + publications, general receipt books/butts/ + records6 years
Postal remittance books/records6 years
Receipt books/records for imposts (such as stamp duty, VAT receipt books)6 years
Irregular remittance books2 years
Cash registersCopies of forms6 years
Reconciliation sheets6 years
Audit rolls2 years
Summaries / analysis records2 years
Reading books / sheets2 years
Cashiers’ recordsHandover books2 years
Revenue records Revenue cash books / sheets / records + Receipt cash books / sheets6 years
Daily revenue dissections 1 year
Debtors’ records and invoicesPeriodical revenue dissections1 year
Copies of invoices/debit notes rendered on debtors (such as invoices paid/unpaid, registers of invoices, debtors ledgers)6 years
Source documents/records used for raising of invoices/debit notes6 years
Debits and refundsCopies of invoices and copies of source documents2 years
Records relating to unrecoverable revenue, debts and overpayments (such as register of debts written off, register of refunds)6 years
Salaries and related recordsSalary recordsEmployee pay histories + Note that the last three years’ records must be kept for leavers, in either the personnel or finance records system, for the calculation of pension entitlement6 years
Salary rates registerWhen superceded
Salary ledger card/records6 years
Copies of salaries/wages payroll sheets2 years
Stores recordsStores recordsGoods inwards books/records6 years
Delivery dockets 2 years
Stock / stores control cards / sheets / records2 years
Stock / stores issue registers / records2 years
Stocktaking sheets/records, including inventories, stock reconciliations, stock take reports2 years
Purchase order recordsPurchase order books / records6 years
Railway / courier consignment books / records2 years
Travel warrants2 years
Requisition recordsRequisition records2 years
Other accountable financial recordsAsset registersAssets / equipment registers / recordsSix years after asset or last one in the register is disposed of
Depreciation registersRecords relating to the calculation of annual depreciationSix years after asset or last one in the register is disposed of
Financial statementsStatements/summaries prepared for inclusion in quarterly/annual reports6 years
Periodic financial statements prepared for management on a regular basisDestroy when cumulated into quarterly/annual reports
Ad hoc statements1 year
+ +

Central expenditure records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Estimates records (including revised and supplementary) where detailed justification is provided and which are submitted to the Treasury6 years
Estimates submissions from regional or local offices2 years
Calculations and costings for annual estimates2 years
Expenditure scrutinies2 years
Records relating to bids from the Civil Contingencies Fund 6 years
Spending reviewsOne year after the cycle to which the records relate
Records relating to dealings with the + Public Accounts Committee and the Select + Committee on Expenditure6 years
Expenditure and revenue returnsOne year after the year to which the returns relate
Financial statements prepared for annual reportsOne year after publication of the report
Financial statements prepared for managementOne year after completion of annual financial report
Grant funding records Six years after action completed/grant made
Financial authorities or delegationsSix years after authority or delegation is superseded
Policy and strategy records (including investment policy) Second review
Treasury Directives and Circulars Until superseded
Asset registersSix years after item / asset is disposed of
Land registers Twelve years after disposal
Depreciation recordsSix years after item / asset is disposed of
Audit investigations (external) Six years after action completed
Financial records relating to capital works projectsSix years after action/project is completed
Investment recordsTwo years after investments are liquidated or matured
Stocktaking records2 years after audit
Unclaimed monies records6 years after audit
Records relating to serious matters of: +
    +
  • theft
  • +
  • fraud
  • +
  • misappropriation
  • +
  • irrecoverable debts and overpayments
  • +
  • write-offs
  • +
  • recovery of debt
  • +
  • wavering of debt
  • +
+ (where external action has been taken) +
Ten years after action / investigation is completed
Records relating to minor matters of: +
    +
  • theft
  • +
  • fraud
  • +
  • misappropriation
  • +
  • irrecoverable debts and overpayments
  • +
  • write-offs
  • +
  • recovery of debt
  • +
  • wavering of debt
  • +
+ (where matter was resolved internally) +
Six years after audit
Procedure manuals 2 years after superceded
+ +

Comlaints records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Policy statementsWhen superseded
System handbook / guideWhen superseded
Minutes of meetings of Complaints Committee, Service Standards Team and others10 years
Surveys3 years
Case recordsEnquiries3 years
Investigations10 years
Statistical reports5 years
Reports on particular complaints or on categories of complaints3 years
PrecedentsReview after 10 years
Register of complaints10 years
ReviewsCorrespondence and papers10 years
Reports3 years
+ +

Contractual records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Policy mattersPolicy on contracts, normally contained in a separate registered file seriesFirst and second review
Initial proposalEnd user requirement6 years
List of approved suppliersAn active document – updated regularly
Statements of interestOne year from date of last paper
Draft specificationDestroy when specification has been agreed
Agreed specificationSix years from end of contract
Evaluation criteriaSix years from end of contract
Invitation to tenderSix years from end of contract
TenderingUnsuccessful tender documentOne year after date of last paper
Successful tender documentSix years from award of contract
Background information supplied by departmentOne year after date of last paper
Interview panel – report and notes of proceedingsOne year from end of contract
Commissioning letterOne year from end of contract
Signed contract6 years from end of contract
Contract operation and monitoringReports from contractors2 years from end of contract
Schedules of works2 years from end of contract
Bills of quantity (building contracts)16 years
Surveys and inspections + a. equipment and supplies + b. buildingsa. Two years from date of last + paper + b. Second review
Records of complaints6 years from end of contract
Disputes over payment6 years from end of contract
Final accounts6 years from end of contract
Minutes and papers of meetingsSecond review
Amendments to contractsChanges to requirements6 years from end of contract
Forms of variation6 years from end of contract
Extensions to contract6 years from end of contract
+ +

Employee personnel records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Employment and careerWritten particulars of employment + Contracts of employment, including theCertificate of Qualification or its equivalent and including the Senior Civil Service + Changes to terms and conditions, including change of hours lettersUntil age 100
Job History - consolidated record of whole career and location details (paper or electronic)Until age 100
Current address detailsSix years after employment has ended
Record of location of overseas serviceUntil age 100
Variation of hours - calculation formula for individualDestroy after use
Promotion, temporary promotion and / or substitution documentation Destroy after summary noted
Working Time Directive opt out formsThree years after the opt-out has been rescinded or has ceased to apply
Record of previous service datesUntil age 100
Previous service supporting papersDestroy after records noted as appropriate
Qualifications/references6 years
Transfer documents (OGD E18) Destroy after summary noted and actioned
Annual/Assessment reports or summary of performance marks where an open reporting system operates5 years
Annual/Assessment reports for the last five years of service or summary of performance marks where an open reporting system operatesUntil age 72
Training history6 years
Travel and subsistence - claims and authorisation6 years
Employment and careerAnnual leave records (dependent on departmental practice)2 years
Job applications – internal1 year
Recruitment, appointment and/or promotion board selection papers1 year
Building society references6 months
HealthHealth DeclarationUntil age 100
Health referrals, including: + Medical reports from doctors and consultants + Correspondence with the appointed medical + adviser to the PCSPS (currently BMI Health + Services and, previous to that body, the + Occupational Health and Safety Agency Ltd, + the Civil Service Occupational Health Service + or the Medical Advisory Service (MAS)) + Until age 100
Papers relating to any injury on dutyUntil age 100
Medical reports of those exposed to a + substance hazardous to health, including: + Lead (Control of Lead at Work Regulations + 1980) 40 years from date at which entry was + made + Asbestos (Control of Asbestos at Work + Regulations 1996) 40 years after last record + Compressed Air (Work in Compressed Air + Regulations 1996) 40 years from date of last + entry + Radiation (Ionising Radiation Regulations + 1985)50 years from date of last entry
Medical/Self Certificates – unrelated to industrial injury4 years
PersonalWelfare papersDestroy after minimum of six years after last + action
SecuritySecurity personnel files Five years after leaving (if at normal retirement age) or ten years after leaving (if before normal retirement age)
Pay and pensionBank details - current Six years after employment has ended
Death Benefit Nomination and Revocation + Forms Until age 100
Death certificatesReturn original to provider + Retain copy until age 100
Decree AbsolutesReturn original to provider + Retain copy until age 100
Housing advanceUntil age 100
Marriage certificate and documentation relating to civil registrationReturn original to provider + Retain copy until age 100
Unpaid leave periods (such as maternity leave)Until age 100
Statutory maternity pay documents6 years
Other maternity pay documentation18 months
Overpayment documentation6 years after repayment or write-off
Personal payroll history, including record of + pay, performance pay, overtime pay, + allowances, pay enhancements, other taxable + allowances, payment for untaken leave, + reduced pay, no pay, maternity leaveUntil age 100
Pensions estimates and awardsUntil age 100
Record of: + Full name and date of birth + National Insurance number + Pensionable pay at leaving + Reckonable service for pension purposes (and + actual service where this is different, together + with reasons for the difference) + Reason for leaving and new employer’s name + (where known) + Amount and destination of any transfer value + paid + Amount of any refund of PCSPS contributions + Amount and date of any Contributions + Equivalent Premium paid + All other papers relating to pensionability not + listed above (such as papers about + pensionability of other employment (including + war service); extension of service papers; + papers about widow’s, widower’s, children’s + and other dependant’s pensions; + correspondence with the Cabinet Office, other + departments and pension administrators, or the + officer and his/her representatives (MPs, + unions or others) about pension mattersUntil age 100
Resignation, termination and/or retirement + lettersUntil age 100
Added yearsUntil age 100
Additional Voluntary Contributions (AVC)Until age 100
Payroll input forms6 years
Bonus nominations6 years
Complete sick absence record showing dates and causes of sick leaveUntil age 72
Statutory Sick Pay (SSP) formsFor last 4 to 6 years
Papers relating to disciplinary action which has resulted in any changes to terms and conditions of service, salary, performance pay or allowancesUntil age 72
Authorisation for deputising, substitution allowance and/or overtime/travel time claim6 years
Advances for: + Season tickets + Car parking + Bicycles + Christmas/holidays + Housing6 years after repayment
+ + + +

Employee personnel records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
ReportsAudit reports (including interim), where these have included the examination of long-term contracts6 years
Report papers used in the course of a fraud investigation6 years after legal proceedings have been completed
Other audit reports (including interim)3 years
UndertakingsTerms of reference3 years
Programmes / plans / strategiesOne year after the last date of the plan
Correspondence3 years
Record keepingMinutes of meetings and related papers, including those of the Audit Committee3 years
Working papers3 years
Other recordsInternal Audit guidesWhen superseded
Manuals and guides relating to departmental proceduresWhen superseded
Local auditing standardsWhen superseded
DisposalAnnual reports to Accounting Officers3 years
+ +

Project records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Project proposalsapproved10 years after completion of project
rejected or deferred5 years after completion of project
Project Initiation Documents (PIDs)PIDs and supporting documentation (including business cases)Small projects: + 10 years after completion of project + Major projects: + 25 years after completion of project +
Feasibility studiesreportsSmall projects: + 10 years after issue + Major projects: + 25 years after completion of project +
-draft reportsSmall projects: + 2 years after last paper + Major projects: + 25 years after completion of project +
working papersSmall projects: + 2 years after last paper + Major projects: + 25 years after completion of project +
correspondenceSmall projects: + 2 years after last paper + Major projects: + 25 years after completion of project +
Plans and specifications (such as statements of requirements, operational requirements, technical plans, resource plans)provisional / proposed5 years after completion of project
final10 years after completion of project
variations10 years after completion of project
Contracts and agreementscontracts under sealSee 'Contractual records' above.
other contractsSee 'Contractual records' above.
title deedsSee 'Contractual records' above.
correspondenceSee 'Contractual records' above.
Contractorsapproved nominations1 year after issue
rejected nominations1 year after issue
approved listwhen new list is issued
removals/suspensions6 years after the end of the project
Tender boardsrecord sets of papersat the end of the project
other copies1 year after date of last paper
working papers2 years after date of last paper
minutes of meetings5 years after date of last paper
Maps, plans, drawings and photographsmaster setSMall projects: + 10 years after completion of project + Major projects: + 25 years after completion of project
working copiesSmall projects: + at the end of the project + Major projects: + 25 years after completion of project
other copiesSmall projects: + 5 years after date of last paper + Major projects: + 25 years after completion of project
Financial documentsFinancial documents (including + investment appraisals)6 years after completion of project
Equipment and suppliesEquipment and supplies6 years after completion of project
Land recordsallocationwhen land released for other purposes
procurement / disposal12 years after date of disposal
Human resourcesSee 'Personal records' above.
Project boards, Assessment meetings, etc.minutesSmall projects: + 5 years after date of last paper + Major projects: + 25 years after completion of project
correspondenceSmall projects: + 5 years after date of last paper + Major projects: + 25 years after completion of project
Reports (such as stage assessments, quality reviews, highlight reports, GANTT charts)interim5 years after issue
final25 years after completion of project
evaluation25 years after completion of project
draftat the end of the project
Product descriptions5 years after completion of project
Project operating manuals5 years after completion of project
Miscellaneous recordsCopies of documentation from other projects; information on products, equipment or machinery; training courses; correspondence2 years after completion of project
+ +

Inquiry records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
InquiriesEvidence of steps taken to secure the attendance of witnesses7 years from the final report
Schedules of witnesses7 years from the final report
Research gathered by the inquiry team 7 years from the final report
Financial records7 years from the final report
Cost solicitor’s records7 years from the final report
Parliamentary questions7 years from the final report
All other data related to the inquiryPermanent
+ +

Public inquiry records

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CategoryRecord typeDescription of UseRetention period
Customer dataEnquiries6 years from end of contract
Contracted servicesInitial proposal6 years from end of contract
Signed contracts and ammendments6 years from end of contract
Reports from third party services contracted6 years from end of contract
+
+ + + + + +{% endblock %} + diff --git a/templates/_shared.html b/templates/_shared.html new file mode 100644 index 00000000..7a0c5fb6 --- /dev/null +++ b/templates/_shared.html @@ -0,0 +1,4 @@ + +{% if block_id == 'button_get_in_touch' %} + +{% endif %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index fa327ddb..c511fb40 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -1,5 +1,5 @@ - + @@ -61,13 +61,13 @@
- +

Precision And Research Technology Systems

- +
@@ -140,7 +140,7 @@ @@ -162,3 +162,4 @@ hookupShared(); }); +