add regex attr for field masking in list
This commit is contained in:
parent
a9b6dd01df
commit
c6118f52c4
@ -34,6 +34,7 @@ serde_derive = "^1.0.152"
|
|||||||
sea-orm = { version = "^0.10.6", features = [], default-features = false }
|
sea-orm = { version = "^0.10.6", features = [], default-features = false }
|
||||||
actix-admin-macros = { version = "0.3.0", path = "actix_admin_macros" }
|
actix-admin-macros = { version = "0.3.0", path = "actix_admin_macros" }
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
|
regex = "1.7.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sea-orm = { version = "^0.10.6", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
sea-orm = { version = "^0.10.6", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||||
|
@ -16,4 +16,5 @@ proc-macro = true
|
|||||||
bae = "0.1.7"
|
bae = "0.1.7"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||||
proc-macro2 = { version = "1.0.36", default-features = false }
|
proc-macro2 = { version = "1.0.36", default-features = false }
|
||||||
|
regex = "1.7.1"
|
@ -18,7 +18,8 @@ pub mod derive_attr {
|
|||||||
pub file_upload: Option<()>,
|
pub file_upload: Option<()>,
|
||||||
pub not_empty: Option<()>,
|
pub not_empty: Option<()>,
|
||||||
pub list_sort_position: Option<syn::LitStr>,
|
pub list_sort_position: Option<syn::LitStr>,
|
||||||
pub list_hide_column: Option<()>
|
pub list_hide_column: Option<()>,
|
||||||
|
pub list_regex_mask: Option<syn::LitStr>
|
||||||
//pub inner_type: Option<syn::Type>,
|
//pub inner_type: Option<syn::Type>,
|
||||||
|
|
||||||
// Anything that implements `syn::parse::Parse` is supported.
|
// Anything that implements `syn::parse::Parse` is supported.
|
||||||
|
@ -38,6 +38,7 @@ pub fn derive_actix_admin(_input: proc_macro::TokenStream) -> proc_macro::TokenS
|
|||||||
EntityTrait
|
EntityTrait
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use regex::Regex;
|
||||||
};
|
};
|
||||||
proc_macro::TokenStream::from(expanded)
|
proc_macro::TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
@ -145,6 +146,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
|
|
||||||
let field_names = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.ident.to_string() });
|
let field_names = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.ident.to_string() });
|
||||||
let field_html_input_type = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.html_input_type.to_string() });
|
let field_html_input_type = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.html_input_type.to_string() });
|
||||||
|
let field_list_regex_mask = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.list_regex_mask.to_string() });
|
||||||
let field_select_list = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.select_list.to_string() });
|
let field_select_list = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.select_list.to_string() });
|
||||||
let is_option_list = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.is_option() });
|
let is_option_list = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.is_option() });
|
||||||
let fields_for_create_model = get_fields_for_create_model(&fields);
|
let fields_for_create_model = get_fields_for_create_model(&fields);
|
||||||
@ -202,12 +204,20 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
let list_hide_columns = [
|
let list_hide_columns = [
|
||||||
#(#fields_list_hide_column),*
|
#(#fields_list_hide_column),*
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let list_regex_masks = [
|
||||||
|
#(#field_list_regex_mask),*
|
||||||
|
];
|
||||||
|
|
||||||
for (field_name, html_input_type, select_list, is_option_list, fields_type_path, is_textarea, is_file_upload, list_sort_position, list_hide_column) in actix_admin::prelude::izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths, fields_textareas, fields_fileupload, list_sort_positions, list_hide_columns) {
|
for (field_name, html_input_type, select_list, is_option_list, fields_type_path, is_textarea, is_file_upload, list_sort_position, list_hide_column, list_regex_mask) in actix_admin::prelude::izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths, fields_textareas, fields_fileupload, list_sort_positions, list_hide_columns, list_regex_masks) {
|
||||||
|
|
||||||
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
||||||
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
||||||
let html_input_type = html_input_type.replace('"', "").replace(' ', "").to_string();
|
let html_input_type = html_input_type.replace('"', "").replace(' ', "").to_string();
|
||||||
|
let mut list_regex_mask_regex = None;
|
||||||
|
if list_regex_mask != "" {
|
||||||
|
list_regex_mask_regex = Some(Regex::new(list_regex_mask).unwrap());
|
||||||
|
};
|
||||||
|
|
||||||
vec.push(ActixAdminViewModelField {
|
vec.push(ActixAdminViewModelField {
|
||||||
field_name: field_name,
|
field_name: field_name,
|
||||||
@ -216,7 +226,8 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
is_option: is_option_list,
|
is_option: is_option_list,
|
||||||
list_sort_position: list_sort_position,
|
list_sort_position: list_sort_position,
|
||||||
field_type: ActixAdminViewModelFieldType::get_field_type(fields_type_path, select_list, is_textarea, is_file_upload),
|
field_type: ActixAdminViewModelFieldType::get_field_type(fields_type_path, select_list, is_textarea, is_file_upload),
|
||||||
list_hide_column: list_hide_column
|
list_hide_column: list_hide_column,
|
||||||
|
list_regex_mask: list_regex_mask_regex
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
vec
|
vec
|
||||||
|
@ -16,7 +16,8 @@ pub struct ModelField {
|
|||||||
pub file_upload: bool,
|
pub file_upload: bool,
|
||||||
pub not_empty: bool,
|
pub not_empty: bool,
|
||||||
pub list_sort_position: usize,
|
pub list_sort_position: usize,
|
||||||
pub list_hide_column: bool
|
pub list_hide_column: bool,
|
||||||
|
pub list_regex_mask: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelField {
|
impl ModelField {
|
||||||
|
@ -56,6 +56,12 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
let is_not_empty = actix_admin_attr
|
let is_not_empty = actix_admin_attr
|
||||||
.clone()
|
.clone()
|
||||||
.map_or(false, |attr| attr.not_empty.is_some());
|
.map_or(false, |attr| attr.not_empty.is_some());
|
||||||
|
let list_regex_mask = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
||||||
|
attr.list_regex_mask
|
||||||
|
.map_or("".to_string(), |attr_field| {
|
||||||
|
(LitStr::from(attr_field)).value()
|
||||||
|
})
|
||||||
|
});
|
||||||
let list_sort_position: usize = actix_admin_attr.clone().map_or(99, |attr| {
|
let list_sort_position: usize = actix_admin_attr.clone().map_or(99, |attr| {
|
||||||
attr.list_sort_position.map_or( 99, |attr_field| {
|
attr.list_sort_position.map_or( 99, |attr_field| {
|
||||||
let sort_pos = LitStr::from(attr_field).value().parse::<usize>();
|
let sort_pos = LitStr::from(attr_field).value().parse::<usize>();
|
||||||
@ -90,7 +96,8 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
file_upload: is_file_upload,
|
file_upload: is_file_upload,
|
||||||
not_empty: is_not_empty,
|
not_empty: is_not_empty,
|
||||||
list_sort_position: list_sort_position,
|
list_sort_position: list_sort_position,
|
||||||
list_hide_column: is_list_hide_column
|
list_hide_column: is_list_hide_column,
|
||||||
|
list_regex_mask: list_regex_mask
|
||||||
};
|
};
|
||||||
Some(model_field)
|
Some(model_field)
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,7 +12,7 @@ pub struct Model {
|
|||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub comment: String,
|
pub comment: String,
|
||||||
#[sea_orm(column_type = "Text")]
|
#[sea_orm(column_type = "Text")]
|
||||||
#[actix_admin(html_input_type = "email")]
|
#[actix_admin(html_input_type = "email", list_regex_mask= "^([a-zA-Z]*)")]
|
||||||
pub user: String,
|
pub user: String,
|
||||||
#[sea_orm(column_type = "DateTime")]
|
#[sea_orm(column_type = "DateTime")]
|
||||||
pub insert_date: DateTime,
|
pub insert_date: DateTime,
|
||||||
|
24
src/lib.rs
24
src/lib.rs
@ -63,6 +63,7 @@ lazy_static! {
|
|||||||
tera.register_filter("get_html_input_type", get_html_input_type);
|
tera.register_filter("get_html_input_type", get_html_input_type);
|
||||||
tera.register_filter("get_html_input_class", get_html_input_class);
|
tera.register_filter("get_html_input_class", get_html_input_class);
|
||||||
tera.register_filter("get_icon", get_icon);
|
tera.register_filter("get_icon", get_icon);
|
||||||
|
tera.register_filter("get_regex_val", get_regex_val);
|
||||||
|
|
||||||
let list_html = include_str!("templates/list.html");
|
let list_html = include_str!("templates/list.html");
|
||||||
let create_or_edit_html = include_str!("templates/create_or_edit.html");
|
let create_or_edit_html = include_str!("templates/create_or_edit.html");
|
||||||
@ -134,6 +135,29 @@ pub fn get_icon<S: BuildHasher>(
|
|||||||
Ok(to_value(font_awesome_icon).unwrap())
|
Ok(to_value(font_awesome_icon).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_regex_val<S: BuildHasher>(
|
||||||
|
value: &tera::Value,
|
||||||
|
args: &HashMap<String, tera::Value, S>,
|
||||||
|
) -> Result<tera::Value> {
|
||||||
|
let field = try_get_value!("get_regex_val", "value", ActixAdminViewModelField, value);
|
||||||
|
|
||||||
|
let s = args.get("values");
|
||||||
|
let field_val = s.unwrap().get(&field.field_name);
|
||||||
|
|
||||||
|
println!("field {} regex {:?}", field.field_name, field.list_regex_mask);
|
||||||
|
match (field_val, field.list_regex_mask) {
|
||||||
|
(Some(val), Some(r)) => {
|
||||||
|
let val_str = val.to_string();
|
||||||
|
let is_match = r.is_match(&val_str);
|
||||||
|
println!("is match: {}, regex {}", is_match, r.to_string());
|
||||||
|
let result_str = r.replace_all(&val_str, "*");
|
||||||
|
return Ok(to_value(result_str).unwrap());
|
||||||
|
},
|
||||||
|
(Some(val), None) => { return Ok(to_value(val).unwrap()); },
|
||||||
|
(_, _) => panic!("key {} not found in model values", &field.field_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_html_input_type<S: BuildHasher>(
|
pub fn get_html_input_type<S: BuildHasher>(
|
||||||
value: &tera::Value,
|
value: &tera::Value,
|
||||||
_: &HashMap<String, tera::Value, S>,
|
_: &HashMap<String, tera::Value, S>,
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||||
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::{Deserialize};
|
use tera::Context;
|
||||||
use tera::{Context};
|
|
||||||
use crate::{prelude::*};
|
|
||||||
|
|
||||||
use crate::ActixAdminViewModelTrait;
|
use super::{
|
||||||
use crate::ActixAdminViewModel;
|
add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE,
|
||||||
|
};
|
||||||
use crate::ActixAdminModel;
|
use crate::ActixAdminModel;
|
||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
|
use crate::ActixAdminViewModel;
|
||||||
|
use crate::ActixAdminViewModelTrait;
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
use actix_session::{Session};
|
use actix_session::Session;
|
||||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized, Params, DEFAULT_ENTITIES_PER_PAGE};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum SortOrder {
|
pub enum SortOrder {
|
||||||
@ -29,6 +31,22 @@ impl fmt::Display for SortOrder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_regex(view_model: &ActixAdminViewModel, models: &mut Vec<ActixAdminModel>) {
|
||||||
|
view_model
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.list_regex_mask.is_some())
|
||||||
|
.for_each(|f| {
|
||||||
|
models.into_iter().for_each(|m| {
|
||||||
|
let regex = f.list_regex_mask.as_ref().unwrap();
|
||||||
|
let field = f;
|
||||||
|
let vals = &mut m.values;
|
||||||
|
vals.entry(field.field_name.to_string())
|
||||||
|
.and_modify(|f| *f = regex.replace_all(f, "****").to_string());
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
session: Session,
|
session: Session,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@ -38,7 +56,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
let mut errors: Vec<ActixAdminError> = Vec::new();
|
let mut errors: Vec<ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
|
|
||||||
@ -58,23 +76,38 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let search = params.search.clone().unwrap_or(String::new());
|
let search = params.search.clone().unwrap_or(String::new());
|
||||||
|
|
||||||
let db = data.get_db();
|
let db = data.get_db();
|
||||||
let sort_by = params.sort_by.clone().unwrap_or(view_model.primary_key.to_string());
|
let sort_by = params
|
||||||
|
.sort_by
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(view_model.primary_key.to_string());
|
||||||
let sort_order = params.sort_order.as_ref().unwrap_or(&SortOrder::Asc);
|
let sort_order = params.sort_order.as_ref().unwrap_or(&SortOrder::Asc);
|
||||||
|
|
||||||
let result = E::list(db, page, entities_per_page, &search, &sort_by, &sort_order).await;
|
let result = E::list(db, page, entities_per_page, &search, &sort_by, &sort_order).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
let entities = res.1;
|
let mut entities = res.1;
|
||||||
|
replace_regex(view_model, &mut entities);
|
||||||
let num_pages = std::cmp::max(res.0, 1);
|
let num_pages = std::cmp::max(res.0, 1);
|
||||||
ctx.insert("entities", &entities);
|
ctx.insert("entities", &entities);
|
||||||
ctx.insert("num_pages", &num_pages);
|
ctx.insert("num_pages", &num_pages);
|
||||||
ctx.insert("page", &std::cmp::min(num_pages, page));
|
ctx.insert("page", &std::cmp::min(num_pages, page));
|
||||||
page = std::cmp::min(page, num_pages);
|
page = std::cmp::min(page, num_pages);
|
||||||
let min_show_page = if &page < &5 { 1 } else { let max_page = &page - &5; max_page };
|
let min_show_page = if &page < &5 {
|
||||||
let max_show_page = if &page >= &num_pages { std::cmp::max(1, num_pages - 1) } else { let max_page = &page + &5; std::cmp::min(num_pages - 1, max_page) };
|
1
|
||||||
|
} else {
|
||||||
|
let max_page = &page - &5;
|
||||||
|
max_page
|
||||||
|
};
|
||||||
|
let max_show_page = if &page >= &num_pages {
|
||||||
|
std::cmp::max(1, num_pages - 1)
|
||||||
|
} else {
|
||||||
|
let max_page = &page + &5;
|
||||||
|
std::cmp::min(num_pages - 1, max_page)
|
||||||
|
};
|
||||||
ctx.insert("min_show_page", &min_show_page);
|
ctx.insert("min_show_page", &min_show_page);
|
||||||
ctx.insert("max_show_page", &max_show_page);
|
ctx.insert("max_show_page", &max_show_page);
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ctx.insert("entities", &Vec::<ActixAdminModel>::new());
|
ctx.insert("entities", &Vec::<ActixAdminModel>::new());
|
||||||
ctx.insert("num_pages", &0);
|
ctx.insert("num_pages", &0);
|
||||||
@ -87,9 +120,10 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
|
|
||||||
let mut http_response_code = match errors.is_empty() {
|
let mut http_response_code = match errors.is_empty() {
|
||||||
false => HttpResponse::InternalServerError(),
|
false => HttpResponse::InternalServerError(),
|
||||||
true => HttpResponse::Ok()
|
true => HttpResponse::Ok(),
|
||||||
};
|
};
|
||||||
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
let notifications: Vec<ActixAdminNotification> = errors
|
||||||
|
.into_iter()
|
||||||
.map(|err| ActixAdminNotification::from(err))
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -97,7 +131,10 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("notifications", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
ctx.insert("entities_per_page", &entities_per_page);
|
ctx.insert("entities_per_page", &entities_per_page);
|
||||||
ctx.insert("render_partial", &render_partial);
|
ctx.insert("render_partial", &render_partial);
|
||||||
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));
|
ctx.insert(
|
||||||
|
"view_model",
|
||||||
|
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
||||||
|
);
|
||||||
ctx.insert("search", &search);
|
ctx.insert("search", &search);
|
||||||
ctx.insert("sort_by", &sort_by);
|
ctx.insert("sort_by", &sort_by);
|
||||||
ctx.insert("sort_order", &sort_order);
|
ctx.insert("sort_order", &sort_order);
|
||||||
@ -106,4 +143,4 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
.render("list.html", &ctx)
|
.render("list.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(http_response_code.content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use regex::Regex;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -84,7 +85,9 @@ pub struct ActixAdminViewModelField {
|
|||||||
pub is_option: bool,
|
pub is_option: bool,
|
||||||
pub field_type: ActixAdminViewModelFieldType,
|
pub field_type: ActixAdminViewModelFieldType,
|
||||||
pub list_sort_position: usize,
|
pub list_sort_position: usize,
|
||||||
pub list_hide_column: bool
|
pub list_hide_column: bool,
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub list_regex_mask: Option<Regex>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActixAdminViewModelFieldType {
|
impl ActixAdminViewModelFieldType {
|
||||||
|
Loading…
Reference in New Issue
Block a user