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 70fdd409..e50d830d 100644 Binary files a/__pycache__/app.cpython-311.pyc and b/__pycache__/app.cpython-311.pyc differ diff --git a/__pycache__/config.cpython-311.pyc b/__pycache__/config.cpython-311.pyc index fba12cd8..e669f86a 100644 Binary files a/__pycache__/config.cpython-311.pyc and b/__pycache__/config.cpython-311.pyc differ diff --git a/__pycache__/forms.cpython-311.pyc b/__pycache__/forms.cpython-311.pyc index 9d042520..27d38a73 100644 Binary files a/__pycache__/forms.cpython-311.pyc and b/__pycache__/forms.cpython-311.pyc differ 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 909a08d8..c98cc42e 100644 Binary files a/business_objects/__pycache__/__init__.cpython-311.pyc and b/business_objects/__pycache__/__init__.cpython-311.pyc differ diff --git a/business_objects/__pycache__/basket.cpython-311.pyc b/business_objects/__pycache__/basket.cpython-311.pyc index 60cc1273..c6901b7b 100644 Binary files a/business_objects/__pycache__/basket.cpython-311.pyc and b/business_objects/__pycache__/basket.cpython-311.pyc differ diff --git a/business_objects/__pycache__/category.cpython-311.pyc b/business_objects/__pycache__/category.cpython-311.pyc index ac01a405..9767b109 100644 Binary files a/business_objects/__pycache__/category.cpython-311.pyc and b/business_objects/__pycache__/category.cpython-311.pyc differ diff --git a/business_objects/__pycache__/currency.cpython-311.pyc b/business_objects/__pycache__/currency.cpython-311.pyc index 6fca61c1..8908a0d6 100644 Binary files a/business_objects/__pycache__/currency.cpython-311.pyc and b/business_objects/__pycache__/currency.cpython-311.pyc differ diff --git a/business_objects/__pycache__/delivery_option.cpython-311.pyc b/business_objects/__pycache__/delivery_option.cpython-311.pyc index c53d6d1e..ee996e8d 100644 Binary files a/business_objects/__pycache__/delivery_option.cpython-311.pyc and b/business_objects/__pycache__/delivery_option.cpython-311.pyc differ diff --git a/business_objects/__pycache__/delivery_region.cpython-311.pyc b/business_objects/__pycache__/delivery_region.cpython-311.pyc index e450138c..c0f6e2b4 100644 Binary files a/business_objects/__pycache__/delivery_region.cpython-311.pyc and b/business_objects/__pycache__/delivery_region.cpython-311.pyc differ diff --git a/business_objects/__pycache__/discount.cpython-311.pyc b/business_objects/__pycache__/discount.cpython-311.pyc index 17de0115..f581f319 100644 Binary files a/business_objects/__pycache__/discount.cpython-311.pyc and b/business_objects/__pycache__/discount.cpython-311.pyc differ diff --git a/business_objects/__pycache__/image.cpython-311.pyc b/business_objects/__pycache__/image.cpython-311.pyc index c7a58709..c04cd308 100644 Binary files a/business_objects/__pycache__/image.cpython-311.pyc and b/business_objects/__pycache__/image.cpython-311.pyc differ diff --git a/business_objects/__pycache__/order.cpython-311.pyc b/business_objects/__pycache__/order.cpython-311.pyc index b5dd3130..aebfe3f3 100644 Binary files a/business_objects/__pycache__/order.cpython-311.pyc and b/business_objects/__pycache__/order.cpython-311.pyc differ diff --git a/business_objects/__pycache__/product.cpython-311.pyc b/business_objects/__pycache__/product.cpython-311.pyc index fd572b5f..547dcc04 100644 Binary files a/business_objects/__pycache__/product.cpython-311.pyc and b/business_objects/__pycache__/product.cpython-311.pyc differ diff --git a/business_objects/__pycache__/sql_error.cpython-311.pyc b/business_objects/__pycache__/sql_error.cpython-311.pyc index bcddb337..98bcccb1 100644 Binary files a/business_objects/__pycache__/sql_error.cpython-311.pyc and b/business_objects/__pycache__/sql_error.cpython-311.pyc differ diff --git a/business_objects/__pycache__/variation.cpython-311.pyc b/business_objects/__pycache__/variation.cpython-311.pyc index 913ca769..99b191c2 100644 Binary files a/business_objects/__pycache__/variation.cpython-311.pyc and b/business_objects/__pycache__/variation.cpython-311.pyc differ diff --git a/config.py b/config.py index d37c8b3e..b4c3853e 100644 --- a/config.py +++ b/config.py @@ -40,10 +40,10 @@ class Config: MAIL_SERVER = 'smtp.gmail.com' MAIL_PORT = 587 MAIL_USE_TLS = True - MAIL_USERNAME = 'edward.middletonsmith@gmail.com' + MAIL_USERNAME = os.getenv('MAIL_DEFAULT_SENDER') MAIL_PASSWORD = os.getenv('MAIL_PASSWORD') - MAIL_DEFAULT_SENDER = 'edward.middletonsmith@gmail.com' - + MAIL_DEFAULT_SENDER = os.getenv('MAIL_DEFAULT_SENDER') + MAIL_CONTACT_PUBLIC = os.getenv('MAIL_CONTACT_PUBLIC') RECAPTCHA_PUBLIC_KEY = os.getenv('RECAPTCHA_PUBLIC_KEY') RECAPTCHA_PRIVATE_KEY = os.getenv('RECAPTCHA_PRIVATE_KEY') diff --git a/datastores/__pycache__/__init__.cpython-311.pyc b/datastores/__pycache__/__init__.cpython-311.pyc index 47f3190e..9022d72f 100644 Binary files a/datastores/__pycache__/__init__.cpython-311.pyc and b/datastores/__pycache__/__init__.cpython-311.pyc differ diff --git a/datastores/__pycache__/datastore_store.cpython-311.pyc b/datastores/__pycache__/datastore_store.cpython-311.pyc index fb281c5e..bd7618ed 100644 Binary files a/datastores/__pycache__/datastore_store.cpython-311.pyc and b/datastores/__pycache__/datastore_store.cpython-311.pyc differ 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 781787b4..6733d0f1 100644 Binary files a/lib/__pycache__/__init__.cpython-311.pyc and b/lib/__pycache__/__init__.cpython-311.pyc differ diff --git a/lib/__pycache__/argument_validation.cpython-311.pyc b/lib/__pycache__/argument_validation.cpython-311.pyc index bccb53bf..4f6636f0 100644 Binary files a/lib/__pycache__/argument_validation.cpython-311.pyc and b/lib/__pycache__/argument_validation.cpython-311.pyc differ diff --git a/lib/__pycache__/data_types.cpython-311.pyc b/lib/__pycache__/data_types.cpython-311.pyc index 74a56733..9ac28162 100644 Binary files a/lib/__pycache__/data_types.cpython-311.pyc and b/lib/__pycache__/data_types.cpython-311.pyc differ diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc index e89d6f77..53ba36d8 100644 Binary files a/models/__pycache__/__init__.cpython-311.pyc and b/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/models/__pycache__/model_view_base.cpython-311.pyc b/models/__pycache__/model_view_base.cpython-311.pyc index edd6c758..49379f52 100644 Binary files a/models/__pycache__/model_view_base.cpython-311.pyc and b/models/__pycache__/model_view_base.cpython-311.pyc differ diff --git a/models/__pycache__/model_view_contact.cpython-311.pyc b/models/__pycache__/model_view_contact.cpython-311.pyc index 18dce3c4..9a00e87d 100644 Binary files a/models/__pycache__/model_view_contact.cpython-311.pyc and b/models/__pycache__/model_view_contact.cpython-311.pyc differ diff --git a/models/__pycache__/model_view_home.cpython-311.pyc b/models/__pycache__/model_view_home.cpython-311.pyc index 8c1c4e36..109a1054 100644 Binary files a/models/__pycache__/model_view_home.cpython-311.pyc and b/models/__pycache__/model_view_home.cpython-311.pyc differ diff --git a/models/model_view_base.py b/models/model_view_base.py index 2fd1556e..a8162c69 100644 --- a/models/model_view_base.py +++ b/models/model_view_base.py @@ -57,6 +57,7 @@ class Model_View_Base(ABC): FLAG_SCROLLABLE = 'scrollable' FLAG_SUBMITTED = 'submitted' # flagIsDatePicker = 'is-date-picker' + HASH_PAGE_ACCESSIBILITY_STATEMENT = '/accessibility-statement' HASH_PAGE_CONTACT = '/contact' HASH_PAGE_ERROR_NO_PERMISSION = '/error' HASH_PAGE_HOME = '/' @@ -78,6 +79,7 @@ class Model_View_Base(ABC): ID_NAV_STORE_PRODUCT = 'navStoreProduct' ID_OVERLAY_HAMBURGER = 'overlayHamburger' ID_PAGE_BODY = 'pageBody' + NAME_COMPANY = 'Precision And Research Technology Systems Limited' URL_HOST = 'https://www.partsltd.co.uk' # 'http://127.0.0.1:5000' URL_GITHUB = 'https://github.com/Teddy-1024' URL_LINKEDIN = 'https://uk.linkedin.com/in/lordteddyms' diff --git a/passenger_wsgi.py b/passenger_wsgi.py index f0f4120a..5e871cd0 100644 --- a/passenger_wsgi.py +++ b/passenger_wsgi.py @@ -12,4 +12,4 @@ def application(environ, start_response): response = '\n'.join([message, version]) return [response.encode()] -from app2 import app as application # Import the Flask application from app.py \ No newline at end of file +from app import app as application # Import the Flask application from app.py \ No newline at end of file diff --git a/static/css/shared.css b/static/css/shared.css index 40c7c632..c502775a 100644 --- a/static/css/shared.css +++ b/static/css/shared.css @@ -464,7 +464,7 @@ button, .btn-submit, input[type="submit"] { color: var(--c_purple_dark); font-weight: bold; font-size: 18px; - height: 27px; + height: 18px; } .hamburger > :hover { background-color: var(--c_purple_light); @@ -472,8 +472,14 @@ button, .btn-submit, input[type="submit"] { } .hamburger > * > * { width: 100%; + /* margin-top: 4.5px; margin-bottom: 4.5px; + */ +} +.hamburger > .container { + padding-top: 4.5px; + padding-bottom: 4.5px; } li { 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 00000000..0e81a38c Binary files /dev/null and b/static/docs/generated-privacy-notice-general-business.docx differ 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 00000000..cafb0af2 Binary files /dev/null and b/static/docs/generated-privacy-notice-general-business.odt differ 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(); }); +