From 73ae8396d75f7e755e8fbd36dbcf454ebcb7b954 Mon Sep 17 00:00:00 2001 From: b4-sharp <Bradley2.Sharp@live.uwe.ac.uk> Date: Wed, 5 Apr 2023 04:08:40 +0100 Subject: [PATCH] Add admin ability to remove items --- README.md | 6 +- requirements.txt | 3 +- store/forms.py | 16 ++- store/routes.py | 100 +++++++++++++++++- store/templates/base.html | 1 + store/templates/userContent/admin.html | 1 + .../userContent/database_management.html | 100 ++++++++++++++++++ store/utility.py | 41 +++---- 8 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 store/templates/userContent/database_management.html diff --git a/README.md b/README.md index 36ac4fb..bc1e83e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -This codebase uses Python flask, see https://flask.palletsprojects.com/ for more information. - # Running the Website To host an instance of the server run the command ``python run.py`` in a terminal from the root directory of the project, where ``run.py`` is. @@ -21,6 +19,8 @@ To ensure a consistent code style Python [black](https://pypi.org/project/black/ Python black follows pep8, that means snake_case for variables and function names, PascalCase for classes. +This codebase uses Python flask, see https://flask.palletsprojects.com/ for more information. + ## Static Static content, e.g. images, css, are stored in the static folder. @@ -72,3 +72,5 @@ Ensure code works, do not knowingly commit broken code so as to avoid interrupti Commit messages should be as though they are telling the commit history what to do, e.g. "Add file.py".\ Do not commit files that can be regenerated, for example virtual environment files.\ Update requirements.txt with any newly imported moduless. + +[](https://github.com/psf/black) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9de8bb8..b2c7d62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ Flask_SQLAlchemy Flask_WTF WTForms flask_login -flask_mail \ No newline at end of file +flask_mail +black \ No newline at end of file diff --git a/store/forms.py b/store/forms.py index ec125c5..25703ed 100644 --- a/store/forms.py +++ b/store/forms.py @@ -1,5 +1,13 @@ from flask_wtf import FlaskForm -from wtforms import BooleanField, StringField, PasswordField, SubmitField, validators +from wtforms import ( + BooleanField, + StringField, + PasswordField, + SubmitField, + SelectField, + IntegerField, + validators, +) from wtforms.validators import DataRequired # Basic example.# @@ -37,3 +45,9 @@ class LoginForm(FlaskForm): class SearchForm(FlaskForm): query = StringField("Query", [validators.DataRequired()]) submit = SubmitField("Submit") + + +class AccessDataForm(FlaskForm): + table = SelectField("Table", choices=["Item", "Item Set"]) + id = IntegerField("ID", [validators.DataRequired()]) + submit = SubmitField("Submit") diff --git a/store/routes.py b/store/routes.py index e63b9cc..f29e073 100644 --- a/store/routes.py +++ b/store/routes.py @@ -1,10 +1,20 @@ from store import app, db -from flask import render_template, request, flash, redirect, url_for, Flask, session +from flask import ( + render_template, + request, + flash, + redirect, + url_for, + Flask, + session, + abort, +) import json from store.utility import * from store.forms import * import string import random +import sys # Official flask-login doc free liecense @@ -318,6 +328,92 @@ def admin(): return render_template("userContent/admin.html") +@app.route("/database_management", methods=["GET", "POST"]) +@login_required +def database_management(): + if current_user.userType != "admin": + flash("Unauthorized access") + return redirect(url_for("home")) + # No need for an else statement as early return will + # prevent access anyway. + access_data_form = AccessDataForm() + if access_data_form.validate_on_submit(): + if access_data_form.table.data == "Item": + table = Item + elif access_data_form.table.data == "Item Set": + table = ItemSet + id = access_data_form.id.data + item = table.query.get(id) + if item == None: + flash("Invalid ID!") + + # No point to using another page, just use the same template. + return render_template( + "userContent/database_management.html", + access_data_form=access_data_form, + main_item=item, + is_item=type(item) is Item, + ) + return render_template( + "userContent/database_management.html", + access_data_form=access_data_form, + main_item=None, + ) + + +# @app.route("/add_item", methods=["POST"]) +# def add_item(): +# if current_user.userType != "admin": +# flash("Unauthorized access") +# return redirect(url_for("home")) + + +@app.route("/delete_item", methods=["POST"]) +def delete_item(): + if current_user.userType != "admin": + flash("Unauthorized access") + return redirect(url_for("home")) + id = request.form.get("item_id") + item = Item.query.get(id) + if item == None: + abort(406) + print("Deleting Item:", id, file=sys.stderr) + remove_item(item) + return "ok" + + +@app.route("/delete_item_set", methods=["DELETE"]) +def delete_item_set(): + if current_user.userType != "admin": + flash("Unauthorized access") + return redirect(url_for("home")) + id = request.form.get("item_id") + item = ItemSet.query.get(id) + if item == None: + abort(406) + print("Deleting Item Set:", id, file=sys.stderr) + remove_item_set(item) + return "ok" + + +@app.route("/delete_item_from_set", methods=["DELETE"]) +def delete_item_from_set(): + if current_user.userType != "admin": + flash("Unauthorized access") + return redirect(url_for("home")) + item_id = request.form.get("item_id") + set_id = request.form.get("set_id") + + item_set = ItemSet.query.get(set_id) + item = Item.query.get(item_id) + if item_set == None or item == None: + abort(406) + print("Deleting Item:", item_id, "From Set:", set_id, file=sys.stderr) + item_set.items.remove(item) + db.session.commit() + return "ok" + + @app.route("/ChangeUsername/", methods=["GET", "POST"]) @login_required def ChangeUsername(): @@ -435,7 +531,7 @@ def add_to_basket_set(): @app.route("/remove_item", methods=["POST"]) -def remove_item(): +def remove_item_basket(): # Calling this remove_item shadows the utility function remove_item() if request.method == "POST": item_id = request.form["item"] diff --git a/store/templates/base.html b/store/templates/base.html index 36c73cd..078786a 100644 --- a/store/templates/base.html +++ b/store/templates/base.html @@ -6,6 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>{% block title %}{% endblock%}</title> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='_main.css') }}"> <!-- Load the CSS. --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script> </head> <body> diff --git a/store/templates/userContent/admin.html b/store/templates/userContent/admin.html index dec9ae5..1632a25 100644 --- a/store/templates/userContent/admin.html +++ b/store/templates/userContent/admin.html @@ -6,5 +6,6 @@ <ul> <li><a href="{{ url_for('accountDetails')}}">View my Account details</a></li> <li><a href="{{ url_for('view_address')}}">View my shipping address</a></li> + <li><a href="{{ url_for('database_management')}}">View database management</a></li> </ul> {% endblock %} \ No newline at end of file diff --git a/store/templates/userContent/database_management.html b/store/templates/userContent/database_management.html new file mode 100644 index 0000000..6f9e013 --- /dev/null +++ b/store/templates/userContent/database_management.html @@ -0,0 +1,100 @@ +{% from "_formhelpers.html" import render_field %} +{%extends 'base.html' %} +{% block title %} Database Management | Antiques Online {% endblock %} +{% block content %} +<form method="POST"> + {{access_data_form.hidden_tag()}} + <dl> + <table class="loginTable"> + <tr> + <td>{{ render_field(access_data_form.table) }} </td> + </tr> + <tr> + <td>{{ render_field(access_data_form.id) }} </td> + </tr> + </table> + </dl> + <br> + {{access_data_form.submit()}} + <br> +</form> + +{% if main_item %} +<div> + <img src='..\static\image_placeholder.png' alt="Image Placeholder" width="200" height="170"> + <br /> + Item price: £{{main_item.price}} + <br /> + Item description: {{main_item.description}} + <br /> + {% if is_item %} + <button onclick="deleteItem({{main_item.id}})">Delete</button> + {%else%} + <button onclick="deleteItemSet({{main_item.id}})">Delete</button> + {% endif %} + + {% if main_item.items %} + <div> + <h1>Items contained in set: </h1> + <table> + <thead> + <tr> + <th>Item description</th> + <th>Item price</th> + <th></th> + </tr> + </thead> + <tbody> + {% for item in main_item.items %} + <tr> + <td>{{ item['description'] }}</td> + <td>£{{ item['price'] }}</td> + </tr> + <button onclick="deleteItemFromSet({{item.id}}, {{main_item.id}})">Delete</button> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} +</div> +{% endif %} + +<script> + function deleteItem(id) { + if (confirm("Are you sure you want to delete this item? It is irreversible.")) { + $.ajax({ + url: '/delete_item', + type: 'POST', + data: { item_id: id }, + success: function () { + window.location.href = window.location.href; + } + }); + } + }; + function deleteItemSet(id) { + if (confirm("Are you sure you want to delete this item? It is irreversible.")) { + $.ajax({ + url: '/delete_item_set', + type: 'DELETE', + data: { item_id: id }, + success: function () { + window.location.href = window.location.href; + } + }); + } + }; + function deleteItemFromSet(id, set_id) { + if (confirm("Are you sure you want to delete this item? It is irreversible.")) { + $.ajax({ + url: '/delete_item_from_set', + type: 'DELETE', + data: { set_id: set_id, item_id: id }, + success: function () { + window.location.href = window.location.href; + } + }); + } + }; +</script> +{% endblock %} \ No newline at end of file diff --git a/store/utility.py b/store/utility.py index aae06c2..a7e9fea 100644 --- a/store/utility.py +++ b/store/utility.py @@ -76,12 +76,6 @@ def get_similar_items(description): return found -def remove_item(item): - update_set(item) - db.session.Item.query(item).delete() - db.session.commit() - - def get_by_keyword(substring): """Search desciption for matching substring. Case insensitive. This is most useful for a user keyword search. @@ -103,15 +97,8 @@ def get_itemsets_by_item_id(item_id): return item_set_contained_id -def sell_item(item, date=datetime.datetime.today()): - update_set(item) - item.date_sold = date - db.session.commit() - - # Alternate approach where items_sets are retrieved from queries, but are not in the desired format. - # item_sets = select(ItemSet).where(ItemSet.items.any(Item.id==item_id)) - # flask_item_sets = db.session.execute(item_sets).all() - # flask_item_set = flask_item_sets[0][0] +def get_item_by_id(id): + return Item.query.get(id) def add_item(description, price, date_sold=None): @@ -120,8 +107,26 @@ def add_item(description, price, date_sold=None): db.session.commit() +def remove_item(item): + update_set(item) + db.session.delete(item) + db.session.commit() + + +def remove_item_set(item): + db.session.delete(item) + db.session.commit() + + +def sell_item(item, date=datetime.datetime.today()): + update_set(item) + item.date_sold = date + db.session.commit() + + def update_set(reference_item): - """Update the set that the passed item belongs to by removing it from the set and adding a similar item.""" + """Update the set that the passed item belongs to by removing it from the set + and adding a similar item.""" # We need the owning item set to avoid adding duplicates. owning_itemsets = get_itemsets_by_item_id(reference_item.id) @@ -147,7 +152,3 @@ def update_set(reference_item): itemset.items.remove(reference_item) itemset.items.append(similar_items[0]) db.session.commit() - - -def get_item_by_id(id): - return Item.query.get(id) -- GitLab