Adding pagination
This commit is contained in:
1 parent
a50fccd56d
commit
e923250177
8 files changed
+269
-46
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import distinct, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.models.associations import augmented_part_imported_parts
|
from app.models.associations import augmented_part_imported_parts
|
||||||
@@ -52,10 +52,39 @@ async def list_filtered(
|
|||||||
return list(result.scalars().all())
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
async def count_filtered(
|
||||||
|
db: AsyncSession,
|
||||||
|
description: str = "",
|
||||||
|
colour_id: int | None = None,
|
||||||
|
manufacturer_id: int | None = None,
|
||||||
|
scope_model_id: int | None = None,
|
||||||
|
part_type_id: int | None = None,
|
||||||
|
sub_part_type_id: int | None = None,
|
||||||
|
) -> int:
|
||||||
|
from sqlalchemy import func
|
||||||
|
q = select(func.count()).select_from(AugmentedPart)
|
||||||
|
if description.strip():
|
||||||
|
q = q.where(AugmentedPart.description.ilike(f"%{description.strip()}%"))
|
||||||
|
if colour_id is not None:
|
||||||
|
q = q.where(AugmentedPart.colour_id == colour_id)
|
||||||
|
if manufacturer_id is not None:
|
||||||
|
q = q.where(AugmentedPart.manufacturer_id == manufacturer_id)
|
||||||
|
if scope_model_id is not None:
|
||||||
|
q = q.where(AugmentedPart.scope_model_id == scope_model_id)
|
||||||
|
if part_type_id is not None:
|
||||||
|
q = q.where(AugmentedPart.part_type_id == part_type_id)
|
||||||
|
if sub_part_type_id is not None:
|
||||||
|
q = q.where(AugmentedPart.sub_part_type_id == sub_part_type_id)
|
||||||
|
result = await db.execute(q)
|
||||||
|
return result.scalar_one()
|
||||||
|
|
||||||
|
|
||||||
async def get_unlinked_imported_parts_filtered(
|
async def get_unlinked_imported_parts_filtered(
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
colour: str = "",
|
colour: str = "",
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 50,
|
||||||
) -> list[ImportedPart]:
|
) -> list[ImportedPart]:
|
||||||
q = (
|
q = (
|
||||||
select(ImportedPart)
|
select(ImportedPart)
|
||||||
@@ -69,10 +98,48 @@ async def get_unlinked_imported_parts_filtered(
|
|||||||
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
||||||
if colour.strip():
|
if colour.strip():
|
||||||
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
|
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
|
||||||
result = await db.execute(q.order_by(ImportedPart.InventoryID))
|
result = await db.execute(q.order_by(ImportedPart.InventoryID).offset(skip).limit(limit))
|
||||||
return list(result.scalars().all())
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
async def count_unlinked_filtered(
|
||||||
|
db: AsyncSession,
|
||||||
|
description: str = "",
|
||||||
|
colour: str = "",
|
||||||
|
) -> int:
|
||||||
|
from sqlalchemy import func
|
||||||
|
q = (
|
||||||
|
select(func.count())
|
||||||
|
.select_from(ImportedPart)
|
||||||
|
.where(
|
||||||
|
ImportedPart.InventoryID.not_in(
|
||||||
|
select(augmented_part_imported_parts.c.imported_part_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if description.strip():
|
||||||
|
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
||||||
|
if colour.strip():
|
||||||
|
q = q.where(ImportedPart.TBXColour.ilike(f"%{colour.strip()}%"))
|
||||||
|
result = await db.execute(q)
|
||||||
|
return result.scalar_one()
|
||||||
|
|
||||||
|
|
||||||
|
async def distinct_unlinked_colours(db: AsyncSession) -> list[str]:
|
||||||
|
q = (
|
||||||
|
select(distinct(ImportedPart.TBXColour))
|
||||||
|
.where(
|
||||||
|
ImportedPart.TBXColour.isnot(None),
|
||||||
|
ImportedPart.InventoryID.not_in(
|
||||||
|
select(augmented_part_imported_parts.c.imported_part_id)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.order_by(ImportedPart.TBXColour)
|
||||||
|
)
|
||||||
|
result = await db.execute(q)
|
||||||
|
return [row[0] for row in result.all()]
|
||||||
|
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ async def count(db: AsyncSession) -> int:
|
|||||||
return result.scalar_one()
|
return result.scalar_one()
|
||||||
|
|
||||||
|
|
||||||
|
async def count_filtered(db: AsyncSession, description: str = "") -> int:
|
||||||
|
from sqlalchemy import func
|
||||||
|
q = select(func.count()).select_from(ImportedPart)
|
||||||
|
if description.strip():
|
||||||
|
q = q.where(ImportedPart.Description.ilike(f"%{description.strip()}%"))
|
||||||
|
result = await db.execute(q)
|
||||||
|
return result.scalar_one()
|
||||||
|
|
||||||
|
|
||||||
async def delete(db: AsyncSession, part: ImportedPart) -> None:
|
async def delete(db: AsyncSession, part: ImportedPart) -> None:
|
||||||
await db.delete(part)
|
await db.delete(part)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|||||||
@@ -400,6 +400,27 @@ input[type=password] {
|
|||||||
.ap-item-conn { flex: 1; }
|
.ap-item-conn { flex: 1; }
|
||||||
.ap-item .row-actions { margin-left: auto; }
|
.ap-item .row-actions { margin-left: auto; }
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Filter bar */
|
/* Filter bar */
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<div id="augmented-parts-table-card" class="card">
|
<div id="augmented-parts-table-card" class="card">
|
||||||
|
{% include "admin/_pagination.html" %}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<div id="imported-parts-table-card" class="card" style="overflow-x:auto">
|
<div id="imported-parts-table-card" class="card" style="overflow-x:auto">
|
||||||
<p class="muted" style="margin-bottom:.5rem">
|
{% include "admin/_pagination.html" %}
|
||||||
Showing {{ parts|length }} record{{ '' if parts|length == 1 else 's' }}{% if total and total > parts|length %} of {{ total }} total{% endif %}.
|
|
||||||
</p>
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{% if pagination %}
|
||||||
|
<div class="pagination">
|
||||||
|
<span class="muted pagination-info">
|
||||||
|
{% if pagination.total == 0 %}
|
||||||
|
No records found.
|
||||||
|
{% elif pagination.total_pages == 1 %}
|
||||||
|
Showing {{ pagination.total }} record{{ '' if pagination.total == 1 else 's' }}.
|
||||||
|
{% else %}
|
||||||
|
Showing {{ pagination.showing_from }}–{{ pagination.showing_to }} of {{ pagination.total }}.
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% if pagination.total_pages > 1 %}
|
||||||
|
<div class="pagination-nav">
|
||||||
|
{% if pagination.prev_url %}
|
||||||
|
<button class="btn btn-ghost btn-sm"
|
||||||
|
hx-get="{{ pagination.prev_url }}"
|
||||||
|
hx-target="{{ pagination.target_id }}"
|
||||||
|
hx-swap="outerHTML">← Prev</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-ghost btn-sm" disabled>← Prev</button>
|
||||||
|
{% endif %}
|
||||||
|
<span class="muted">{{ pagination.page }} / {{ pagination.total_pages }}</span>
|
||||||
|
{% if pagination.next_url %}
|
||||||
|
<button class="btn btn-ghost btn-sm"
|
||||||
|
hx-get="{{ pagination.next_url }}"
|
||||||
|
hx-target="{{ pagination.target_id }}"
|
||||||
|
hx-swap="outerHTML">Next →</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-ghost btn-sm" disabled>Next →</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<div id="skipped-parts-table-card" class="card" style="overflow-x:auto">
|
<div id="skipped-parts-table-card" class="card" style="overflow-x:auto">
|
||||||
|
{% include "admin/_pagination.html" %}
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
+133
-41
@@ -1,5 +1,8 @@
|
|||||||
"""HTMX-driven admin dashboard for users, roles, and thread standards."""
|
"""HTMX-driven admin dashboard for users, roles, and thread standards."""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -31,6 +34,40 @@ from app.web.templates import templates
|
|||||||
|
|
||||||
router = APIRouter(prefix="/admin", include_in_schema=False)
|
router = APIRouter(prefix="/admin", include_in_schema=False)
|
||||||
|
|
||||||
|
_PAGE_SIZE = 50
|
||||||
|
|
||||||
|
|
||||||
|
def _paginate(
|
||||||
|
base_path: str,
|
||||||
|
filter_params: dict,
|
||||||
|
page: int,
|
||||||
|
page_size: int,
|
||||||
|
total: int,
|
||||||
|
target_id: str,
|
||||||
|
) -> dict:
|
||||||
|
total_pages = max(1, math.ceil(total / page_size))
|
||||||
|
page = max(1, min(page, total_pages))
|
||||||
|
skip = (page - 1) * page_size
|
||||||
|
|
||||||
|
def _url(p: int) -> str:
|
||||||
|
params = {k: v for k, v in filter_params.items() if v not in (None, "", 0)}
|
||||||
|
params["page"] = p
|
||||||
|
params["page_size"] = page_size
|
||||||
|
return f"{base_path}?{urllib.parse.urlencode(params)}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"total": total,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
"skip": skip,
|
||||||
|
"showing_from": skip + 1 if total > 0 else 0,
|
||||||
|
"showing_to": min(skip + page_size, total),
|
||||||
|
"prev_url": _url(page - 1) if page > 1 else None,
|
||||||
|
"next_url": _url(page + 1) if page < total_pages else None,
|
||||||
|
"target_id": target_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def web_require(*perms: str):
|
def web_require(*perms: str):
|
||||||
"""Web dependency: allow superusers or holders of all given permissions."""
|
"""Web dependency: allow superusers or holders of all given permissions."""
|
||||||
@@ -1045,12 +1082,13 @@ async def imported_parts_page(
|
|||||||
"username": config.username if config else None,
|
"username": config.username if config else None,
|
||||||
"has_password": bool(config and config.password),
|
"has_password": bool(config and config.password),
|
||||||
}
|
}
|
||||||
parts = await imported_part_crud.list_all(db)
|
|
||||||
total = await imported_part_crud.count(db)
|
total = await imported_part_crud.count(db)
|
||||||
|
parts = await imported_part_crud.list_filtered(db, limit=_PAGE_SIZE)
|
||||||
|
pagination = _paginate("/admin/imported-parts/filter", {}, 1, _PAGE_SIZE, total, "#imported-parts-table-card")
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/imported_parts.html",
|
"admin/imported_parts.html",
|
||||||
{"user": user, "config": config_view, "parts": parts, "total": total},
|
{"user": user, "config": config_view, "parts": parts, "pagination": pagination},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1087,13 +1125,17 @@ async def refresh_imported_parts(
|
|||||||
actor: User = Depends(web_require("imported_parts:write")),
|
actor: User = Depends(web_require("imported_parts:write")),
|
||||||
):
|
):
|
||||||
config = await imported_part_config_crud.get(db)
|
config = await imported_part_config_crud.get(db)
|
||||||
if not config or not config.odata_url:
|
|
||||||
parts = await imported_part_crud.list_all(db)
|
async def _imported_table_ctx(sync_result=None, sync_error=None):
|
||||||
total = await imported_part_crud.count(db)
|
total = await imported_part_crud.count(db)
|
||||||
|
parts = await imported_part_crud.list_filtered(db, limit=_PAGE_SIZE)
|
||||||
|
pagination = _paginate("/admin/imported-parts/filter", {}, 1, _PAGE_SIZE, total, "#imported-parts-table-card")
|
||||||
|
return {"parts": parts, "pagination": pagination, "sync_result": sync_result, "sync_error": sync_error}
|
||||||
|
|
||||||
|
if not config or not config.odata_url:
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request, "admin/_imported_parts_table.html",
|
||||||
"admin/_imported_parts_table.html",
|
await _imported_table_ctx(sync_error="OData URL is not configured."),
|
||||||
{"parts": parts, "total": total, "sync_error": "OData URL is not configured."},
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
records = await odata_service.fetch(config.odata_url, config.username, config.password)
|
records = await odata_service.fetch(config.odata_url, config.username, config.password)
|
||||||
@@ -1103,24 +1145,14 @@ async def refresh_imported_parts(
|
|||||||
resource_id=None,
|
resource_id=None,
|
||||||
description=f"OData sync: {created} created, {updated} updated ({len(records)} total records)",
|
description=f"OData sync: {created} created, {updated} updated ({len(records)} total records)",
|
||||||
)
|
)
|
||||||
parts = await imported_part_crud.list_all(db)
|
|
||||||
total = await imported_part_crud.count(db)
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request, "admin/_imported_parts_table.html",
|
||||||
"admin/_imported_parts_table.html",
|
await _imported_table_ctx(sync_result=f"Sync complete: {created} created, {updated} updated."),
|
||||||
{
|
|
||||||
"parts": parts,
|
|
||||||
"total": total,
|
|
||||||
"sync_result": f"Sync complete: {created} created, {updated} updated.",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
parts = await imported_part_crud.list_all(db)
|
|
||||||
total = await imported_part_crud.count(db)
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request, "admin/_imported_parts_table.html",
|
||||||
"admin/_imported_parts_table.html",
|
await _imported_table_ctx(sync_error=f"Sync failed: {exc}"),
|
||||||
{"parts": parts, "total": total, "sync_error": f"Sync failed: {exc}"},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1128,20 +1160,35 @@ async def refresh_imported_parts(
|
|||||||
async def filter_imported_parts(
|
async def filter_imported_parts(
|
||||||
request: Request,
|
request: Request,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = _PAGE_SIZE,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: User = Depends(web_require("imported_parts:read")),
|
_: User = Depends(web_require("imported_parts:read")),
|
||||||
):
|
):
|
||||||
parts = await imported_part_crud.list_filtered(db, description=description)
|
total = await imported_part_crud.count_filtered(db, description=description)
|
||||||
|
pagination = _paginate(
|
||||||
|
"/admin/imported-parts/filter",
|
||||||
|
{"description": description},
|
||||||
|
page, page_size, total,
|
||||||
|
"#imported-parts-table-card",
|
||||||
|
)
|
||||||
|
parts = await imported_part_crud.list_filtered(
|
||||||
|
db, description=description, skip=pagination["skip"], limit=page_size,
|
||||||
|
)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_imported_parts_table_card.html",
|
"admin/_imported_parts_table_card.html",
|
||||||
{"parts": parts},
|
{"parts": parts, "pagination": pagination},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- Augmented Parts ---------------------------------------------------------
|
# --- Augmented Parts ---------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
async def _skipped_colours(db: AsyncSession) -> list[str]:
|
||||||
|
return await augmented_part_crud.distinct_unlinked_colours(db)
|
||||||
|
|
||||||
|
|
||||||
async def _augmented_part_form_ctx(db: AsyncSession) -> dict:
|
async def _augmented_part_form_ctx(db: AsyncSession) -> dict:
|
||||||
return {
|
return {
|
||||||
"part_colours": await part_colour_crud.list_all(db),
|
"part_colours": await part_colour_crud.list_all(db),
|
||||||
@@ -1159,8 +1206,10 @@ async def augmented_parts_page(
|
|||||||
user: User = Depends(web_require("augmented_parts:read")),
|
user: User = Depends(web_require("augmented_parts:read")),
|
||||||
):
|
):
|
||||||
ctx = await _augmented_part_form_ctx(db)
|
ctx = await _augmented_part_form_ctx(db)
|
||||||
|
total = await augmented_part_crud.count_filtered(db)
|
||||||
|
ctx["augmented_parts"] = await augmented_part_crud.list_filtered(db, limit=_PAGE_SIZE)
|
||||||
|
ctx["pagination"] = _paginate("/admin/augmented-parts/filter", {}, 1, _PAGE_SIZE, total, "#augmented-parts-table-card")
|
||||||
ctx["user"] = user
|
ctx["user"] = user
|
||||||
ctx["augmented_parts"] = await augmented_part_crud.list_all(db)
|
|
||||||
return templates.TemplateResponse(request, "admin/augmented_parts.html", ctx)
|
return templates.TemplateResponse(request, "admin/augmented_parts.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@@ -1195,10 +1244,12 @@ async def create_augmented_part(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
form_ctx = await _augmented_part_form_ctx(db)
|
form_ctx = await _augmented_part_form_ctx(db)
|
||||||
|
total = await augmented_part_crud.count_filtered(db)
|
||||||
|
pagination = _paginate("/admin/augmented-parts/filter", {}, 1, _PAGE_SIZE, total, "#augmented-parts-table-card")
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_augmented_parts_table.html",
|
"admin/_augmented_parts_table.html",
|
||||||
{**form_ctx, "augmented_parts": await augmented_part_crud.list_all(db)},
|
{**form_ctx, "augmented_parts": await augmented_part_crud.list_filtered(db, limit=_PAGE_SIZE), "pagination": pagination},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1228,12 +1279,15 @@ async def sync_augmented_parts(
|
|||||||
sync_result = None
|
sync_result = None
|
||||||
sync_error = f"Sync failed: {exc}"
|
sync_error = f"Sync failed: {exc}"
|
||||||
form_ctx = await _augmented_part_form_ctx(db)
|
form_ctx = await _augmented_part_form_ctx(db)
|
||||||
|
total = await augmented_part_crud.count_filtered(db)
|
||||||
|
pagination = _paginate("/admin/augmented-parts/filter", {}, 1, _PAGE_SIZE, total, "#augmented-parts-table-card")
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_augmented_parts_table.html",
|
"admin/_augmented_parts_table.html",
|
||||||
{
|
{
|
||||||
**form_ctx,
|
**form_ctx,
|
||||||
"augmented_parts": await augmented_part_crud.list_all(db),
|
"augmented_parts": await augmented_part_crud.list_filtered(db, limit=_PAGE_SIZE),
|
||||||
|
"pagination": pagination,
|
||||||
"sync_result": sync_result,
|
"sync_result": sync_result,
|
||||||
"sync_error": sync_error,
|
"sync_error": sync_error,
|
||||||
},
|
},
|
||||||
@@ -1249,22 +1303,49 @@ async def filter_augmented_parts(
|
|||||||
scope_model_id: str = "",
|
scope_model_id: str = "",
|
||||||
part_type_id: str = "",
|
part_type_id: str = "",
|
||||||
sub_part_type_id: str = "",
|
sub_part_type_id: str = "",
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = _PAGE_SIZE,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: User = Depends(web_require("augmented_parts:read")),
|
_: User = Depends(web_require("augmented_parts:read")),
|
||||||
):
|
):
|
||||||
|
colour_id_int = int(colour_id) if colour_id else None
|
||||||
|
manufacturer_id_int = int(manufacturer_id) if manufacturer_id else None
|
||||||
|
scope_model_id_int = int(scope_model_id) if scope_model_id else None
|
||||||
|
part_type_id_int = int(part_type_id) if part_type_id else None
|
||||||
|
sub_part_type_id_int = int(sub_part_type_id) if sub_part_type_id else None
|
||||||
|
total = await augmented_part_crud.count_filtered(
|
||||||
|
db,
|
||||||
|
description=description,
|
||||||
|
colour_id=colour_id_int,
|
||||||
|
manufacturer_id=manufacturer_id_int,
|
||||||
|
scope_model_id=scope_model_id_int,
|
||||||
|
part_type_id=part_type_id_int,
|
||||||
|
sub_part_type_id=sub_part_type_id_int,
|
||||||
|
)
|
||||||
|
filter_params = {
|
||||||
|
"description": description,
|
||||||
|
"colour_id": colour_id,
|
||||||
|
"manufacturer_id": manufacturer_id,
|
||||||
|
"scope_model_id": scope_model_id,
|
||||||
|
"part_type_id": part_type_id,
|
||||||
|
"sub_part_type_id": sub_part_type_id,
|
||||||
|
}
|
||||||
|
pagination = _paginate("/admin/augmented-parts/filter", filter_params, page, page_size, total, "#augmented-parts-table-card")
|
||||||
parts = await augmented_part_crud.list_filtered(
|
parts = await augmented_part_crud.list_filtered(
|
||||||
db,
|
db,
|
||||||
description=description,
|
description=description,
|
||||||
colour_id=int(colour_id) if colour_id else None,
|
colour_id=colour_id_int,
|
||||||
manufacturer_id=int(manufacturer_id) if manufacturer_id else None,
|
manufacturer_id=manufacturer_id_int,
|
||||||
scope_model_id=int(scope_model_id) if scope_model_id else None,
|
scope_model_id=scope_model_id_int,
|
||||||
part_type_id=int(part_type_id) if part_type_id else None,
|
part_type_id=part_type_id_int,
|
||||||
sub_part_type_id=int(sub_part_type_id) if sub_part_type_id else None,
|
sub_part_type_id=sub_part_type_id_int,
|
||||||
|
skip=pagination["skip"],
|
||||||
|
limit=page_size,
|
||||||
)
|
)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_augmented_parts_table_card.html",
|
"admin/_augmented_parts_table_card.html",
|
||||||
{"augmented_parts": parts},
|
{"augmented_parts": parts, "pagination": pagination},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1275,8 +1356,10 @@ async def skipped_parts_page(
|
|||||||
user: User = Depends(web_require("augmented_parts:read")),
|
user: User = Depends(web_require("augmented_parts:read")),
|
||||||
):
|
):
|
||||||
form_ctx = await _augmented_part_form_ctx(db)
|
form_ctx = await _augmented_part_form_ctx(db)
|
||||||
unlinked = await augmented_part_crud.get_unlinked_imported_parts(db)
|
total_unlinked = await augmented_part_crud.count_unlinked_filtered(db)
|
||||||
skipped_colours = sorted({p.TBXColour for p in unlinked if p.TBXColour})
|
skipped_colours = await _skipped_colours(db)
|
||||||
|
pagination = _paginate("/admin/augmented-parts/skipped/filter", {}, 1, _PAGE_SIZE, total_unlinked, "#skipped-parts-table-card")
|
||||||
|
unlinked = await augmented_part_crud.get_unlinked_imported_parts_filtered(db, limit=_PAGE_SIZE)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/augmented_parts_skipped.html",
|
"admin/augmented_parts_skipped.html",
|
||||||
@@ -1284,10 +1367,11 @@ async def skipped_parts_page(
|
|||||||
"user": user,
|
"user": user,
|
||||||
**form_ctx,
|
**form_ctx,
|
||||||
"unlinked_parts": unlinked,
|
"unlinked_parts": unlinked,
|
||||||
"any_unlinked": bool(unlinked),
|
"any_unlinked": bool(total_unlinked),
|
||||||
"skipped_colours": skipped_colours,
|
"skipped_colours": skipped_colours,
|
||||||
"filter_description": "",
|
"filter_description": "",
|
||||||
"filter_colour": "",
|
"filter_colour": "",
|
||||||
|
"pagination": pagination,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1340,18 +1424,21 @@ async def sync_skipped_parts(
|
|||||||
sync_result = f"{synced} part(s) synced successfully."
|
sync_result = f"{synced} part(s) synced successfully."
|
||||||
|
|
||||||
form_ctx = await _augmented_part_form_ctx(db)
|
form_ctx = await _augmented_part_form_ctx(db)
|
||||||
unlinked = await augmented_part_crud.get_unlinked_imported_parts(db)
|
total_unlinked = await augmented_part_crud.count_unlinked_filtered(db)
|
||||||
skipped_colours = sorted({p.TBXColour for p in unlinked if p.TBXColour})
|
colours = await _skipped_colours(db)
|
||||||
|
pagination = _paginate("/admin/augmented-parts/skipped/filter", {}, 1, _PAGE_SIZE, total_unlinked, "#skipped-parts-table-card")
|
||||||
|
unlinked = await augmented_part_crud.get_unlinked_imported_parts_filtered(db, limit=_PAGE_SIZE)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_skipped_parts_table.html",
|
"admin/_skipped_parts_table.html",
|
||||||
{
|
{
|
||||||
**form_ctx,
|
**form_ctx,
|
||||||
"unlinked_parts": unlinked,
|
"unlinked_parts": unlinked,
|
||||||
"any_unlinked": bool(unlinked),
|
"any_unlinked": bool(total_unlinked),
|
||||||
"skipped_colours": skipped_colours,
|
"skipped_colours": colours,
|
||||||
"filter_description": "",
|
"filter_description": "",
|
||||||
"filter_colour": "",
|
"filter_colour": "",
|
||||||
|
"pagination": pagination,
|
||||||
"sync_result": sync_result,
|
"sync_result": sync_result,
|
||||||
"sync_error": sync_error,
|
"sync_error": sync_error,
|
||||||
},
|
},
|
||||||
@@ -1363,16 +1450,21 @@ async def filter_skipped_parts(
|
|||||||
request: Request,
|
request: Request,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
colour: str = "",
|
colour: str = "",
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = _PAGE_SIZE,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
_: User = Depends(web_require("augmented_parts:read")),
|
_: User = Depends(web_require("augmented_parts:read")),
|
||||||
):
|
):
|
||||||
|
total = await augmented_part_crud.count_unlinked_filtered(db, description=description, colour=colour)
|
||||||
|
filter_params = {"description": description, "colour": colour}
|
||||||
|
pagination = _paginate("/admin/augmented-parts/skipped/filter", filter_params, page, page_size, total, "#skipped-parts-table-card")
|
||||||
unlinked = await augmented_part_crud.get_unlinked_imported_parts_filtered(
|
unlinked = await augmented_part_crud.get_unlinked_imported_parts_filtered(
|
||||||
db, description=description, colour=colour
|
db, description=description, colour=colour, skip=pagination["skip"], limit=page_size,
|
||||||
)
|
)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"admin/_skipped_parts_table_card.html",
|
"admin/_skipped_parts_table_card.html",
|
||||||
{"unlinked_parts": unlinked},
|
{"unlinked_parts": unlinked, "pagination": pagination},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user