Add collector PC->printer links and searchable custom fields
Collector: the computers collector schema gains defaultprinter and printers; apply_collector_payload resolves each reported identifier to a printer asset (windowsname/hostname/sharename/assetnumber/IP, first-hit case-insensitive) and idempotently syncs relationships - defaultprinter (directional) for the default, connectedto for the rest. Collector-created rows are tagged so a re-report archives dropped links while manual relationships are never touched; unresolved identifiers warn instead of failing. Both PC and printer detail pages show the links via the shared relationships card (no frontend change). GE-Enforce Win32_Printer collection snippet documented. Searchable custom fields: a per-field searchable flag (migration 7d24); global search matches custom-field values on flagged active fields and routes each hit to the asset detail page, reusing the existing (type,id) dedupe and search_<type>_enabled domain filter. Searchable toggle on the Custom Fields settings page. 822 tests pass; both verified live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ from sqlalchemy.orm import joinedload
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models import (
|
||||
Application, Setting,
|
||||
Asset, AssetType, Communication, Vendor, Model
|
||||
Asset, AssetType, Communication, Vendor, Model,
|
||||
CustomField, CustomFieldValue
|
||||
)
|
||||
from shopdb.core.api.settings import get_cached_settings
|
||||
from shopdb.utils.responses import success_response
|
||||
@@ -343,6 +344,41 @@ def _search_measuringtools(query, search_term):
|
||||
return results
|
||||
|
||||
|
||||
def _search_customfields(query, search_term):
|
||||
"""Search custom-field VALUES for fields flagged searchable.
|
||||
|
||||
Joins CustomFieldValue -> CustomField (searchable + active) -> Asset (active)
|
||||
and emits a normal asset result routed to that asset's detail page. Results
|
||||
are deduped against built-in-field asset matches by the shared (type, id) key
|
||||
in global_search, and the asset's search_<type>_enabled domain toggle is
|
||||
applied by the same end-of-request domain filter. The matched field label is
|
||||
put in the subtitle so a value hit reads sensibly.
|
||||
"""
|
||||
results = []
|
||||
try:
|
||||
rows = db.session.query(Asset, CustomField).join(
|
||||
CustomFieldValue, CustomFieldValue.assetid == Asset.assetid
|
||||
).join(
|
||||
CustomField, CustomField.fieldid == CustomFieldValue.fieldid
|
||||
).options(
|
||||
joinedload(Asset.assettype),
|
||||
joinedload(Asset.location),
|
||||
).filter(
|
||||
Asset.isactive == True,
|
||||
CustomField.searchable == True,
|
||||
CustomField.isactive == True,
|
||||
CustomFieldValue.value.ilike(search_term),
|
||||
).limit(15).all()
|
||||
|
||||
for asset, field in rows:
|
||||
result = _get_asset_result(asset, query, relevance=40)
|
||||
result['subtitle'] = field.label
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Custom field search failed: {e}")
|
||||
return results
|
||||
|
||||
|
||||
def _search_by_ip(query, search_term):
|
||||
"""Search Communications table for IP address matches."""
|
||||
results = []
|
||||
@@ -799,6 +835,7 @@ def global_search():
|
||||
results.extend(_search_employees(query, search_term))
|
||||
results.extend(_search_assets(query, search_term))
|
||||
results.extend(_search_measuringtools(query, search_term))
|
||||
results.extend(_search_customfields(query, search_term))
|
||||
results.extend(_search_notifications(query, search_term))
|
||||
results.extend(_search_hostnames(query, search_term))
|
||||
results.extend(_search_vendor_model_type(query, search_term))
|
||||
|
||||
Reference in New Issue
Block a user