From f4e6d818d7588e1dfccc4cd5bcec9890f5abae45 Mon Sep 17 00:00:00 2001 From: b4-sharp <Bradley2.Sharp@live.uwe.ac.uk> Date: Tue, 28 Mar 2023 01:21:44 +0100 Subject: [PATCH] Implement item search --- store/forms.py | 5 ++++ store/routes.py | 18 +++++++++++++ store/templates/base.html | 26 +++++++++--------- store/templates/search.html | 53 +++++++++++++++++++++++++++++++++++++ store/utility.py | 33 +++++++++-------------- unit_tests.py | 1 + 6 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 store/templates/search.html diff --git a/store/forms.py b/store/forms.py index 61ef12b..ec125c5 100644 --- a/store/forms.py +++ b/store/forms.py @@ -32,3 +32,8 @@ class LoginForm(FlaskForm): "Enter your Favourite Colour", [validators.Length(min=3, max=35)] ) submit = SubmitField("Login", render_kw={"class": "button"}) + + +class SearchForm(FlaskForm): + query = StringField("Query", [validators.DataRequired()]) + submit = SubmitField("Submit") diff --git a/store/routes.py b/store/routes.py index a7e6463..d807d21 100644 --- a/store/routes.py +++ b/store/routes.py @@ -457,3 +457,21 @@ def ItemPage(item_id): item_description=item_description, item_id=item_id, ) + + +@app.context_processor +def base(): + """So that search works on every page, and so that the search form does not need to be passed + to every template a context processor is used to inject a variable (in this case search_form) + into every template. + """ + form = SearchForm() + return dict(search_form=form) + + +@app.route("/search", methods=["POST"]) +def search(): + form = SearchForm() + if form.validate_on_submit(): + items = get_by_keyword(form.query.data) + return render_template("search.html", form=form, items=items) diff --git a/store/templates/base.html b/store/templates/base.html index e91b02e..3cfeeca 100644 --- a/store/templates/base.html +++ b/store/templates/base.html @@ -1,3 +1,4 @@ +{% from "_formhelpers.html" import render_field %} <!DOCTYPE html> <html lang="en"> @@ -8,9 +9,9 @@ </head> <body> - <header class="header"> - <a href="{{ url_for('index')}}">ANTIQUES ONLINE</a> - </header> + <header class="header"> + <a href="{{ url_for('index')}}">ANTIQUES ONLINE</a> + </header> <nav class="navbar"> <ul class="navbar-list"> @@ -27,9 +28,10 @@ <li><a href="{{ url_for('register')}}">Register</a></li> {% endif %} - <form class="search"> - <button type="submit">Search</button> - <input type="text" placeholder="Search..."> + <form method="POST" action="{{url_for('search')}}" class="search"> + {{search_form.hidden_tag()}} + {{render_field(search_form.query) }} + {{search_form.submit()}} </form> </ul> </nav> @@ -48,12 +50,12 @@ {% endblock %} <!-- Where HTML will go when extended. --> - <footer class="footer"> - <br> - <p>Contact Info: mail@mail.com</p> - <p>Phone Number 0000 000 0000</p> - <br> - </footer> + <footer class="footer"> + <br> + <p>Contact Info: mail@mail.com</p> + <p>Phone Number 0000 000 0000</p> + <br> + </footer> </body> </html> \ No newline at end of file diff --git a/store/templates/search.html b/store/templates/search.html new file mode 100644 index 0000000..0922e01 --- /dev/null +++ b/store/templates/search.html @@ -0,0 +1,53 @@ +{%extends 'base.html' %} +{% block title %} Search | Antiques Online {% endblock %} +{% block content %} +Searching for {{form.query.data}} +<style> + .grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-gap: 10px; + grid-auto-rows: minmax(100px, auto); + } + + .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('ItemPage', item_id = item.id)}}">View + Details</a> + + </div> + {% endfor %} + {% else %} + <p>No items available</p> + {% endif %} +</div> +{% endblock %} \ No newline at end of file diff --git a/store/utility.py b/store/utility.py index 89384d8..9e77b4c 100644 --- a/store/utility.py +++ b/store/utility.py @@ -2,7 +2,7 @@ from difflib import SequenceMatcher from store.models import * -from sqlalchemy import select +from sqlalchemy import select, func import datetime from store import * import re @@ -84,26 +84,17 @@ def remove_item(item): # db.session.Item.query() -# Search desciption for matching substring. -# This is most useful for a user keyword search. def get_by_keyword(substring): - to_search = get_items() + get_item_sets() - found = [] - for descriptable in to_search: - if substring.lower() in descriptable.description.lower(): - found.append(descriptable) - return found - - -# Search desciption for matching substring. -# This is most useful for a user keyword search. -def get_by_keyword(substring): - to_search = get_items() + get_item_sets() - found = [] - for descriptable in to_search: - if substring.lower() in descriptable.description.lower(): - found.append(descriptable) - return found + """Search desciption for matching substring. Case insensitive. + This is most useful for a user keyword search. + """ + substring = substring.lower() + return ( + Item.query.filter(func.lower(Item.description.contains(substring))).all() + + ItemSet.query.filter( + func.lower(ItemSet.description.contains(substring)) + ).all() + ) def get_itemsets_by_item_id(item_id): @@ -134,6 +125,8 @@ def add_item(description, price, date_sold=None): 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.""" + # We need the owning item set to avoid adding duplicates. owning_itemsets = get_itemsets_by_item_id(reference_item.id) # Sort by closest match. Could also do it based of the description of the set. diff --git a/unit_tests.py b/unit_tests.py index f710e56..eb6d6f7 100644 --- a/unit_tests.py +++ b/unit_tests.py @@ -1,4 +1,5 @@ from store import app + """ An instance of the database needs to exist to unit test, as the database is seeded from file this can be used as a test database. Really there should be an inmemory database for testing purposes, but -- GitLab