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 }
|
||||
actix-admin-macros = { version = "0.3.0", path = "actix_admin_macros" }
|
||||
derive_more = "0.99.17"
|
||||
regex = "1.7.1"
|
||||
|
||||
[dev-dependencies]
|
||||
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"
|
||||
quote = "1.0"
|
||||
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 not_empty: Option<()>,
|
||||
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>,
|
||||
|
||||
// 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
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use regex::Regex;
|
||||
};
|
||||
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_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 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);
|
||||
@ -202,12 +204,20 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
||||
let list_hide_columns = [
|
||||
#(#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 field_name = field_name.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 {
|
||||
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,
|
||||
list_sort_position: list_sort_position,
|
||||
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
|
||||
|
@ -16,7 +16,8 @@ pub struct ModelField {
|
||||
pub file_upload: bool,
|
||||
pub not_empty: bool,
|
||||
pub list_sort_position: usize,
|
||||
pub list_hide_column: bool
|
||||
pub list_hide_column: bool,
|
||||
pub list_regex_mask: String
|
||||
}
|
||||
|
||||
impl ModelField {
|
||||
|
@ -56,6 +56,12 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
let is_not_empty = actix_admin_attr
|
||||
.clone()
|
||||
.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| {
|
||||
attr.list_sort_position.map_or( 99, |attr_field| {
|
||||
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,
|
||||
not_empty: is_not_empty,
|
||||
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)
|
||||
} else {
|
||||
|
@ -12,7 +12,7 @@ pub struct Model {
|
||||
pub id: i32,
|
||||
pub comment: String,
|
||||
#[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,
|
||||
#[sea_orm(column_type = "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_class", get_html_input_class);
|
||||
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 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())
|
||||
}
|
||||
|
||||
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>(
|
||||
value: &tera::Value,
|
||||
_: &HashMap<String, tera::Value, S>,
|
||||
|
@ -1,18 +1,20 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::prelude::*;
|
||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize};
|
||||
use tera::{Context};
|
||||
use crate::{prelude::*};
|
||||
use tera::Context;
|
||||
|
||||
use crate::ActixAdminViewModelTrait;
|
||||
use crate::ActixAdminViewModel;
|
||||
use super::{
|
||||
add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE,
|
||||
};
|
||||
use crate::ActixAdminModel;
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::ActixAdminViewModel;
|
||||
use crate::ActixAdminViewModelTrait;
|
||||
use crate::TERA;
|
||||
use actix_session::{Session};
|
||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized, Params, DEFAULT_ENTITIES_PER_PAGE};
|
||||
use actix_session::Session;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
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>(
|
||||
session: Session,
|
||||
req: HttpRequest,
|
||||
@ -38,7 +56,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
let entity_name = E::get_entity_name();
|
||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||
let mut errors: Vec<ActixAdminError> = Vec::new();
|
||||
|
||||
|
||||
let mut ctx = Context::new();
|
||||
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 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 result = E::list(db, page, entities_per_page, &search, &sort_by, &sort_order).await;
|
||||
|
||||
match result {
|
||||
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);
|
||||
ctx.insert("entities", &entities);
|
||||
ctx.insert("num_pages", &num_pages);
|
||||
ctx.insert("page", &std::cmp::min(num_pages, page));
|
||||
page = std::cmp::min(page, num_pages);
|
||||
let min_show_page = if &page < &5 { 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) };
|
||||
let min_show_page = if &page < &5 {
|
||||
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("max_show_page", &max_show_page);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.insert("entities", &Vec::<ActixAdminModel>::new());
|
||||
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() {
|
||||
false => HttpResponse::InternalServerError(),
|
||||
true => HttpResponse::Ok()
|
||||
};
|
||||
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||
true => HttpResponse::Ok(),
|
||||
};
|
||||
let notifications: Vec<ActixAdminNotification> = errors
|
||||
.into_iter()
|
||||
.map(|err| ActixAdminNotification::from(err))
|
||||
.collect();
|
||||
|
||||
@ -97,7 +131,10 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
ctx.insert("notifications", ¬ifications);
|
||||
ctx.insert("entities_per_page", &entities_per_page);
|
||||
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("sort_by", &sort_by);
|
||||
ctx.insert("sort_order", &sort_order);
|
||||
@ -106,4 +143,4 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
.render("list.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
Ok(http_response_code.content_type("text/html").body(body))
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::HashMap;
|
||||
@ -84,7 +85,9 @@ pub struct ActixAdminViewModelField {
|
||||
pub is_option: bool,
|
||||
pub field_type: ActixAdminViewModelFieldType,
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user