From eb7e446e56777603ec724147a617505421735158 Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Fri, 13 Jun 2025 15:29:35 -0500 Subject: [PATCH] Implement search functionality with pagination and enhance templates for improved user experience --- __pycache__/routes.cpython-313.pyc | Bin 25326 -> 29123 bytes routes.py | 62 ++++++++++++++++- templates/fragments/_link_fragment.html | 13 +++- templates/index.html | 8 +-- templates/inventory_index.html | 2 +- templates/layout.html | 86 ++++++++++++++++-------- templates/search.html | 65 ++++++++++++++++-- 7 files changed, 193 insertions(+), 43 deletions(-) diff --git a/__pycache__/routes.cpython-313.pyc b/__pycache__/routes.cpython-313.pyc index c45292bed3b4e9fc6c5c75247fdf1da1ef297356..1b0adbbbb1ace4a9e5fede77641c7fd54e0f9fed 100644 GIT binary patch delta 8466 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 V5@BR?X8g>|z$E^W3B&{k8~~*ZmhS)n diff --git a/routes.py b/routes.py index f27d0b9..47294d3 100644 --- a/routes.py +++ b/routes.py @@ -1,7 +1,8 @@ -from flask import Blueprint, render_template, url_for, request +from flask import Blueprint, render_template, url_for, request, redirect from .models import Area, Brand, Item, Inventory, RoomFunction, User, WorkLog, Room import html -from sqlalchemy.orm import joinedload +from sqlalchemy import or_ +from sqlalchemy.orm import aliased from typing import Callable, Any, List from . import db from .utils import eager_load_user_relationships, eager_load_inventory_relationships, eager_load_room_relationships, eager_load_worklog_relationships, chunk_list @@ -307,4 +308,59 @@ def worklog_entry(id): @main.route("/search") def search(): - return render_template('search.html', title="Database Search") + query = request.args.get('q', '').strip() + inventory_page = request.args.get('inventory_page', default=1, type=int) + user_page = request.args.get('user_page', default=1, type=int) + worklog_page = request.args.get('worklog_page', default=1, type=int) + + if not query: + return redirect(url_for('index')) + + UserAlias = aliased(User) + + inventory_query = eager_load_inventory_relationships(db.session.query(Inventory).join(UserAlias, Inventory.owner)).filter( + or_( + Inventory.inventory_name.ilike(f"%{query}%"), + Inventory.serial.ilike(f"%{query}%"), + Inventory.barcode.ilike(f"%{query}%"), + Inventory.notes.ilike(f"%{query}%"), + UserAlias.first_name.ilike(f"%{query}%"), + UserAlias.last_name.ilike(f"%{query}%") + )) + inventory_pagination = make_paginated_data(inventory_query, inventory_page) + user_query = eager_load_user_relationships(db.session.query(User)).filter( + or_( + User.first_name.ilike(f"%{query}%"), + User.last_name.ilike(f"%{query}%") + )) + user_pagination = make_paginated_data(user_query, user_page) + worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact)).filter( + or_( + WorkLog.notes.ilike(f"%{query}%"), + UserAlias.first_name.ilike(f"%{query}%"), + UserAlias.last_name.ilike(f"%{query}%") + )) + worklog_pagination = make_paginated_data(worklog_query, worklog_page) + + results = { + 'inventory': { + 'results': inventory_query, + 'headers': inventory_headers, + 'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_pagination['items']], + 'pagination': inventory_pagination + }, + 'users': { + 'results': user_query, + 'headers': user_headers, + 'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_pagination['items']], + 'pagination': user_pagination + }, + 'worklog': { + 'results': worklog_query, + 'headers': worklog_headers, + 'rows': [{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in worklog_pagination['items']], + 'pagination': worklog_pagination + } + } + + return render_template('search.html', title="Database Search", results=results, query=query) diff --git a/templates/fragments/_link_fragment.html b/templates/fragments/_link_fragment.html index 8fe9cc3..cf3495d 100644 --- a/templates/fragments/_link_fragment.html +++ b/templates/fragments/_link_fragment.html @@ -1,4 +1,4 @@ -{% macro category_link(endpoint, label, icon_html=None, arguments={}) %} +{% macro category_link(endpoint, label, icon_html=none, arguments={}) %}

