actix_admin/routes/
list.rs

1use crate::prelude::*;
2use crate::view_model::ActixAdminViewModelParams;
3use actix_web::http::header::ContentDisposition;
4use actix_web::{error, web, Error, HttpRequest, HttpResponse};
5use csv::WriterBuilder;
6use sea_orm::DatabaseConnection;
7use serde_derive::{Deserialize, Serialize};
8use std::fmt;
9use tera::Context;
10use urlencoding::decode;
11
12use super::helpers::{add_default_context, SearchParams};
13use super::{
14    add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE,
15};
16use crate::ActixAdminModel;
17use crate::ActixAdminNotification;
18use crate::ActixAdminViewModel;
19use crate::ActixAdminViewModelTrait;
20use actix_session::Session;
21
22#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
23pub enum SortOrder {
24    Asc,
25    Desc,
26}
27
28impl fmt::Display for SortOrder {
29    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30        match self {
31            SortOrder::Asc => write!(f, "Asc"),
32            SortOrder::Desc => write!(f, "Desc"),
33        }
34    }
35}
36
37pub fn replace_regex(view_model: &ActixAdminViewModel, models: &mut Vec<ActixAdminModel>) {
38    for field in view_model.fields.iter().filter(|f| f.list_regex_mask.is_some()) {
39        let regex = field.list_regex_mask.as_ref().unwrap();
40        for model in models.iter_mut() {
41            if let Some(value) = model.values.get_mut(&field.field_name) {
42                *value = regex.replace_all(value, "****").to_string();
43            }
44        }
45    }
46}
47
48pub async fn export_csv<E: ActixAdminViewModelTrait>(
49    session: Session,
50    req: HttpRequest,
51    data: web::Data<ActixAdmin>,
52    db: web::Data<DatabaseConnection>,
53) -> Result<HttpResponse, Error> {
54    let actix_admin = &data.into_inner();
55    let entity_name = E::get_entity_name();
56    let view_model = actix_admin.view_models.get(&entity_name).unwrap();
57
58    if !user_can_access_page(&session, actix_admin, view_model) {
59        return render_unauthorized(&Context::new(), actix_admin);
60    }
61
62    let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
63    let search = params.search.clone().unwrap_or_default();
64    let sort_by = params.sort_by.clone().unwrap_or_else(|| view_model.primary_key.clone());
65    let sort_order = params.sort_order.clone().unwrap_or(SortOrder::Asc);
66
67    let actixadminfilters = decode(req.query_string())
68        .unwrap()
69        .split('&')
70        .filter_map(|qf| {
71            if qf.starts_with("filter_") {
72                let mut kv = qf.split('=');
73                Some(ActixAdminViewModelFilter {
74                    name: kv.next()?.strip_prefix("filter_")?.to_string(),
75                    value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
76                    values: None,
77                    filter_type: None,
78                    foreign_key: None,
79                })
80            } else {
81                None
82            }
83        })
84        .collect();
85
86    let params = ActixAdminViewModelParams {
87        page: None,
88        entities_per_page: None,
89        viewmodel_filter: actixadminfilters,
90        search,
91        sort_by,
92        sort_order,
93        tenant_ref: actix_admin.configuration.user_tenant_ref.and_then(|f| f(&session)),
94    };
95
96    let entities = match E::list(&db, &params).await {
97        Ok(res) => {
98            let mut entities = res.1;
99            replace_regex(view_model, &mut entities);
100            entities
101        }
102        Err(_) => Vec::new(),
103    };
104
105    let mut writer = WriterBuilder::new().from_writer(vec![]);
106    let mut fields = view_model.fields.iter().map(|f| f.field_name.clone()).collect::<Vec<_>>();
107    fields.insert(0, view_model.primary_key.clone());
108    writer.write_record(&fields).ok();
109
110    for entity in entities {
111        let mut values = vec![entity.primary_key.unwrap_or_default()];
112        for field in view_model.fields {
113            let value = entity.values.get(&field.field_name).cloned().unwrap_or_default();
114            values.push(entity.fk_values.get(&field.field_name).cloned().unwrap_or(value));
115        }
116        writer.write_record(&values).ok();
117    }
118
119    Ok(HttpResponse::Ok()
120        .content_type("text/csv")
121        .insert_header(ContentDisposition::attachment("export.csv"))
122        .body(writer.into_inner().unwrap()))
123}
124
125pub async fn list<E: ActixAdminViewModelTrait>(
126    session: Session,
127    req: HttpRequest,
128    data: web::Data<ActixAdmin>,
129    db: web::Data<DatabaseConnection>,
130) -> Result<HttpResponse, Error> {
131    let actix_admin = &data.into_inner();
132    let entity_name = E::get_entity_name();
133    let view_model = actix_admin.view_models.get(&entity_name).unwrap();
134    let mut ctx = Context::new();
135    add_auth_context(&session, actix_admin, &mut ctx);
136
137    if !user_can_access_page(&session, actix_admin, view_model) {
138        return render_unauthorized(&ctx, actix_admin);
139    }
140
141    let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
142    let page = params.page.unwrap_or(1);
143    let entities_per_page = params.entities_per_page.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
144    let search = params.search.clone().unwrap_or_default();
145    let sort_by = params.sort_by.clone().unwrap_or_else(|| view_model.primary_key.clone());
146    let sort_order = params.sort_order.clone().unwrap_or(SortOrder::Asc);
147
148    let search_params = SearchParams::from_params(&params, view_model);
149
150    let actixadminfilters = decode(req.query_string())
151        .unwrap()
152        .split('&')
153        .filter_map(|qf| {
154            if qf.starts_with("filter_") {
155                let mut kv = qf.split('=');
156                Some(ActixAdminViewModelFilter {
157                    name: kv.next()?.strip_prefix("filter_")?.to_string(),
158                    value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
159                    values: None,
160                    filter_type: None,
161                    foreign_key: None,
162                })
163            } else {
164                None
165            }
166        })
167        .collect();
168
169    let params = ActixAdminViewModelParams {
170        page: Some(page),
171        entities_per_page: Some(entities_per_page),
172        viewmodel_filter: actixadminfilters,
173        search,
174        sort_by,
175        sort_order,
176        tenant_ref: actix_admin.configuration.user_tenant_ref.and_then(|f| f(&session)),
177    };
178
179    let (num_pages, mut entities) = match E::list(&db, &params).await {
180        Ok(res) => res,
181        Err(e) => {
182            ctx.insert("entities", &Vec::<ActixAdminModel>::new());
183            ctx.insert("num_pages", &0);
184            ctx.insert("min_show_page", &1);
185            ctx.insert("max_show_page", &1);
186            ctx.insert("page", &1);
187            ctx.insert("notifications", &[ActixAdminNotification::from(e)]);
188            return Ok(HttpResponse::InternalServerError().content_type("text/html").body(
189                actix_admin.tera.render("list.html", &ctx).map_err(error::ErrorInternalServerError)?,
190            ));
191        }
192    };
193
194    replace_regex(view_model, &mut entities);
195    let num_pages = num_pages.unwrap_or(1);
196    let page = page.min(num_pages);
197    let min_show_page = (page.saturating_sub(4)).max(1);
198    let max_show_page = (page + 4).min(num_pages);
199
200    add_default_context(
201        &mut ctx,
202        req,
203        view_model,
204        entity_name,
205        actix_admin,
206        Vec::new(),
207        &search_params,
208    );
209
210    ctx.insert("entities", &entities);
211    ctx.insert("num_pages", &num_pages);
212    ctx.insert("min_show_page", &min_show_page);
213    ctx.insert("max_show_page", &max_show_page);
214    ctx.insert("viewmodel_filter", &E::get_viewmodel_filter(&db).await);
215
216    Ok(HttpResponse::Ok().content_type("text/html").body(
217        actix_admin.tera.render("list.html", &ctx).map_err(|err| error::ErrorInternalServerError(format!("{:?}", err)))?,
218    ))
219}