From e495a6a3a311fc2a01ab21856dfcc74845a6d68c Mon Sep 17 00:00:00 2001 From: b4-sharp <Bradley2.Sharp@live.uwe.ac.uk> Date: Mon, 24 Apr 2023 11:40:03 +0100 Subject: [PATCH] Bootstrap checkout page --- store/forms.py | 57 +++++-- store/routes.py | 90 +--------- store/static/_main.css | 159 ------------------ store/templates/basket.html | 63 +------ store/templates/checkout.html | 143 +++++----------- store/templates/items.html | 46 ----- .../templates/userContent/ChangePhNumber.html | 1 - .../templates/userContent/ChangeUsername.html | 2 - store/templates/userContent/login.html | 25 --- store/templates/userContent/register.html | 1 - .../userContent/verify_code_security1.html | 15 -- .../verify_code_security1_confirm.html | 20 --- store/utility.py | 27 +++ 13 files changed, 116 insertions(+), 533 deletions(-) delete mode 100644 store/templates/userContent/verify_code_security1.html delete mode 100644 store/templates/userContent/verify_code_security1_confirm.html diff --git a/store/forms.py b/store/forms.py index 164439f..5c1902d 100644 --- a/store/forms.py +++ b/store/forms.py @@ -10,28 +10,21 @@ from wtforms import ( EmailField, TelField, DecimalField, + TextAreaField, validators, ) +import datetime +from store.utility import validate_address, validate_cvv_2 class RegistrationForm(FlaskForm): -<<<<<<< HEAD username = StringField( "Username", [validators.DataRequired(), validators.Length(max=256)] ) email = EmailField( "Email Address", [validators.DataRequired(), validators.Length(max=256)] ) - securityQ1 = StringField( - "Enter your Favourite Colour", - [validators.DataRequired(), validators.Length(max=30)], - ) phone_number = TelField("Phone Number", [validators.DataRequired()]) -======= - username = StringField("Username", [validators.Length(min=4, max=255)]) - email = StringField("Email Address", [validators.Length(min=6, max=255)]) - phone_number = StringField("Phone Number", [validators.Length(min=6, max=35)]) ->>>>>>> master password = PasswordField( "New Password", [ @@ -51,14 +44,9 @@ class RegistrationForm(FlaskForm): class LoginForm(FlaskForm): -<<<<<<< HEAD username = StringField("Username") password = PasswordField("Password") securityQ1 = StringField("Enter your Favourite Colour", [validators.Length(max=30)]) -======= - username = StringField("Username", [validators.Length(min=4, max=255)]) - password = PasswordField("Password", [validators.Length(min=6, max=220)]) ->>>>>>> master submit = SubmitField("Login", render_kw={"class": "button"}) @@ -84,3 +72,42 @@ class AddForm(FlaskForm): description = StringField("Description", validators=[validators.DataRequired()]) price = DecimalField(default=0, validators=[validators.InputRequired()]) submit = SubmitField("Submit") + + +class CheckoutForm(FlaskForm): + months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + today = datetime.date.today() + year = int(today.year) + years = [year] + for year in range(year, year + 50, 1): + years.append(year) + name = StringField( + "Name on card", [validators.Length(min=2, max=30), validators.DataRequired()] + ) + card_number = StringField( + "Card Number", [validators.Length(min=15, max=16), validators.DataRequired()] + ) + exp_date_month = SelectField("Month", [validators.DataRequired()], choices=months) + exp_date_year = SelectField("Year", [validators.DataRequired()], choices=years) + cvv_2 = StringField( + "CVV2", + [validators.Length(min=3, max=4), validators.DataRequired(), validate_cvv_2], + ) + address = TextAreaField( + "Address", + [ + validators.Length(min=10, max=250), + validators.DataRequired(), + validate_address, + ], + ) + postcode = StringField( + "Postcode", + [ + validators.Length(min=6, max=10), + validators.DataRequired(), + validate_address, + ], + ) + + submit = SubmitField("Complete Payment") diff --git a/store/routes.py b/store/routes.py index cb39c22..e28e5ed 100644 --- a/store/routes.py +++ b/store/routes.py @@ -9,20 +9,6 @@ from flask import ( session, abort, ) -from flask_wtf import FlaskForm -from flask_wtf.csrf import generate_csrf -import requests -import datetime -import re -from wtforms import ( - Form, - StringField, - validators, - SelectField, - TextAreaField, - ValidationError, - HiddenField, -) import json from store.utility import * from store.forms import * @@ -499,71 +485,6 @@ def ChangePhNumber(): return render_template("userContent/ChangePhNumber.html") -def validate_cvv_2(form, field): - regex = "^[0-9]{3,4}$" - p = re.compile(regex) - if not re.search(p, field.data): - raise ValidationError("CVV2 is incorrect") - - -def validate_address(form, field): - # Uses the google maps geocoding api to check if a postcode or address exists - - url = "https://maps.googleapis.com/maps/api/geocode/json" - - api_key = "AIzaSyCz4xyIl5yaIg869orL07ye2ainw7kv5Pc" - - params = {"address": field, "key": api_key} - - response = requests.get(url, params=params).json() - - # Check if response status is "OK" and has at least one result - if response["status"] == "OK" and len(response["results"]) > 0: - print("Valid address or postcode") - pass - else: - raise ValidationError("Enter valid address") - - -class CheckoutForm(Form): - months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] - today = datetime.date.today() - year = int(today.year) - years = [year] - for year in range(year, year + 50, 1): - years.append(year) - name = StringField( - "Name on card", [validators.Length(min=2, max=30), validators.DataRequired()] - ) - card_number = StringField( - "Card Number", [validators.Length(min=15, max=16), validators.DataRequired()] - ) - exp_date_month = SelectField("Month", [validators.DataRequired()], choices=months) - exp_date_year = SelectField("Year", [validators.DataRequired()], choices=years) - cvv_2 = StringField( - "CVV2", - [validators.Length(min=3, max=4), validators.DataRequired(), validate_cvv_2], - ) - address = TextAreaField( - "Address", - [ - validators.Length(min=10, max=250), - validators.DataRequired(), - validate_address, - ], - ) - postcode = StringField( - "Postcode", - [ - validators.Length(min=6, max=10), - validators.DataRequired(), - validate_address, - ], - ) - # Cross-site request forgery token to make sure requests are sent from our site - csrf_token = HiddenField() - - @app.route("/order_summary") def order_summary(): total_price = 0 @@ -571,11 +492,10 @@ def order_summary(): if "basket" in session: address = session.get("address", "") for item_id, item in session["basket"].items(): - items.append(item) total_price = total_price + item["price"] session.clear() - + return render_template( "order_summary.html", items=items, address=address, total_price=total_price ) @@ -603,12 +523,10 @@ def checkout(): return redirect(url_for("order_summary")) except: flash("One or more items in basket have already been sold!") - csrf_token = generate_csrf() return render_template( "checkout.html", form=form, total_price=total_price, - csrf_token=csrf_token, items=items, ) @@ -639,7 +557,7 @@ def add_to_basket(): "id": item_obj.id, "description": item_obj.description, "price": item_obj.price, - "is_item_set" : False + "is_item_set": False, } if "basket" not in session: session["basket"] = {} @@ -662,8 +580,8 @@ def add_to_basket_set(): item_dict = { "id": item_obj.id, "description": item_obj.description, - "price": item_obj.price, - "is_item_set" : True + "price": item_obj.price, + "is_item_set": True, } if "basket" not in session: session["basket"] = {} diff --git a/store/static/_main.css b/store/static/_main.css index 3c7a383..1b3d821 100644 --- a/store/static/_main.css +++ b/store/static/_main.css @@ -32,167 +32,8 @@ display: flex; } -<<<<<<< HEAD /* Moves the entire line smoothly to the left. */ .carousel-inner .carousel-item-end, .carousel-inner .carousel-item-start { transform: translateX(0); -======= -@media only screen and (min-width: 768px) { - .col-1 { - width: 8.33%; - } - - .col-2 { - width: 16.66%; - } - - .col-3 { - width: 25%; - } - - .col-4 { - width: 33.33%; - } - - .col-5 { - width: 41.66%; - } - - .col-6 { - width: 50%; - } - - .col-7 { - width: 58.33%; - } - - .col-8 { - width: 66.66%; - } - - .col-9 { - width: 75%; - } - - .col-10 { - width: 83.33%; - } - - .col-11 { - width: 91.66%; - } - - .col-12 { - width: 100%; - } -} - -.content { - /* to stop content being hidden by the fixed footer */ - padding-bottom: 100px; -} - -body { - line-height: 1; -} - -ol, -ul { - list-style: none; -} - -blockquote, -q { - quotes: none; -} - -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ''; - content: none; -} - -/* table { - border-collapse: collapse; - border-spacing: 0; -} */ - - -/* End of resetting */ - - -footer { - background-color: black; - color: white; - position: fixed; - bottom: 0; - left: 0; - width: 100%; -} - - - - -.navbar li a { - color: white; - display: block; - text-align: center; - text-decoration: none; - padding: 14px 16px; -} - -.navbar ul { - list-style-type: none; - margin: 0; - padding: 0; - overflow: hidden; - background-color: black; -} - - -input[type=text] { - float: left; - padding: 6px; - border: none; - margin-top: 8px; - margin-right: 16px; - font-size: 17px; -} - -.navbar li { - float: left; -} - -.navbar li a:hover { - background-color: white; - color: black; -} - -* { - text-align: center; - font-family: sans-serif; -} - -h1 { - color: black; - font-size: 30px; -} - -.error { - text-decoration: none; - color: red; - font-size: 30px; -} - -li { - list-style-type: none; -} - -.loginTable { - margin-left: auto; - margin-right: auto; ->>>>>>> master } \ No newline at end of file diff --git a/store/templates/basket.html b/store/templates/basket.html index dc0077c..c926eab 100644 --- a/store/templates/basket.html +++ b/store/templates/basket.html @@ -1,51 +1,11 @@ {% extends "base.html" %} {% block content %} -<<<<<<< HEAD <div class="container"> <h1>Basket</h1> {% if items %} <table class="table"> <thead class="text-light" style="background-color: #000000"> -======= -<h1>Basket</h1> -<style> - table { - border-collapse: collapse; - width: 100%; - } - - th, - td { - text-align: left; - padding: 8px; - border-bottom: 1px solid #ddd; - } - - th { - background-color: #000000; - color: white; - } - - .remove-button { - background-color: #f44336; - color: white; - border: none; - padding: 8px 16px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; - margin: 4px 2px; - cursor: pointer; - } -</style> - -{% if items %} -<div> - <table> - <thead> ->>>>>>> master <tr> <th>Item description</th> <th>Item price</th> @@ -60,39 +20,26 @@ <td> <form method="POST" action="/remove_item"> <input type="hidden" name="item" value="{{ item['id'] }}"> -<<<<<<< HEAD - <input type="submit" value="Remove" class="btn btn-danger"> -======= - <input type="submit" value="Remove"> ->>>>>>> master + <input type="submit" value="Remove" class="btn btn-danger" style="width: 100px"> </form> </td> </tr> {% endfor %} </tbody> -<<<<<<< HEAD <tfoot style="background-color: #f2f2f2"> -======= - <tfoot> ->>>>>>> master <tr> <td>Total price:</td> <td>£{{ total_price }}</td> - <td></td> + <td> + <a class="btn btn-success" href="{{url_for('checkout', total_price = total_price)}}" + style="width: 100px">Checkout</a> + </td> </tr> </tfoot> </table> -<<<<<<< HEAD {% else %} <p>Your basket is empty.</p> {% endif %} </div> -======= - <a href="{{url_for('checkout', total_price = total_price)}}">Checkout</a> -</div> -{% else %} -<p>Your basket is empty.</p> -{% endif %} ->>>>>>> master {% endblock %} \ No newline at end of file diff --git a/store/templates/checkout.html b/store/templates/checkout.html index bbd426d..4d11e65 100644 --- a/store/templates/checkout.html +++ b/store/templates/checkout.html @@ -4,111 +4,44 @@ {% block content %} -<style> - h1 { - padding: 15px; - } - - .grid-container-element { - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 20px; - width: 90%; - padding: 10px; - } - - .grid-child-element { - margin: 10px; - padding: 10px; - } - - form { - display: flex; - flex-direction: column; - align-items: center; - } - - label { - display: inline-block; - width: 150px; - text-align: left; - margin-right: 10px; - padding: 5px; - } - - input[type="text"], - select { - margin-left: 10px; - outline: none; - padding: 5px; - border: 1px solid gray; - } - - table { - border-collapse: collapse; - width: 100%; - } - - th, - td { - text-align: left; - padding: 8px; - border-bottom: 1px solid #ddd; - } - - th { - background-color: #000000; - color: white; - } -</style> -<div class="grid-container-element"> - <div class="grid-child-element"> - <h1>Checkout</h1> - <form method='POST' class="content"> - {{ form.csrf_token }} - <br /> - <label for="name">{{ form.name.label }}{{ form.name }}</label><br /> - <br /> - <label for="card_number">{{ form.card_number.label }}{{ form.card_number }}</label><br /> - <br /> - <label for="exp_date_month">{{ form.exp_date_month.label }}{{ form.exp_date_month }}</label> - <label for="exp_date_year">{{ form.exp_date_year.label }}{{ form.exp_date_year }}</label><br /> - <br /> - <label for="cvv_2">{{ form.cvv_2.label }}{{ form.cvv_2 }}</label><br /> - <br /> - <label for="address">{{form.address.label}}{{form.address}}</label><br /> - <br /> - <label for="postcode">{{form.postcode.label}}{{form.postcode}}</label><br /> - <br /> - <p><input type="submit" value="Complete payment"></p> - </form> - </div> - <div class="grid-child-element"> - <h1>Order Summary</h1> - <table class="content"> - <thead> - <tr> - <th>Item description</th> - <th>Item price</th> - <th></th> - </tr> - </thead> - <tbody> - {% for item in items %} - <tr> - <td>{{ item['description'] }}</td> - <td>£{{ item['price'] }}</td> - </tr> - {% endfor %} - </tbody> - <tfoot> - <tr> - <td>Total price:</td> - <td>£{{ total_price }}</td> - <td></td> - </tr> - </tfoot> - </table> +<div class="container"> + <div class="row"> + <div class="col"> + <h1>Checkout</h1> + <form method='POST'> + {{ form.hidden_tag() }} + {{ render_field(form.name, class="form-control") }} + {{ render_field(form.card_number, class="form-control") }} + {{ render_field(form.exp_date_month, class="form-control") }} + {{ render_field(form.exp_date_year, class="form-control") }} + {{ render_field(form.cvv_2, class="form-control") }} + {{ render_field(form.address, class="form-control") }} + {{ render_field(form.postcode, class="form-control") }} + {{form.submit(class="btn btn-primary")}} + </form> + </div> + <div class="col"> + <h1>Order Summary</h1> + <table class="content"> + <table class="table"> + <thead class="text-light" style="background-color: #000000"> + <tr> + <th>Item description</th> + <th>Item price</th> + <th></th> + </tr> + </thead> + <tbody> + {% for item in items %} + <tr> + <td>{{ item['description'] }}</td> + <td>£{{ item['price'] }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </table> + </div> </div> </div> {% endblock %} \ No newline at end of file diff --git a/store/templates/items.html b/store/templates/items.html index 3b67af7..c67c840 100644 --- a/store/templates/items.html +++ b/store/templates/items.html @@ -1,6 +1,5 @@ {%extends 'base.html' %} {% block title %} Items | Antiques Online {% endblock %} -<<<<<<< HEAD {% block content %} <!-- https://getbootstrap.com/docs/4.0/layout/grid/ --> <div class="container pb-3"> @@ -25,51 +24,6 @@ {% else %} <p>No items available</p> {% endif %} -======= -<style> - .grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 10px; - grid-auto-rows: minmax(100px, auto); - padding-bottom: 100px; - } - - .grid-item { - grid-row: span 1; - grid-column: span 1; - padding: 10px; - } - - .grid-item img { - max-width: 100%; - height: auto; - } - - .grid-item h2 { - margin: 10px 0; - font-size: 18px; - - } - - .grid-item p { - margin: 5px 0; - font-size: 14px; - } -</style> - -<div class="grid"> - {% if items %} - {% for item in items %} - <div class="grid-item"> - - <img src="static\image_placeholder.png" alt="{{ item.description }}"> - <h2>{{ item.description }}</h2> - <p>£{{ item.price }}</p> - <a href="{{url_for('item_page', item_id = item.id)}}">View - Details</a> - ->>>>>>> master </div> </div> {% endblock %} \ No newline at end of file diff --git a/store/templates/userContent/ChangePhNumber.html b/store/templates/userContent/ChangePhNumber.html index 76ec481..b1790d5 100644 --- a/store/templates/userContent/ChangePhNumber.html +++ b/store/templates/userContent/ChangePhNumber.html @@ -8,7 +8,6 @@ <label for="password"><b>Password verification</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> - <label for="NewNumber"><b> Enter New Number</b></label> <input type="NewNumber" placeholder="Enter new Number" name="NewNumber" id="NewNumber" required> diff --git a/store/templates/userContent/ChangeUsername.html b/store/templates/userContent/ChangeUsername.html index 155155b..f4bdcc1 100644 --- a/store/templates/userContent/ChangeUsername.html +++ b/store/templates/userContent/ChangeUsername.html @@ -8,8 +8,6 @@ <label for="password"><b>Password verification</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> - - <label for="NewUsername"><b>New username</b></label> <input type="NewUsername" placeholder="Enter new Username" name="NewUsername" id="NewUsername" required> diff --git a/store/templates/userContent/login.html b/store/templates/userContent/login.html index 54e40a8..14feb97 100644 --- a/store/templates/userContent/login.html +++ b/store/templates/userContent/login.html @@ -2,7 +2,6 @@ {%extends 'base.html' %} {% block title %} Login | Antiques Online {% endblock %} {% block content %} -<<<<<<< HEAD <div class="container d-flex align-items-center justify-content-center"> <div class="my-4 w-25"> <h1>Login</h1> @@ -10,32 +9,8 @@ {{form.hidden_tag()}} {{ render_field(form.username, class="form-control") }} {{ render_field(form.password, class="form-control") }} - {{ render_field(form.securityQ1, class="form-control") }} {{form.submit(class="btn btn-primary")}} </form> </div> -======= -<br> -<h1>Login</h1> -<br> -<div class="container"> - <form method="POST"> - {{form.hidden_tag()}} - <dl> - <table class="loginTable"> - <tr> - <td>{{ render_field(form.username) }} </td> - </tr> - <td>{{ render_field(form.password) }}</td> - </tr> - </table> - </dl> - <br> - {{form.submit()}} - <br> - </form> - <td><a href="{{ url_for('reset_password')}}">I Forgot my Password!</a></td><br><br> - <br> ->>>>>>> master </div> {% endblock %} \ No newline at end of file diff --git a/store/templates/userContent/register.html b/store/templates/userContent/register.html index 3354261..2a3d96e 100644 --- a/store/templates/userContent/register.html +++ b/store/templates/userContent/register.html @@ -10,7 +10,6 @@ {{ render_field(form.username, class="form-control") }} {{ render_field(form.email, class="form-control") }} {{ render_field(form.phone_number, class="form-control") }} - {{ render_field(form.securityQ1, class="form-control") }} {{ render_field(form.password, class="form-control") }} {{ render_field(form.confirm, class="form-control") }} {{ render_field(form.accept_tos, class="form-check-input") }} diff --git a/store/templates/userContent/verify_code_security1.html b/store/templates/userContent/verify_code_security1.html deleted file mode 100644 index c54d763..0000000 --- a/store/templates/userContent/verify_code_security1.html +++ /dev/null @@ -1,15 +0,0 @@ -{%extends 'base.html' %} -{% block content %} -{% block title %} Verify reset Password {% endblock %} -<form method="POST" action="{{ url_for('verify_code_security1') }}" style="float: center; text-align: center;"> - <div class="container"> - <h1>Reset security answer</h1> - - <label for="verification_code"><b>Enter your code </b></label> - <input type="verification_code" placeholder="Enter your code " name="verification_code" id="verification_code" - required> - - <input class="button" type="submit" value="Reset security answer"> - </div> -</form> -{% endblock %} \ No newline at end of file diff --git a/store/templates/userContent/verify_code_security1_confirm.html b/store/templates/userContent/verify_code_security1_confirm.html deleted file mode 100644 index c3f5889..0000000 --- a/store/templates/userContent/verify_code_security1_confirm.html +++ /dev/null @@ -1,20 +0,0 @@ -{%extends 'base.html' %} -{% block content %} -{% block title %} Verify reset Password {% endblock %} -<form method="POST" action="{{ url_for('verify_code_security1_confirm') }}" style="float: center; text-align: center;"> - <div class="container"> - <h1>Reset security question</h1> - - - <label for="securityQ1"><b>what is your favourite colour</b></label> - <input type="securityQ1" placeholder="Enter a colour" name="securityQ1" id="securityQ1" required> - - - <label for="confirm_securityQ1"><b>confirm answer</b></label> - <input type="confirm_securityQ1" placeholder="confirm answer" name="confirm_securityQ1" id="confirm_securityQ1" - required> - - <input class="button" type="submit" value="confirm password"> - </div> -</form> -{% endblock %} \ No newline at end of file diff --git a/store/utility.py b/store/utility.py index 8029ff9..8c7d8bb 100644 --- a/store/utility.py +++ b/store/utility.py @@ -7,6 +7,7 @@ from sqlalchemy import select, func import datetime from store import * import re +import requests # Difference between filter and filter_by: https://stackoverflow.com/a/31560897 @@ -157,3 +158,29 @@ def update_set(reference_item): def get_item_by_id(id): return Item.query.get(id) + + +def validate_address(form, field): + # Uses the google maps geocoding api to check if a postcode or address exists + + url = "https://maps.googleapis.com/maps/api/geocode/json" + + api_key = "AIzaSyCz4xyIl5yaIg869orL07ye2ainw7kv5Pc" + + params = {"address": field, "key": api_key} + + response = requests.get(url, params=params).json() + + # Check if response status is "OK" and has at least one result + if response["status"] == "OK" and len(response["results"]) > 0: + print("Valid address or postcode") + pass + else: + raise ValidationError("Enter valid address") + + +def validate_cvv_2(form, field): + regex = "^[0-9]{3,4}$" + p = re.compile(regex) + if not re.search(p, field.data): + raise ValidationError("CVV2 is incorrect") -- GitLab