{% endmacro %} + +{% macro navigation_link(endpoint, label, icon_html=none, arguments={}, active=false) %} + +{% endmacro %} diff --git a/templates/index.html b/templates/index.html index 73786c5..1c979c2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,10 +4,8 @@ {% block title %}{{ title }}{% endblock %} {% block content %} -

Table of Contents

-
- {{ links.category_link(endpoint = 'inventory_index', label = 'Inventory', icon_html = icons.inventory) }} - {{ links.category_link(endpoint = 'list_users', label = "Users", icon_html = icons.user) }} - {{ links.category_link(endpoint = 'list_worklog', label = 'Worklog', icon_html = icons.log) }} +
+

Welcome to Inventory Manager

+

Find out about all of your assets.

{% endblock %} \ No newline at end of file diff --git a/templates/inventory_index.html b/templates/inventory_index.html index a5a494e..de0305a 100644 --- a/templates/inventory_index.html +++ b/templates/inventory_index.html @@ -16,7 +16,7 @@ title=title
- {{ links.category_link(endpoint = 'search', label = 'Search', icon_html = icons.search, arguments={'category': 'inventory'}) }} + {{ links.category_link(endpoint = 'search', label = 'Search', icon_html = icons.search) }} {{ links.category_link(endpoint = 'list_inventory', label = "List", icon_html = icons.table) }}
diff --git a/templates/layout.html b/templates/layout.html index a60a051..d58f9cc 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -14,46 +14,78 @@ -
-

{{ title }}

-
+
{% block content %}{% endblock %}
- + \ No newline at end of file diff --git a/templates/search.html b/templates/search.html index 445649a..ce1291f 100644 --- a/templates/search.html +++ b/templates/search.html @@ -1,4 +1,4 @@ - + {% extends "layout.html" %} {% block title %}{{ title }}{% endblock %} @@ -10,11 +10,64 @@ title=title, ) }}
-

Search Categories

-
- {{ links.category_link(endpoint = 'search', label = 'Inventory', icon_html = icons.laptop, arguments = {'category': 'inventory'}) }} - {{ links.category_link(endpoint = 'search', label = 'Users', icon_html = icons.user, arguments = {'category': 'users'}) }} - {{ links.category_link(endpoint = 'search', label = 'Work Logs', icon_html = icons.log, arguments = {'category': 'logs'}) }} +
+ {{ tables.render_table( + headers = results['inventory']['headers'], + rows = results['inventory']['rows'], + entry_route = 'inventory_item', + title='Inventory Results' + )}} + {{ tables.render_pagination( + endpoint = 'main.search', + page = results['inventory']['pagination']['page'], + has_prev = results['inventory']['pagination']['has_prev'], + has_next = results['inventory']['pagination']['has_next'], + total_pages = results['inventory']['pagination']['total_pages'], + page_variable = 'inventory_page', + extra_args = { + 'q': query, + 'user_page': results['users']['pagination']['page'], + 'worklog_page': results['worklog']['pagination']['page'] + })}} +
+
+ {{ tables.render_table( + headers = results['users']['headers'], + rows = results['users']['rows'], + entry_route = 'user', title='User Results' + )}} + {{ tables.render_pagination( + endpoint = 'main.search', + page = results['users']['pagination']['page'], + has_prev = results['users']['pagination']['has_prev'], + has_next = results['users']['pagination']['has_next'], + total_pages = results['users']['pagination']['total_pages'], + page_variable = 'user_page', + extra_args = { + 'q': query, + 'inventory_page': results['inventory']['pagination']['page'], + 'worklog_page': results['worklog']['pagination']['page'] + })}} +
+
+ {{ tables.render_table( + headers = results['worklog']['headers'], + rows = results['worklog']['rows'], + entry_route = 'worklog_entry', + title='Worklog Results' + )}} + {{ tables.render_pagination( + endpoint = 'main.search', + page = results['worklog']['pagination']['page'], + has_prev = results['worklog']['pagination']['has_prev'], + has_next = results['worklog']['pagination']['has_next'], + total_pages = results['worklog']['pagination']['total_pages'], + page_variable = 'worklog_page', + extra_args = { + 'q': query, + 'inventory_page': results['inventory']['pagination']['page'], + 'user_page': results['users']['pagination']['page'] + })}}
{% endblock %} \ No newline at end of file