reinhardt_admin/core/
model_admin.rs1use async_trait::async_trait;
6
7#[async_trait]
11pub trait ModelAdmin: Send + Sync {
12 fn model_name(&self) -> &str;
14
15 fn table_name(&self) -> &str {
19 ""
22 }
23
24 fn pk_field(&self) -> &str {
28 "id"
29 }
30
31 fn list_display(&self) -> Vec<&str> {
33 vec!["id"]
34 }
35
36 fn list_filter(&self) -> Vec<&str> {
38 vec![]
39 }
40
41 fn search_fields(&self) -> Vec<&str> {
43 vec![]
44 }
45
46 fn fields(&self) -> Option<Vec<&str>> {
48 None
49 }
50
51 fn readonly_fields(&self) -> Vec<&str> {
53 vec![]
54 }
55
56 fn ordering(&self) -> Vec<&str> {
58 vec!["-id"]
59 }
60
61 fn list_per_page(&self) -> Option<usize> {
63 None
64 }
65
66 async fn has_view_permission(&self, _user: &(dyn std::any::Any + Send + Sync)) -> bool {
71 true
72 }
73
74 async fn has_add_permission(&self, _user: &(dyn std::any::Any + Send + Sync)) -> bool {
79 true
80 }
81
82 async fn has_change_permission(&self, _user: &(dyn std::any::Any + Send + Sync)) -> bool {
87 true
88 }
89
90 async fn has_delete_permission(&self, _user: &(dyn std::any::Any + Send + Sync)) -> bool {
95 true
96 }
97}
98
99#[derive(Debug, Clone)]
118pub struct ModelAdminConfig {
119 model_name: String,
120 table_name: Option<String>,
121 pk_field: String,
122 list_display: Vec<String>,
123 list_filter: Vec<String>,
124 search_fields: Vec<String>,
125 fields: Option<Vec<String>>,
126 readonly_fields: Vec<String>,
127 ordering: Vec<String>,
128 list_per_page: Option<usize>,
129}
130
131impl ModelAdminConfig {
132 pub fn new(model_name: impl Into<String>) -> Self {
143 Self {
144 model_name: model_name.into(),
145 table_name: None,
146 pk_field: "id".into(),
147 list_display: vec!["id".into()],
148 list_filter: vec![],
149 search_fields: vec![],
150 fields: None,
151 readonly_fields: vec![],
152 ordering: vec!["-id".into()],
153 list_per_page: None,
154 }
155 }
156
157 pub fn builder() -> ModelAdminConfigBuilder {
170 ModelAdminConfigBuilder::default()
171 }
172
173 pub fn with_list_display(mut self, fields: Vec<impl Into<String>>) -> Self {
175 self.list_display = fields.into_iter().map(Into::into).collect();
176 self
177 }
178
179 pub fn with_list_filter(mut self, fields: Vec<impl Into<String>>) -> Self {
181 self.list_filter = fields.into_iter().map(Into::into).collect();
182 self
183 }
184
185 pub fn with_search_fields(mut self, fields: Vec<impl Into<String>>) -> Self {
187 self.search_fields = fields.into_iter().map(Into::into).collect();
188 self
189 }
190}
191
192#[async_trait]
193impl ModelAdmin for ModelAdminConfig {
194 fn model_name(&self) -> &str {
195 &self.model_name
196 }
197
198 fn table_name(&self) -> &str {
199 self.table_name
200 .as_deref()
201 .unwrap_or(self.model_name.as_str())
202 }
203
204 fn pk_field(&self) -> &str {
205 &self.pk_field
206 }
207
208 fn list_display(&self) -> Vec<&str> {
209 self.list_display.iter().map(|s| s.as_str()).collect()
210 }
211
212 fn list_filter(&self) -> Vec<&str> {
213 self.list_filter.iter().map(|s| s.as_str()).collect()
214 }
215
216 fn search_fields(&self) -> Vec<&str> {
217 self.search_fields.iter().map(|s| s.as_str()).collect()
218 }
219
220 fn fields(&self) -> Option<Vec<&str>> {
221 self.fields
222 .as_ref()
223 .map(|f| f.iter().map(|s| s.as_str()).collect())
224 }
225
226 fn readonly_fields(&self) -> Vec<&str> {
227 self.readonly_fields.iter().map(|s| s.as_str()).collect()
228 }
229
230 fn ordering(&self) -> Vec<&str> {
231 self.ordering.iter().map(|s| s.as_str()).collect()
232 }
233
234 fn list_per_page(&self) -> Option<usize> {
235 self.list_per_page
236 }
237}
238
239#[derive(Debug, Default)]
241pub struct ModelAdminConfigBuilder {
242 model_name: Option<String>,
243 table_name: Option<String>,
244 pk_field: Option<String>,
245 list_display: Option<Vec<String>>,
246 list_filter: Option<Vec<String>>,
247 search_fields: Option<Vec<String>>,
248 fields: Option<Vec<String>>,
249 readonly_fields: Option<Vec<String>>,
250 ordering: Option<Vec<String>>,
251 list_per_page: Option<usize>,
252}
253
254impl ModelAdminConfigBuilder {
255 pub fn model_name(mut self, name: impl Into<String>) -> Self {
257 self.model_name = Some(name.into());
258 self
259 }
260
261 pub fn table_name(mut self, name: impl Into<String>) -> Self {
265 self.table_name = Some(name.into());
266 self
267 }
268
269 pub fn pk_field(mut self, field: impl Into<String>) -> Self {
273 self.pk_field = Some(field.into());
274 self
275 }
276
277 pub fn list_display(mut self, fields: Vec<impl Into<String>>) -> Self {
279 self.list_display = Some(fields.into_iter().map(Into::into).collect());
280 self
281 }
282
283 pub fn list_filter(mut self, fields: Vec<impl Into<String>>) -> Self {
285 self.list_filter = Some(fields.into_iter().map(Into::into).collect());
286 self
287 }
288
289 pub fn search_fields(mut self, fields: Vec<impl Into<String>>) -> Self {
291 self.search_fields = Some(fields.into_iter().map(Into::into).collect());
292 self
293 }
294
295 pub fn fields(mut self, fields: Vec<impl Into<String>>) -> Self {
297 self.fields = Some(fields.into_iter().map(Into::into).collect());
298 self
299 }
300
301 pub fn readonly_fields(mut self, fields: Vec<impl Into<String>>) -> Self {
303 self.readonly_fields = Some(fields.into_iter().map(Into::into).collect());
304 self
305 }
306
307 pub fn ordering(mut self, fields: Vec<impl Into<String>>) -> Self {
309 self.ordering = Some(fields.into_iter().map(Into::into).collect());
310 self
311 }
312
313 pub fn list_per_page(mut self, count: usize) -> Self {
315 self.list_per_page = Some(count);
316 self
317 }
318
319 pub fn build(self) -> ModelAdminConfig {
325 ModelAdminConfig {
326 model_name: self.model_name.expect("model_name is required"),
327 table_name: self.table_name,
328 pk_field: self.pk_field.unwrap_or_else(|| "id".into()),
329 list_display: self.list_display.unwrap_or_else(|| vec!["id".into()]),
330 list_filter: self.list_filter.unwrap_or_default(),
331 search_fields: self.search_fields.unwrap_or_default(),
332 fields: self.fields,
333 readonly_fields: self.readonly_fields.unwrap_or_default(),
334 ordering: self.ordering.unwrap_or_else(|| vec!["-id".into()]),
335 list_per_page: self.list_per_page,
336 }
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use rstest::rstest;
344
345 #[rstest]
346 fn test_model_admin_config_creation() {
347 let admin = ModelAdminConfig::new("User");
348 assert_eq!(admin.model_name(), "User");
349 assert_eq!(admin.list_display(), vec!["id"]);
350 assert_eq!(admin.list_filter(), Vec::<&str>::new());
351 }
352
353 #[rstest]
354 fn test_model_admin_config_builder() {
355 let admin = ModelAdminConfig::builder()
356 .model_name("User")
357 .list_display(vec!["id", "username", "email"])
358 .list_filter(vec!["is_active"])
359 .search_fields(vec!["username", "email"])
360 .list_per_page(50)
361 .build();
362
363 assert_eq!(admin.model_name(), "User");
364 assert_eq!(admin.list_display(), vec!["id", "username", "email"]);
365 assert_eq!(admin.list_filter(), vec!["is_active"]);
366 assert_eq!(admin.search_fields(), vec!["username", "email"]);
367 assert_eq!(admin.list_per_page(), Some(50));
368 }
369
370 #[rstest]
371 fn test_with_methods() {
372 let admin = ModelAdminConfig::new("Post")
373 .with_list_display(vec!["id", "title", "author"])
374 .with_list_filter(vec!["status", "created_at"])
375 .with_search_fields(vec!["title", "content"]);
376
377 assert_eq!(admin.list_display(), vec!["id", "title", "author"]);
378 assert_eq!(admin.list_filter(), vec!["status", "created_at"]);
379 assert_eq!(admin.search_fields(), vec!["title", "content"]);
380 }
381
382 #[rstest]
383 #[should_panic(expected = "model_name is required")]
384 fn test_builder_without_model_name() {
385 ModelAdminConfig::builder().build();
386 }
387}