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
//! Request-aware scoping for the admin list view: a hook that reads
//! the request and narrows the rows the list may show.
//!
//! An inventory registry of `(table, fn(&Parts) -> Vec<Filter>)`
//! entries. The list view walks it per request and adds the filters to
//! its WHERE clause. Use it to hide soft-deleted rows, show only rows
//! the current user owns, or scope by tenant.
//!
//! `manager_fn` and the `show_only` / `read_only` allowlists are the
//! compile-time equivalents; this hook sees the request.
//!
//! ## Usage
//!
//! ```ignore
//! use axum::http::request::Parts;
//! use rustango::core::{Filter, Op, SqlValue};
//!
//! fn only_published(_parts: &Parts) -> Vec<Filter> {
//! vec![Filter::new("is_published", Op::Eq, SqlValue::Bool(true))]
//! }
//!
//! rustango::register_admin_queryset!("blog_post", only_published);
//! ```
//!
//! `/admin/blog_post` now always adds `AND is_published = true` to its
//! SELECT, whatever else the URL asks for. Several hooks on one table
//! compose: their filters are added in registration order.
//!
//! A hook returns predicates rather than a whole queryset because the
//! list view already builds its SELECT from the admin config, the
//! query params and custom filters. Adding `Filter`s to that pipeline
//! composes with search, facets, ordering, pagination and
//! `list_select_related`, none of which need to know hooks exist.
//!
//! Hooks are `fn` pointers for the same const-storage reason as
//! [`crate::admin::custom_views::CustomViewHandler`] and
//! [`crate::template_extensions::TeraFilterFn`].
use Parts;
use crateFilter;
/// An admin queryset hook. Takes the request [`Parts`], which hold
/// everything but the body, and returns extra [`Filter`]s for the list
/// view's WHERE clause.
///
/// A plain `fn` pointer, not `Arc<dyn Fn>`, so the registration fits
/// in inventory's const storage.
pub type QuerySetHookFn = fn ;
/// One queryset hook registration, collected by inventory via
/// [`crate::register_admin_queryset!`].
collect!;
/// Every hook registered for `table`, in registration order. The
/// filters are ANDed, so the order only shows up in the logs.
/// Register an admin queryset hook for one model.
///
/// See the module docs for what it does. The hook must be a plain
/// `fn`, or a closure with no captures, that coerces to
/// [`QuerySetHookFn`].
///
/// ```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::new(
/// "owner_id",
/// rustango::core::Op::Eq,
/// rustango::core::SqlValue::I64(user_id),
/// )]
/// }
/// rustango::register_admin_queryset!("blog_post", only_owned);
/// ```