diff --git a/store/forms.py b/store/forms.py index 61ef12b75fdd177854ac3b1a4e87cb5b118c2911..ec125c50b01db198e8520e6836d8a0bf19e113d9 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 a7e6463be4ffbbb2d608a600e99521f94e123804..d807d216286466e0c2ab96d9ac0dd53cb6924f27 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 e91b02e822abc86e5e0841fe898ad17850c0bf9b..3cfeeca86593d147890ac6f0267b3fbcc39cd5e7 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 0000000000000000000000000000000000000000..0922e01408c9436cf4efb9efd314acb7d0018670 --- /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 89384d83fd3558b49bf753a282a7f3d90ac21c9b..9e77b4c0fa75f031b89f9a16c79e316f9d2b6ca9 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 f710e56eb746b72e3377a4acd42a2733a51b531e..eb6d6f744bf0e50757e89984f336737202f7d64f 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