GlobalSearchView (horilla.contrib.generics) queries models in FEATURE_REGISTRY[‘global_search_models’], with company scoping and view_own_* row filters via get_allowed_user_ids().

This post is Part 14 of 28 in the Horilla CRM Technical Blog series.

What is global search in Horilla CRM?

Global search in Horilla CRM enables you to:

  • GlobalSearchView with HTMX results panel
  • Per-model search fields configuration
  • Company-scoped results
  • Keyboard shortcut integration

Global search entry point

Global search is implemented by GlobalSearchView in horilla.contrib.generics.views.global_search. The view provides a single discovery endpoint across models that have deliberately opted in; it is not a database-wide text search and should never become one by accident. The URL is named generics:global_search, allowing templates and JavaScript to resolve it through the project URL namespace rather than hard-coding a route.

The registration source is FEATURE_REGISTRY. Models eligible for cross-application lookup are collected from its global_search_models capability. This keeps search participation aligned with the feature system: an installed model is not automatically searchable, and a model with sensitive records can remain excluded. New modules should register a model only after its detail route, label representation, and row-level permissions have been verified.

LayerResponsibility
FEATURE_REGISTRYDeclares models under global_search_models
GlobalSearchViewBuilds permitted, ranked result sets
Model permissionsDetermines whether a user can view model records
Row-level filteringRestricts results to owned or assigned objects
Result rendererEscapes labels and links to authorized detail pages

Candidate fields and query construction

For each registered model, the default discovery strategy examines the first five CharField and TextField fields. This bounded selection is a practical safeguard: search remains predictable and avoids generating a broad OR condition across every column in every app. Field order therefore matters. Put meaningful identifiers—such as name, title, email, subject, or reference code—early in a model definition where appropriate, but do not reorder database fields solely for search without evaluating migrations and existing conventions.

The search view should combine matching fields within a model while retaining model boundaries in result metadata. It should not expose hidden or internal text merely because it is a TextField. For example, if a model places an internal notes field among its first five text fields, opt out or provide a search-safe pattern rather than assuming all stored text is appropriate for a global result snippet.

from django.urls import reverse


search_url = reverse("generics:global_search")

The user input is a search term, not a filter expression. Escape output, apply normal ORM parameterization, trim empty queries, and set a reasonable minimum or result cap in the UI. Resist implementing raw SQL LIKE concatenation or interpreting user input as lookup syntax.

Permission and own-record filtering

Search must apply access control before returning a result. Model-level view_<model> permission determines whether a model is available at all. When a user has only an own-record permission such as view_own_<model>, the search flow uses get_allowed_user_ids to identify records that are valid under the model’s ownership rules. This is a critical distinction: filtering by created_by=request.user is not a substitute because many CRM models are owned through fields such as owner, assigned_to, or another configured OWNER_FIELDS relation.

allowed_user_ids = get_allowed_user_ids(
    user=request.user,
    model=Lead,
)

The shown call expresses the role of the helper; use the project’s actual call pattern and model access utilities when implementing a new integration. The important sequencing is: determine eligible records, then apply the text search, then limit and render results. Applying ownership after an unrestricted match can cause incorrect counts, unnecessary database work, and accidental leakage through snippets or timing.

Ranking and response shape

Results should include a model label, human-readable record label, URL, and only safe secondary context. A lead named “Acme” and an account named “Acme” should remain distinguishable, while users should not need to know internal app labels. Prefer exact or prefix matches ahead of broad substring matches when the database and current implementation support it; do not claim relevance scores that have not been computed.

Architecture note: global search aggregates a small number of independently filtered querysets, rather than joining unrelated tables into a universal record table. That makes feature registration and model permissions explicit, though it means performance must be measured as the number of registered models grows. Set per-model and total limits, avoid N+1 URL or label generation, and profile with realistic tenant data.

Adding a searchable model safely

Register a model under global_search_models in FEATURE_REGISTRY, then test it with a user who has full view permission, a user with only own-record permission, and a user with no permission. Verify a result’s URL reaches a protected detail view; hiding a result after it is clicked is not an adequate security design. Test duplicate display labels, Unicode search terms, empty queries, and records from another company where relevant.

Finally, keep search results resilient to optional modules. A feature registry entry may refer to an app whose routes or presentation integration are unavailable in a partial deployment. The global view should skip unavailable registrations safely and log actionable diagnostics for administrators rather than allowing one extension model to break every search request.

Benefits of Global Search in Horilla CRM

  • Find any record from one search box
  • Opt-in per model via registration
  • Consistent UX across modules

Add global_search to register_model_for_feature, and your model appears in the universal search bar.

Continue the series

Previous: Part 13 — Horilla CRM Activities Explained: Calls, Meetings, Tasks, and More

Next: Part 15 — Import and Export in Horilla CRM: CSV Pipelines and Scheduled Exports

More posts are on the Horilla Blogs; share feedback on GitHub.

Share this article