1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Django-shape `ModelAdmin.get_queryset(self, request)` — issue #360.
//!
//! Django lets a ModelAdmin scope its list view by overriding
//! `get_queryset(request)` — return a filtered queryset based on
//! the request user, headers, or any other per-request signal.
//! Common uses:
//!
//! - Hide soft-deleted rows.
//! - Show only the rows the current user owns.
//! - Filter by tenant when the admin runs outside the tenancy
//! middleware.
//! - Restrict to a date window pulled from a session cookie.
//!
//! rustango's admin had `manager_fn` (compile-time QuerySet
//! shortcut) and the per-Builder `show_only` / `read_only`
//! allowlists, but no *request-aware* hook. This module ships the
//! equivalent: an inventory-collected registry of
//! `(table, fn(&Parts) -> Vec<Filter>)` entries that the admin
//! list view walks at request time and appends to the WHERE
//! clause.
//!
//! ## Usage
//!
//! ```ignore
//! use axum::http::request::Parts;
//! use rustango::core::{Filter, Op, SqlValue};
//!
//! fn only_published(_parts: &Parts) -> Vec<Filter> {
//! vec![Filter {
//! column: "is_published",
//! op: Op::Eq,
//! value: SqlValue::Bool(true),
//! }]
//! }
//!
//! rustango::register_admin_queryset!("blog_post", only_published);
//! ```
//!
//! Now `/admin/blog_post` automatically appends `AND is_published =
//! true` to its SELECT, no matter what other filter params the URL
//! carries. Multiple registrations on the same table compose — the
//! filters from every hook are appended in registration order.
//!
//! ## Why a hook returning predicates instead of a full QuerySet?
//!
//! rustango's admin already builds its `SELECT` from the
//! `AdminConfig` + per-request query params + custom filters. The
//! hook integrates by adding more `Filter`s to that same pipeline,
//! which is the smallest possible surface — it composes with
//! search, facets, ordering, pagination, and `list_select_related`
//! without any of them needing to know hooks exist. Django's full-
//! QuerySet override is more flexible in principle but the
//! incremental-filter shape covers 95% of real use cases.
//!
//! ## Why inventory storage requires `fn` pointers
//!
//! Same const-constructible reason as
//! [`crate::admin::custom_views::CustomViewHandler`] and
//! [`crate::template_extensions::TeraFilterFn`].
use Parts;
use crateFilter;
/// Signature of an admin queryset hook. Receives the per-request
/// [`Parts`] (headers + uri + method + extensions, minus body) and
/// returns extra [`Filter`]s to append to the list view's WHERE
/// clause.
///
/// Plain `fn` pointer (not `Arc<dyn Fn>`) so the registration can
/// live in `inventory::submit!`'s `static` storage.
pub type QuerySetHookFn = fn ;
/// One per-table queryset hook registration. Inventory-collected
/// via [`crate::register_admin_queryset!`].
collect!;
/// Return every hook registered for `table`, in registration order.
/// The admin list view applies them in this order; the surface is
/// commutative for ANDed predicates so the order is observable
/// only through tracing logs.
/// Register an admin queryset hook scoped to one model.
///
/// See the module-level docs for semantics + use cases. The hook
/// callable must be a plain `fn` or non-capturing closure that
/// coerces to [`QuerySetHookFn`] — `inventory::submit!`'s static
/// storage doesn't accept `Arc<dyn Fn>`.
///
/// ```ignore
/// fn only_owned(parts: &axum::http::request::Parts) -> Vec<rustango::core::Filter> {
/// let user_id = parts.extensions.get::<UserId>().copied().unwrap_or(0);
/// vec![rustango::core::Filter {
/// column: "owner_id",
/// op: rustango::core::Op::Eq,
/// value: rustango::core::SqlValue::I64(user_id),
/// }]
/// }
/// rustango::register_admin_queryset!("blog_post", only_owned);
/// ```