Various bug fixes. Still trying to fix cartesian issue on search.
This commit is contained in:
parent
0dbf246bdb
commit
3c07741500
9 changed files with 412 additions and 94 deletions
|
|
@ -1,14 +1,12 @@
|
|||
from flask import Blueprint, render_template, abort, request
|
||||
from flask import Blueprint, render_template, abort, request, url_for
|
||||
|
||||
import crudkit
|
||||
|
||||
from crudkit.api._cursor import decode_cursor, encode_cursor
|
||||
from crudkit.ui.fragments import render_table, register_template_globals
|
||||
|
||||
bp_listing = Blueprint("listing", __name__)
|
||||
|
||||
def init_listing_routes(app):
|
||||
# Make helpers available in all templates
|
||||
register_template_globals(app)
|
||||
|
||||
@bp_listing.get("/listing/<model>")
|
||||
|
|
@ -21,12 +19,15 @@ def init_listing_routes(app):
|
|||
abort(404)
|
||||
|
||||
# read query args
|
||||
limit = request.args.get("limit", None)
|
||||
limit = int(limit) if (limit is not None and str(limit).isdigit()) else 15
|
||||
sort = request.args.get("sort")
|
||||
fields_qs = request.args.get("fields")
|
||||
cursor = request.args.get("cursor")
|
||||
key, _desc, backward = decode_cursor(cursor)
|
||||
# accept both per_page and limit; per_page wins if both provided
|
||||
per_page_qs = request.args.get("per_page")
|
||||
limit_qs = request.args.get("limit")
|
||||
page = int(request.args.get("page", 1) or 1)
|
||||
per_page = int(per_page_qs) if (per_page_qs and per_page_qs.isdigit()) else (
|
||||
int(limit_qs) if (limit_qs and limit_qs.isdigit()) else 15
|
||||
)
|
||||
sort = request.args.get("sort")
|
||||
fields_qs = request.args.get("fields")
|
||||
|
||||
# base spec per model
|
||||
spec = {}
|
||||
|
|
@ -34,16 +35,8 @@ def init_listing_routes(app):
|
|||
row_classes = []
|
||||
if model.lower() == 'inventory':
|
||||
spec = {"fields": [
|
||||
"label",
|
||||
"name",
|
||||
"barcode",
|
||||
"serial",
|
||||
"brand.name",
|
||||
"model",
|
||||
"device_type.description",
|
||||
"condition",
|
||||
"owner.label",
|
||||
"location.label",
|
||||
"label", "name", "barcode", "serial", "brand.name", "model",
|
||||
"device_type.description", "condition", "owner.label", "location.label",
|
||||
]}
|
||||
columns = [
|
||||
{"field": "label"},
|
||||
|
|
@ -60,14 +53,9 @@ def init_listing_routes(app):
|
|||
]
|
||||
elif model.lower() == 'user':
|
||||
spec = {"fields": [
|
||||
"label",
|
||||
"last_name",
|
||||
"first_name",
|
||||
"supervisor.label",
|
||||
"robot.overlord",
|
||||
"staff",
|
||||
"active",
|
||||
], "sort": "first_name,last_name"} # default for users
|
||||
"label", "last_name", "first_name", "supervisor.label",
|
||||
"robot.overlord", "staff", "active",
|
||||
], "sort": "first_name,last_name"}
|
||||
columns = [
|
||||
{"field": "label", "label": "Full Name"},
|
||||
{"field": "last_name"},
|
||||
|
|
@ -86,11 +74,7 @@ def init_listing_routes(app):
|
|||
]
|
||||
elif model.lower() == 'worklog':
|
||||
spec = {"fields": [
|
||||
"work_item.label",
|
||||
"contact.label",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"complete",
|
||||
"work_item.label", "contact.label", "start_time", "end_time", "complete",
|
||||
]}
|
||||
columns = [
|
||||
{"field": "work_item.label", "label": "Work Item",
|
||||
|
|
@ -106,44 +90,106 @@ def init_listing_routes(app):
|
|||
{"when": {"field": "complete", "is": False}, "class": "table-danger"}
|
||||
]
|
||||
|
||||
# Build params to feed CRUDService (flat dict; parse_filters expects flat keys)
|
||||
# Build params to feed CRUDService
|
||||
params = dict(spec)
|
||||
|
||||
# overlay fields from query (?fields=...)
|
||||
if fields_qs:
|
||||
params["fields"] = [p.strip() for p in fields_qs.split(",") if p.strip()]
|
||||
|
||||
# overlay sort from query (?sort=...)
|
||||
if sort:
|
||||
params["sort"] = sort
|
||||
|
||||
# limit semantics: 0 means "unlimited" in your service layer
|
||||
params["limit"] = limit
|
||||
|
||||
# forward *all other* query params as filters (flat), excluding known control keys
|
||||
CONTROL_KEYS = {"limit", "cursor", "sort", "fields"}
|
||||
# forward remaining query params as filters (flat), excluding control keys
|
||||
CONTROL_KEYS = {"page", "per_page", "limit", "sort", "fields"}
|
||||
for k, v in request.args.items():
|
||||
if k in CONTROL_KEYS:
|
||||
continue
|
||||
if v is None or v == "":
|
||||
if k in CONTROL_KEYS or v in (None, ""):
|
||||
continue
|
||||
params[k] = v
|
||||
|
||||
service = crudkit.crud.get_service(cls)
|
||||
|
||||
window = service.seek_window(params, key=key, backward=backward, include_total=True)
|
||||
# Use page-based pagination from the service
|
||||
result = service.page(params, page=page, per_page=per_page, include_total=True)
|
||||
items = result["items"]
|
||||
|
||||
table = render_table(window.items, columns=columns,
|
||||
table = render_table(items, columns=columns,
|
||||
opts={"object_class": model, "row_classes": row_classes})
|
||||
|
||||
def _base_params():
|
||||
keep = {}
|
||||
for k, v in request.args.items():
|
||||
if k == "page" or v in (None, ""):
|
||||
continue
|
||||
keep[k] = v
|
||||
# keep both for compatibility; per_page wins in the service anyway
|
||||
keep["per_page"] = per_page
|
||||
keep["limit"] = per_page
|
||||
return keep
|
||||
|
||||
total = int(result["total"] or 0)
|
||||
pages = int(result["pages"] or 1)
|
||||
page = int(result["page"] or 1)
|
||||
|
||||
has_prev = page > 1
|
||||
has_next = page < pages
|
||||
|
||||
base = _base_params()
|
||||
prev_url = url_for("listing.show_list", model=model, **{**base, "page": max(1, page - 1)})
|
||||
next_url = url_for("listing.show_list", model=model, **{**base, "page": min(pages, page + 1)})
|
||||
|
||||
def page_url(n: int) -> str:
|
||||
return url_for("listing.show_list", model=model, **{**base, "page": n})
|
||||
|
||||
def build_nav(page: int, pages: int, window: int = 2):
|
||||
"""
|
||||
Returns a list like:
|
||||
[{'type':'page','n':1,'url':'...','active':False}, {'type':'ellipsis'}, ...]
|
||||
Shows first, last, current±window, with ellipses where gaps exist.
|
||||
"""
|
||||
if pages <= 1:
|
||||
return [{'type': 'page', 'n': 1, 'url': page_url(1), 'active': True}]
|
||||
|
||||
show = set([1, pages])
|
||||
for n in range(max(1, page - window), min(pages, page + window) + 1):
|
||||
show.add(n)
|
||||
|
||||
out = []
|
||||
last = 0
|
||||
for n in range(1, pages + 1):
|
||||
if n in show:
|
||||
out.append({'type': 'page', 'n': n, 'url': page_url(n), 'active': (n == page)})
|
||||
last = n
|
||||
else:
|
||||
# insert a single ellipsis per gap
|
||||
if last != -1:
|
||||
out.append({'type': 'ellipsis'})
|
||||
last = -1
|
||||
# skip the interior of the gap
|
||||
# we let the for loop continue
|
||||
# collapse any duplicate ellipses at ends (paranoia)
|
||||
cleaned = []
|
||||
for i, item in enumerate(out):
|
||||
if item['type'] == 'ellipsis' and (i == 0 or out[i-1]['type'] == 'ellipsis'):
|
||||
continue
|
||||
cleaned.append(item)
|
||||
if cleaned and cleaned[-1]['type'] == 'ellipsis':
|
||||
cleaned.pop()
|
||||
if cleaned and cleaned[0]['type'] == 'ellipsis':
|
||||
cleaned.pop(0)
|
||||
return cleaned
|
||||
|
||||
pagination_ctx = {
|
||||
"limit": window.limit,
|
||||
"total": window.total,
|
||||
"next_cursor": encode_cursor(window.last_key, list(window.order.desc), backward=False),
|
||||
"prev_cursor": encode_cursor(window.first_key, list(window.order.desc), backward=True),
|
||||
"sort": params.get("sort") # expose current sort to the template
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"total": total,
|
||||
"pages": pages,
|
||||
"has_prev": has_prev,
|
||||
"has_next": has_next,
|
||||
"prev_url": prev_url,
|
||||
"next_url": next_url,
|
||||
"nav": build_nav(page, pages, window=2), # tweak window=2..3 to taste
|
||||
"sort": params.get("sort")
|
||||
}
|
||||
|
||||
return render_template("listing.html", model=model, table=table, pagination=pagination_ctx)
|
||||
|
||||
app.register_blueprint(bp_listing)
|
||||
app.register_blueprint(bp_listing)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ def init_search_routes(app):
|
|||
{"field": "updates", "format": lambda x: len(x)},
|
||||
]
|
||||
worklog_results = worklog_service.list({
|
||||
'contact.label|work_item.label__icontains': q,
|
||||
'contact.label|work_item.label|updates.content__icontains': q,
|
||||
'fields': [
|
||||
"contact.label",
|
||||
"work_item.label",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue