diff --git a/src/builder.rs b/src/builder.rs index 231ceba..d4aaca4 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -3,8 +3,13 @@ use actix_web::{web, Route }; use std::collections::HashMap; use std::fs; use crate::routes::{ - create_get, create_post, delete, delete_many, edit_get, edit_post, index, list, not_found, show, download + create_get, create_post, delete, delete_many, download, edit_get, edit_post, index, list, + not_found, show, }; +use crate::{prelude::*, routes::delete_file, ActixAdminMenuElement}; +use std::hash::BuildHasher; +use tera::Tera; +use tera::{to_value, try_get_value, Result}; /// Represents a builder entity which helps generating the ActixAdmin configuration pub struct ActixAdminBuilder { @@ -21,9 +26,7 @@ pub trait ActixAdminBuilderTrait { &mut self, view_model: &ActixAdminViewModel, ); - fn add_entity_to_category< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_entity_to_category( &mut self, view_model: &ActixAdminViewModel, category_name: &str, @@ -41,20 +44,16 @@ pub trait ActixAdminBuilderTrait { path: &str, route: Route, add_to_menu: bool, - category: &str + category: &str, ); - fn add_custom_handler_for_entity< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_custom_handler_for_entity( &mut self, menu_element_name: &str, path: &str, route: Route, - add_to_menu: bool + add_to_menu: bool, ); - fn add_custom_handler_for_entity_in_category< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_custom_handler_for_entity_in_category( &mut self, menu_element_name: &str, path: &str, @@ -67,6 +66,139 @@ pub trait ActixAdminBuilderTrait { fn get_actix_admin(&self) -> ActixAdmin; } +fn get_html_input_class( + value: &tera::Value, + _: &HashMap, +) -> Result { + let field = try_get_value!( + "get_html_input_class", + "value", + ActixAdminViewModelField, + value + ); + let html_input_type = match field.field_type { + ActixAdminViewModelFieldType::TextArea => "textarea", + ActixAdminViewModelFieldType::Checkbox => "checkbox", + _ => "input", + }; + + Ok(to_value(html_input_type).unwrap()) +} + +fn get_icon( + value: &tera::Value, + _: &HashMap, +) -> Result { + let field = try_get_value!("get_icon", "value", String, value); + let font_awesome_icon = match field.as_str() { + "true" => "", + "false" => "", + _ => panic!("not implemented icon"), + }; + + Ok(to_value(font_awesome_icon).unwrap()) +} + +fn get_regex_val( + value: &tera::Value, + args: &HashMap, +) -> Result { + 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), + } +} + +fn get_html_input_type( + value: &tera::Value, + _: &HashMap, +) -> Result { + let field = try_get_value!( + "get_html_input_type", + "value", + ActixAdminViewModelField, + value + ); + + // TODO: convert to option + if field.html_input_type != "" { + return Ok(to_value(field.html_input_type).unwrap()); + } + + let html_input_type = match field.field_type { + ActixAdminViewModelFieldType::Text => "text", + ActixAdminViewModelFieldType::DateTime => "datetime-local", + ActixAdminViewModelFieldType::Date => "date", + ActixAdminViewModelFieldType::Checkbox => "checkbox", + ActixAdminViewModelFieldType::FileUpload => "file", + _ => "text", + }; + + Ok(to_value(html_input_type).unwrap()) +} + +fn get_tera() -> Tera { + let mut tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/templates/*.html")).unwrap(); + 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"); + let base_html = include_str!("templates/base.html"); + let head_html = include_str!("templates/head.html"); + let index_html = include_str!("templates/index.html"); + let loader_html = include_str!("templates/loader.html"); + let navbar_html = include_str!("templates/navbar.html"); + let not_found_html = include_str!("templates/not_found.html"); + let show_html = include_str!("templates/show.html"); + let unauthorized_html = include_str!("templates/unauthorized.html"); + + // form elements + let checkbox_html = include_str!("templates/form_elements/checkbox.html"); + let input_html = include_str!("templates/form_elements/input.html"); + let selectlist_html = include_str!("templates/form_elements/selectlist.html"); + + let _res = tera.add_raw_templates(vec![ + ("base.html", base_html), + ("list.html", list_html), + ("create_or_edit.html", create_or_edit_html), + ("head.html", head_html), + ("index.html", index_html), + ("loader.html", loader_html), + ("navbar.html", navbar_html), + ("not_found.html", not_found_html), + ("show.html", show_html), + ("unauthorized.html", unauthorized_html), + // form elements + ("form_elements/checkbox.html", checkbox_html), + ("form_elements/input.html", input_html), + ("form_elements/selectlist.html", selectlist_html), + ]); + + tera +} + +>>>>>>> 7db2971 (Trim input name) impl ActixAdminBuilderTrait for ActixAdminBuilder { fn new(configuration: ActixAdminConfiguration) -> Self { ActixAdminBuilder { @@ -89,9 +221,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { let _ = &self.add_entity_to_category::(view_model, ""); } - fn add_entity_to_category< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_entity_to_category( &mut self, view_model: &ActixAdminViewModel, category_name: &str, @@ -108,11 +238,19 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { .route("/delete/{id}", web::delete().to(delete::)) .route("/show/{id}", web::get().to(show::)) .route("/file/{id}/{column_name}", web::get().to(download::)) - .route("/file/{id}/{column_name}", web::delete().to(delete_file::)) - .default_service(web::to(not_found)) - ); + .route( + "/file/{id}/{column_name}", + web::delete().to(delete_file::), + ) + .default_service(web::to(not_found)), + ); - fs::create_dir_all(format!("{}/{}", &self.actix_admin.configuration.file_upload_directory, E::get_entity_name())).unwrap(); + fs::create_dir_all(format!( + "{}/{}", + &self.actix_admin.configuration.file_upload_directory, + E::get_entity_name() + )) + .unwrap(); let category = self.actix_admin.entity_names.get_mut(category_name); let menu_element = ActixAdminMenuElement { @@ -145,7 +283,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { path: &str, route: Route, add_to_menu: bool, - category_name: &str + category_name: &str, ) { self.custom_routes.push((path.to_string(), route)); @@ -168,7 +306,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { self.actix_admin .entity_names .insert(category_name.to_string(), entity_list); - }, + } } } } @@ -178,14 +316,12 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { menu_element_name: &str, path: &str, route: Route, - add_to_menu: bool + add_to_menu: bool, ) { self.add_custom_handler_to_category(menu_element_name, path, route, add_to_menu, ""); } - fn add_custom_handler_for_entity< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_custom_handler_for_entity( &mut self, menu_element_name: &str, path: &str, @@ -201,9 +337,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { ); } - fn add_custom_handler_for_entity_in_category< - E: ActixAdminViewModelTrait + 'static, - >( + fn add_custom_handler_for_entity_in_category( &mut self, menu_element_name: &str, path: &str, @@ -222,8 +356,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder { match existing_scope { Some(scope) => { let existing_scope = scope.route(path, route); - self.scopes - .insert(E::get_entity_name(), existing_scope); + self.scopes.insert(E::get_entity_name(), existing_scope); } _ => { let new_scope = diff --git a/src/lib.rs b/src/lib.rs index d7db717..e4d7ecd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,8 @@ use async_trait::async_trait; use derive_more::{Display, Error}; use sea_orm::DatabaseConnection; use serde_derive::Serialize; -use tera::Tera; use std::collections::HashMap; +use tera::Tera; pub mod builder; pub mod model; @@ -25,11 +25,14 @@ pub mod tera_templates; pub mod prelude { pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait}; - pub use crate::model::{ActixAdminModel, ActixAdminModelTrait, ActixAdminModelValidationTrait, ActixAdminModelFilter, ActixAdminModelFilterTrait, ActixAdminModelFilterType}; + pub use crate::model::{ + ActixAdminModel, ActixAdminModelFilter, ActixAdminModelFilterTrait, + ActixAdminModelFilterType, ActixAdminModelTrait, ActixAdminModelValidationTrait, + }; pub use crate::routes::{create_or_edit_post, get_admin_ctx, SortOrder}; pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType, - ActixAdminViewModelSerializable, ActixAdminViewModelTrait, ActixAdminViewModelFilter + ActixAdminViewModelFilter, ActixAdminViewModelSerializable, ActixAdminViewModelTrait, }; pub use crate::{hashmap, ActixAdminSelectListTrait}; pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError}; @@ -69,7 +72,7 @@ pub struct ActixAdminConfiguration { pub login_link: Option, pub logout_link: Option, pub file_upload_directory: &'static str, - pub navbar_title: &'static str + pub navbar_title: &'static str, } #[derive(Clone)] @@ -77,7 +80,7 @@ pub struct ActixAdmin { pub entity_names: HashMap>, pub view_models: HashMap, pub configuration: ActixAdminConfiguration, - pub tera: Tera + pub tera: Tera, } #[derive(PartialEq, Eq, Clone, Serialize)] @@ -117,7 +120,7 @@ pub enum ActixAdminError { impl error::ResponseError for ActixAdminError { fn error_response(&self) -> HttpResponse { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::debug!("{self:#?}"); HttpResponse::build(self.status_code()) .insert_header(ContentType::html()) diff --git a/src/model.rs b/src/model.rs index 0a3ce39..d784604 100644 --- a/src/model.rs +++ b/src/model.rs @@ -22,7 +22,7 @@ pub trait ActixAdminModelTrait { filter_values: HashMap>, search: &str, sort_by: &str, - sort_order: &SortOrder + sort_order: &SortOrder, ) -> Result<(u64, Vec), ActixAdminError>; fn get_fields() -> &'static [ActixAdminViewModelField]; fn validate_model(model: &mut ActixAdminModel); @@ -38,7 +38,7 @@ pub struct ActixAdminModelFilter { pub name: String, pub filter_type: ActixAdminModelFilterType, pub filter: fn(sea_orm::Select, Option) -> sea_orm::Select, - pub values: Option> + pub values: Option>, } #[derive(Clone, Debug, Serialize)] @@ -47,7 +47,7 @@ pub enum ActixAdminModelFilterType { SelectList, Date, DateTime, - Checkbox + Checkbox, } #[async_trait] @@ -55,7 +55,10 @@ pub trait ActixAdminModelFilterTrait { fn get_filter() -> Vec> { Vec::new() } - async fn get_filter_values(_filter: &ActixAdminModelFilter, _db: &DatabaseConnection)-> Option> { + async fn get_filter_values( + _filter: &ActixAdminModelFilter, + _db: &DatabaseConnection, + ) -> Option> { None } } @@ -66,7 +69,7 @@ impl From> for ActixAdminViewModelFilte name: filter.name, value: None, values: None, - filter_type: Some(filter.filter_type) + filter_type: Some(filter.filter_type), } } } @@ -90,7 +93,8 @@ impl ActixAdminModel { } pub async fn create_from_payload( - mut payload: Multipart, file_upload_folder: &str + mut payload: Multipart, + file_upload_folder: &str, ) -> Result { let mut hashmap = HashMap::::new(); @@ -115,21 +119,28 @@ impl ActixAdminModel { let file_exists = std::path::Path::new(&file_path).exists(); // Avoid overwriting existing files if file_exists { - filename = format!("{}_{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), filename); + filename = format!( + "{}_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + filename + ); file_path = format!("{}/{}", file_upload_folder, filename); } let file = File::create(file_path); let _res = file.unwrap().write_all(&binary_data); - hashmap.insert( - field.name().to_string(), - filename.clone() - ); + hashmap.insert(field.name().to_string(), filename.clone()); } else { let res_string = String::from_utf8(binary_data); if res_string.is_ok() { - hashmap.insert(field.name().to_string(), res_string.unwrap()); + hashmap.insert( + field.name().to_string().trim().to_string(), + res_string.unwrap(), + ); } } } @@ -146,16 +157,18 @@ impl ActixAdminModel { &self, key: &str, is_option_or_string: bool, - is_allowed_to_be_empty: bool + is_allowed_to_be_empty: bool, ) -> Result, String> { - self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| val.parse::()) + self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { + val.parse::() + }) } pub fn get_datetime( &self, key: &str, is_option_or_string: bool, - is_allowed_to_be_empty: bool + is_allowed_to_be_empty: bool, ) -> Result, String> { self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M") @@ -166,21 +179,27 @@ impl ActixAdminModel { &self, key: &str, is_option_or_string: bool, - is_allowed_to_be_empty: bool + is_allowed_to_be_empty: bool, ) -> Result, String> { self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { NaiveDate::parse_from_str(val, "%Y-%m-%d") }) } - pub fn get_bool(&self, key: &str, is_option_or_string: bool, is_allowed_to_be_empty: bool) -> Result, String> { - let val = self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty ,|val| { - if !val.is_empty() && (val == "true" || val == "yes") { - Ok(true) - } else { - Ok(false) - } - }); + pub fn get_bool( + &self, + key: &str, + is_option_or_string: bool, + is_allowed_to_be_empty: bool, + ) -> Result, String> { + let val = + self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { + if !val.is_empty() && (val == "true" || val == "yes") { + Ok(true) + } else { + Ok(false) + } + }); // not selected bool field equals to false and not to missing match val { Ok(val) => Ok(val), diff --git a/src/routes/create_or_edit_get.rs b/src/routes/create_or_edit_get.rs index 5156927..5bf6346 100644 --- a/src/routes/create_or_edit_get.rs +++ b/src/routes/create_or_edit_get.rs @@ -1,14 +1,14 @@ -use actix_web::{error, web, Error, HttpRequest, HttpResponse}; -use sea_orm::DatabaseConnection; -use tera::{Context}; -use actix_session::{Session}; +use crate::prelude::*; use crate::ActixAdminError; use crate::ActixAdminNotification; -use crate::prelude::*; +use actix_session::Session; +use actix_web::{error, web, Error, HttpRequest, HttpResponse}; +use sea_orm::DatabaseConnection; +use tera::Context; -use super::DEFAULT_ENTITIES_PER_PAGE; use super::Params; -use super::{ add_auth_context, user_can_access_page, render_unauthorized}; +use super::DEFAULT_ENTITIES_PER_PAGE; +use super::{add_auth_context, render_unauthorized, user_can_access_page}; pub async fn create_get( session: Session, @@ -20,7 +20,7 @@ pub async fn create_get( ) -> Result { let db = db.get_ref(); let model = ActixAdminModel::create_empty(); - + create_or_edit_get::(&session, req, &data, db, Ok(model)).await } @@ -30,7 +30,7 @@ pub async fn edit_get( data: web::Data, db: web::Data, _text: String, - id: web::Path + id: web::Path, ) -> Result { let db = db.get_ref(); let model = E::get_entity(db, id.into_inner()).await; @@ -38,7 +38,13 @@ pub async fn edit_get( create_or_edit_get::(&session, req, &data, db, model).await } -async fn create_or_edit_get(session: &Session, req: HttpRequest, data: &web::Data, db: &sea_orm::DatabaseConnection, model_result: Result) -> Result{ +async fn create_or_edit_get( + session: &Session, + req: HttpRequest, + data: &web::Data, + db: &sea_orm::DatabaseConnection, + model_result: Result, +) -> Result { let actix_admin = &data.get_ref(); let mut ctx = Context::new(); add_auth_context(&session, actix_admin, &mut ctx); @@ -57,9 +63,9 @@ async fn create_or_edit_get(session: &Session, req: match model_result { Ok(res) => { model = res; - }, + } Err(e) => { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{e}"); errors.push(e); model = ActixAdminModel::create_empty(); @@ -69,8 +75,9 @@ async fn create_or_edit_get(session: &Session, req: let mut http_response_code = match errors.is_empty() { true => HttpResponse::Ok(), false => HttpResponse::InternalServerError(), - }; - let notifications: Vec = errors.into_iter() + }; + let notifications: Vec = errors + .into_iter() .map(ActixAdminNotification::from) .collect(); @@ -82,10 +89,16 @@ async fn create_or_edit_get(session: &Session, req: .unwrap_or(DEFAULT_ENTITIES_PER_PAGE); let render_partial = req.headers().contains_key("HX-Target"); let search = params.search.clone().unwrap_or(String::new()); - 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); - ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone())); + ctx.insert( + "view_model", + &ActixAdminViewModelSerializable::from(view_model.clone()), + ); ctx.insert("select_lists", &E::get_select_lists(db).await?); ctx.insert("base_path", &E::get_base_path(&entity_name)); ctx.insert("model", &model); @@ -96,11 +109,12 @@ async fn create_or_edit_get(session: &Session, req: ctx.insert("sort_by", &sort_by); ctx.insert("sort_order", &sort_order); ctx.insert("page", &page); - - let body = actix_admin.tera + + let body = actix_admin + .tera .render("create_or_edit.html", &ctx) .map_err(|err| { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{err}"); error::ErrorInternalServerError(err) })?; diff --git a/src/routes/create_or_edit_post.rs b/src/routes/create_or_edit_post.rs index c5bd8b5..56ccffb 100644 --- a/src/routes/create_or_edit_post.rs +++ b/src/routes/create_or_edit_post.rs @@ -50,15 +50,7 @@ pub async fn edit_post( ), ) .await; - create_or_edit_post::( - &session, - req, - db, - model, - Some(id.into_inner()), - actix_admin, - ) - .await + create_or_edit_post::(&session, req, db, model, Some(id.into_inner()), actix_admin).await } pub async fn create_or_edit_post( @@ -82,7 +74,7 @@ pub async fn create_or_edit_post( let db = db.get_ref(); let mut model = model_res.unwrap(); - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] { tracing::debug!("Entity model: {:#?}", model); } @@ -90,7 +82,7 @@ pub async fn create_or_edit_post( if model.has_errors() { errors.push(ActixAdminError::ValidationErrors); - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] { tracing::error!("OP errors: {errors:#?}"); tracing::debug!("Model errors: {:#?}", model.errors); @@ -134,7 +126,7 @@ pub async fn create_or_edit_post( .finish()) } Err(e) => { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{e}"); errors.push(e); render_form::( @@ -197,17 +189,18 @@ async fn render_form( let notifications: Vec = errors .into_iter() .map(|err| { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{err}"); ActixAdminNotification::from(err) }) .collect(); ctx.insert("notifications", ¬ifications); - let body = actix_admin.tera + let body = actix_admin + .tera .render("create_or_edit.html", &ctx) .map_err(|err| { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{err}"); error::ErrorInternalServerError(err) })?; diff --git a/src/routes/delete.rs b/src/routes/delete.rs index 130493c..f773920 100644 --- a/src/routes/delete.rs +++ b/src/routes/delete.rs @@ -77,7 +77,11 @@ pub async fn delete_many( let db = &db.get_ref(); let entity_name = E::get_entity_name(); - let ids: Vec = form.iter().filter(|el| el.0 == "ids").map(|el| el.1.parse::().unwrap()).collect(); + let ids: Vec = form + .iter() + .filter(|el| el.0 == "ids") + .map(|el| el.1.parse::().unwrap()) + .collect(); // TODO: implement delete_many for id in ids { @@ -107,23 +111,28 @@ pub async fn delete_many( } } - let entities_per_page = form.iter() + let entities_per_page = form + .iter() .find(|el| el.0 == "entities_per_page") .map(|e| e.1.to_string()) .unwrap_or("10".to_string()); - let search = form.iter() + let search = form + .iter() .find(|el| el.0 == "search") .map(|e| e.1.to_string()) .unwrap_or_default(); - let sort_by = form.iter() + let sort_by = form + .iter() .find(|el| el.0 == "sort_by") .map(|e| e.1.to_string()) .unwrap_or("id".to_string()); - let sort_order = form.iter() + let sort_order = form + .iter() .find(|el| el.0 == "sort_order") .map(|e| e.1.to_string()) .unwrap_or("Asc".to_string()); - let page = form.iter() + let page = form + .iter() .find(|el| el.0 == "page") .map(|e| e.1.to_string()) .unwrap_or("1".to_string()); diff --git a/src/routes/file.rs b/src/routes/file.rs index a4cd66a..8ac3eed 100644 --- a/src/routes/file.rs +++ b/src/routes/file.rs @@ -1,12 +1,18 @@ -use actix_web::{web, error, Error, HttpResponse, HttpRequest}; -use actix_session::{Session}; -use sea_orm::DatabaseConnection; -use tera::{Context}; use crate::prelude::*; +use actix_session::Session; +use actix_web::{error, web, Error, HttpRequest, HttpResponse}; +use sea_orm::DatabaseConnection; +use tera::Context; -use super::{ user_can_access_page, render_unauthorized}; +use super::{render_unauthorized, user_can_access_page}; -pub async fn download(req: HttpRequest, session: Session, data: web::Data, db: web::Data, params: web::Path<(i32, String)>) -> Result { +pub async fn download( + req: HttpRequest, + session: Session, + data: web::Data, + db: web::Data, + params: web::Path<(i32, String)>, +) -> Result { let actix_admin = &data.into_inner(); let db = &db.into_inner(); @@ -16,7 +22,7 @@ pub async fn download(req: HttpRequest, session: Se if !user_can_access_page(&session, actix_admin, view_model) { return render_unauthorized(&ctx, &actix_admin); } - + let (id, column_name) = params.into_inner(); let mut errors: Vec = Vec::new(); let result = E::get_entity(db, id).await; @@ -24,25 +30,36 @@ pub async fn download(req: HttpRequest, session: Se match result { Ok(res) => { model = res; - }, + } Err(e) => { errors.push(e); model = ActixAdminModel::create_empty(); } } - let file_name = model.get_value::(&column_name, true, true).unwrap_or_default(); - let file_path = format!("{}/{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name(), file_name.unwrap_or_default()); + let file_name = model + .get_value::(&column_name, true, true) + .unwrap_or_default(); + let file_path = format!( + "{}/{}/{}", + actix_admin.configuration.file_upload_directory, + E::get_entity_name(), + file_name.unwrap_or_default() + ); let file = actix_files::NamedFile::open_async(file_path).await; match file { Ok(file) => Ok(file.into_response(&req)), - Err(_e) => Ok(HttpResponse::NotFound().content_type("text/html").body("")) + Err(_e) => Ok(HttpResponse::NotFound().content_type("text/html").body("")), } - } -pub async fn delete_file(session: Session, data: web::Data, db: web::Data, params: web::Path<(i32, String)>) -> Result { +pub async fn delete_file( + session: Session, + data: web::Data, + db: web::Data, + params: web::Path<(i32, String)>, +) -> Result { let actix_admin = &data.into_inner(); let mut ctx = Context::new(); @@ -51,7 +68,7 @@ pub async fn delete_file(session: Session, data: we if !user_can_access_page(&session, actix_admin, view_model) { return render_unauthorized(&ctx, &actix_admin); } - + let (id, column_name) = params.into_inner(); let mut errors: Vec = Vec::new(); let result = E::get_entity(db.get_ref(), id).await; @@ -59,27 +76,38 @@ pub async fn delete_file(session: Session, data: we match result { Ok(res) => { model = res; - }, + } Err(e) => { errors.push(e); model = ActixAdminModel::create_empty(); } } - let file_name = model.get_value::(&column_name, true, true).unwrap_or_default(); - let file_path = format!("{}/{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name(), file_name.unwrap_or_default()); + let file_name = model + .get_value::(&column_name, true, true) + .unwrap_or_default(); + let file_path = format!( + "{}/{}/{}", + actix_admin.configuration.file_upload_directory, + E::get_entity_name(), + file_name.unwrap_or_default() + ); std::fs::remove_file(file_path).unwrap(); model.values.remove(&column_name); let _edit_res = E::edit_entity(db.get_ref(), id, model.clone()).await; - let view_model_field = &view_model.fields.iter().find(|field| field.field_name == column_name).unwrap(); + let view_model_field = &view_model + .fields + .iter() + .find(|field| field.field_name == column_name) + .unwrap(); ctx.insert("model_field", view_model_field); ctx.insert("base_path", &E::get_base_path(&entity_name)); ctx.insert("model", &model); - let body = actix_admin.tera + let body = actix_admin + .tera .render("form_elements/input.html", &ctx) - .map_err(|err| error::ErrorInternalServerError(err))? ; + .map_err(|err| error::ErrorInternalServerError(err))?; Ok(HttpResponse::Ok().content_type("text/html").body(body)) - -} \ No newline at end of file +} diff --git a/src/routes/helpers.rs b/src/routes/helpers.rs index 42b13cc..3dfcd3a 100644 --- a/src/routes/helpers.rs +++ b/src/routes/helpers.rs @@ -1,10 +1,9 @@ -use actix_session::{Session}; -use tera::{Context}; +use actix_session::Session; +use tera::Context; use crate::prelude::*; use actix_web::{error, Error, HttpResponse}; - pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut Context) { let enable_auth = &actix_admin.configuration.enable_auth; ctx.insert("enable_auth", &enable_auth); @@ -12,26 +11,53 @@ pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut C if *enable_auth { let func = &actix_admin.configuration.user_is_logged_in.unwrap(); ctx.insert("user_is_logged_in", &func(session)); - ctx.insert("login_link", &actix_admin.configuration.login_link.as_ref().unwrap_or(&String::new())); - ctx.insert("logout_link", &actix_admin.configuration.logout_link.as_ref().unwrap_or(&String::new())); + ctx.insert( + "login_link", + &actix_admin + .configuration + .login_link + .as_ref() + .unwrap_or(&String::new()), + ); + ctx.insert( + "logout_link", + &actix_admin + .configuration + .logout_link + .as_ref() + .unwrap_or(&String::new()), + ); } } -pub fn user_can_access_page(session: &Session, actix_admin: &ActixAdmin, view_model: &ActixAdminViewModel) -> bool { +pub fn user_can_access_page( + session: &Session, + actix_admin: &ActixAdmin, + view_model: &ActixAdminViewModel, +) -> bool { let auth_is_enabled = &actix_admin.configuration.enable_auth; let user_is_logged_in = &actix_admin.configuration.user_is_logged_in; let user_can_access_view_model = &view_model.user_can_access; - match (auth_is_enabled, user_is_logged_in, user_can_access_view_model) { - (true, Some(auth_func), Some(view_model_access_func)) => auth_func(session) && view_model_access_func(session), + match ( + auth_is_enabled, + user_is_logged_in, + user_can_access_view_model, + ) { + (true, Some(auth_func), Some(view_model_access_func)) => { + auth_func(session) && view_model_access_func(session) + } (true, Some(auth_func), _) => auth_func(session), (_, _, _) => !auth_is_enabled, } } pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result { - let body = actix_admin.tera - .render("unauthorized.html", &ctx) - .map_err(|err| error::ErrorInternalServerError(err))?; - Ok(HttpResponse::Unauthorized().content_type("text/html").body(body)) -} \ No newline at end of file + let body = actix_admin + .tera + .render("unauthorized.html", &ctx) + .map_err(|err| error::ErrorInternalServerError(err))?; + Ok(HttpResponse::Unauthorized() + .content_type("text/html") + .body(body)) +} diff --git a/src/routes/index.rs b/src/routes/index.rs index 67bffb2..d2d1b61 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -1,10 +1,10 @@ +use actix_session::Session; use actix_web::{error, web, Error, HttpResponse}; -use actix_session::{Session}; -use tera::{Context}; +use tera::Context; use crate::prelude::*; -use super::{ add_auth_context }; +use super::add_auth_context; pub fn get_admin_ctx(session: Session, data: &web::Data) -> Context { let actix_admin = data.get_ref(); @@ -23,28 +23,29 @@ pub async fn index(session: Session, data: web::Data) -> Result) -> Result { - let body = data.get_ref().tera + let body = data + .get_ref() + .tera .render("not_found.html", &Context::new()) .map_err(|e| { - #[cfg(feature="enable-tracing")] + #[cfg(feature = "enable-tracing")] tracing::error!("{}", e); error::ErrorInternalServerError("Template error") })?; - Ok(HttpResponse::NotFound().content_type("text/html").body(body)) + Ok(HttpResponse::NotFound() + .content_type("text/html") + .body(body)) } - diff --git a/src/routes/list.rs b/src/routes/list.rs index 74e40ad..a300670 100644 --- a/src/routes/list.rs +++ b/src/routes/list.rs @@ -1,10 +1,10 @@ -use std::fmt; -use sea_orm::DatabaseConnection; -use urlencoding::decode; use crate::prelude::*; use actix_web::{error, web, Error, HttpRequest, HttpResponse}; -use serde_derive::{Serialize, Deserialize}; +use sea_orm::DatabaseConnection; +use serde_derive::{Deserialize, Serialize}; +use std::fmt; use tera::Context; +use urlencoding::decode; use super::{ add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE, @@ -50,7 +50,7 @@ pub async fn list( session: Session, req: HttpRequest, data: web::Data, - db: web::Data + db: web::Data, ) -> Result { let actix_admin = &data.into_inner(); let entity_name = E::get_entity_name(); @@ -88,15 +88,30 @@ pub async fn list( .map(|f| { let mut kv = f.split("="); let af = ActixAdminViewModelFilter { - name: kv.next().unwrap().strip_prefix("filter_").unwrap_or_default().to_string(), + name: kv + .next() + .unwrap() + .strip_prefix("filter_") + .unwrap_or_default() + .to_string(), value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()), values: None, - filter_type: None + filter_type: None, }; af - }).collect(); + }) + .collect(); - let result = E::list(&db, page, entities_per_page, actixadminfilters, &search, &sort_by, &sort_order).await; + let result = E::list( + &db, + page, + entities_per_page, + actixadminfilters, + &search, + &sort_by, + &sort_order, + ) + .await; match result { Ok(res) => { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 4b3c3f2..e64fe1b 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,34 +2,34 @@ mod create_or_edit_get; pub use create_or_edit_get::{create_get, edit_get}; mod create_or_edit_post; -pub use create_or_edit_post::{ create_post, edit_post, create_or_edit_post }; +pub use create_or_edit_post::{create_or_edit_post, create_post, edit_post}; mod index; -pub use index::{ index, not_found, get_admin_ctx }; +pub use index::{get_admin_ctx, index, not_found}; mod list; -pub use list::{ list, SortOrder }; +pub use list::{list, SortOrder}; mod show; pub use show::show; mod delete; -pub use delete::{ delete, delete_many }; +pub use delete::{delete, delete_many}; mod helpers; -pub use helpers::{ add_auth_context, user_can_access_page, render_unauthorized }; +pub use helpers::{add_auth_context, render_unauthorized, user_can_access_page}; mod file; -pub use file::{download, delete_file}; +pub use file::{delete_file, download}; -use serde_derive::{Deserialize}; +use serde_derive::Deserialize; #[derive(Debug, Deserialize)] pub struct Params { page: Option, entities_per_page: Option, search: Option, sort_by: Option, - sort_order: Option + sort_order: Option, } -const DEFAULT_ENTITIES_PER_PAGE: u64 = 10; \ No newline at end of file +const DEFAULT_ENTITIES_PER_PAGE: u64 = 10; diff --git a/src/routes/show.rs b/src/routes/show.rs index 29e8e38..9e35ae7 100644 --- a/src/routes/show.rs +++ b/src/routes/show.rs @@ -1,17 +1,21 @@ +use actix_session::Session; use actix_web::HttpRequest; use actix_web::{error, web, Error, HttpResponse}; -use actix_session::{Session}; use sea_orm::DatabaseConnection; -use tera::{Context}; +use tera::Context; -use crate::ActixAdminNotification; use crate::prelude::*; +use crate::ActixAdminNotification; +use super::{add_auth_context, render_unauthorized, user_can_access_page}; use super::{Params, DEFAULT_ENTITIES_PER_PAGE}; -use super::{ add_auth_context, user_can_access_page, render_unauthorized}; pub async fn show( - session: Session, req: HttpRequest, data: web::Data, id: web::Path, db: web::Data + session: Session, + req: HttpRequest, + data: web::Data, + id: web::Path, + db: web::Data, ) -> Result { let actix_admin = &data.into_inner(); @@ -21,14 +25,14 @@ pub async fn show( if !user_can_access_page(&session, actix_admin, view_model) { return render_unauthorized(&ctx, &actix_admin); } - + let mut errors: Vec = Vec::new(); let result = E::get_entity(&db, id.into_inner()).await; let model; match result { Ok(res) => { model = res; - }, + } Err(e) => { errors.push(e); model = ActixAdminModel::create_empty(); @@ -37,9 +41,10 @@ pub async fn show( let mut http_response_code = match errors.is_empty() { false => HttpResponse::InternalServerError(), - true => HttpResponse::Ok() - }; - let notifications: Vec = errors.into_iter() + true => HttpResponse::Ok(), + }; + let notifications: Vec = errors + .into_iter() .map(|err| ActixAdminNotification::from(err)) .collect(); @@ -51,11 +56,17 @@ pub async fn show( .unwrap_or(DEFAULT_ENTITIES_PER_PAGE); let render_partial = req.headers().contains_key("HX-Target"); let search = params.search.clone().unwrap_or(String::new()); - 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); ctx.insert("model", &model); - ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone())); + ctx.insert( + "view_model", + &ActixAdminViewModelSerializable::from(view_model.clone()), + ); ctx.insert("base_path", &E::get_base_path(&entity_name)); ctx.insert("entity_names", &actix_admin.entity_names); ctx.insert("notifications", ¬ifications); @@ -68,12 +79,10 @@ pub async fn show( add_auth_context(&session, actix_admin, &mut ctx); - let body = actix_admin.tera - .render("show.html", &ctx) - .map_err(|err| { - #[cfg(enable_tracing)] - tracing::error!("{err}"); - error::ErrorInternalServerError(format!("{:?}", err)) - })?; + let body = actix_admin.tera.render("show.html", &ctx).map_err(|err| { + #[cfg(enable_tracing)] + tracing::error!("{err}"); + error::ErrorInternalServerError(format!("{:?}", err)) + })?; Ok(http_response_code.content_type("text/html").body(body)) } diff --git a/src/templates/bulma/form_elements/input.html b/src/templates/bulma/form_elements/input.html index 6f3df73..4788795 100644 --- a/src/templates/bulma/form_elements/input.html +++ b/src/templates/bulma/form_elements/input.html @@ -12,21 +12,26 @@ aria-label="{{ model_field.field_name }}">{{ model.values | get(key=model_field.field_name, default="") }} {% elif model_field.field_type == "FileUpload" and model.values | get(key=model_field.field_name, default="") != "" %} {% else %} + {% if model.errors | length > 0 or model.custom_errors | length > 0 %} + {% if model.errors | get(key=model_field.field_name, default="" ) !="" or model.custom_errors | get(key=model_field.field_name, default="" ) !="" %} + is-danger + {% else %} + is-success + {% endif %} + {% endif %} + " + type="{{ model_field | get_html_input_type }}" + value="{{ model.values | get(key=model_field.field_name, default="") }}" + name="{{ model_field.field_name | trim }}" + placeholder="{{ model_field.field_name }}" + aria-label="{{ model_field.field_name }}" +> {% endif %} diff --git a/src/view_model.rs b/src/view_model.rs index a08861f..8b789c5 100644 --- a/src/view_model.rs +++ b/src/view_model.rs @@ -1,12 +1,12 @@ +use crate::ActixAdminError; +use crate::{model::ActixAdminModelFilterType, ActixAdminModel, SortOrder}; +use actix_session::Session; use async_trait::async_trait; use regex::Regex; use sea_orm::DatabaseConnection; -use serde_derive::{Serialize, Deserialize}; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::{ActixAdminModel, SortOrder, model::ActixAdminModelFilterType}; -use actix_session::{Session}; use std::convert::From; -use crate::ActixAdminError; #[async_trait(?Send)] pub trait ActixAdminViewModelTrait { @@ -17,16 +17,30 @@ pub trait ActixAdminViewModelTrait { viewmodel_filter: Vec, search: &str, sort_by: &str, - sort_order: &SortOrder + sort_order: &SortOrder, ) -> Result<(u64, Vec), ActixAdminError>; - + // TODO: Replace return value with proper Result Type containing Ok or Err - async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> Result; + async fn create_entity( + db: &DatabaseConnection, + model: ActixAdminModel, + ) -> Result; async fn delete_entity(db: &DatabaseConnection, id: i32) -> Result; - async fn get_entity(db: &DatabaseConnection, id: i32) -> Result; - async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> Result; - async fn get_select_lists(db: &DatabaseConnection) -> Result>, ActixAdminError>; - async fn get_viewmodel_filter(db: &DatabaseConnection) -> HashMap; + async fn get_entity( + db: &DatabaseConnection, + id: i32, + ) -> Result; + async fn edit_entity( + db: &DatabaseConnection, + id: i32, + model: ActixAdminModel, + ) -> Result; + async fn get_select_lists( + db: &DatabaseConnection, + ) -> Result>, ActixAdminError>; + async fn get_viewmodel_filter( + db: &DatabaseConnection, + ) -> HashMap; fn validate_entity(model: &mut ActixAdminModel); fn get_entity_name() -> String; @@ -40,10 +54,10 @@ pub trait ActixAdminViewModelTrait { pub struct ActixAdminViewModel { pub entity_name: String, pub primary_key: String, - pub fields: &'static[ActixAdminViewModelField], + pub fields: &'static [ActixAdminViewModelField], pub show_search: bool, pub user_can_access: Option bool>, - pub default_show_aside: bool + pub default_show_aside: bool, } #[derive(Clone, Debug, Serialize)] @@ -52,7 +66,7 @@ pub struct ActixAdminViewModelSerializable { pub primary_key: String, pub fields: &'static [ActixAdminViewModelField], pub show_search: bool, - pub default_show_aside: bool + pub default_show_aside: bool, } #[derive(Clone, Debug, Serialize)] @@ -60,7 +74,7 @@ pub struct ActixAdminViewModelFilter { pub name: String, pub value: Option, pub values: Option>, - pub filter_type: Option + pub filter_type: Option, } // TODO: better alternative to serialize only specific fields for ActixAdminViewModel @@ -71,7 +85,7 @@ impl From for ActixAdminViewModelSerializable { primary_key: entity.primary_key, fields: entity.fields, show_search: entity.show_search, - default_show_aside: entity.default_show_aside + default_show_aside: entity.default_show_aside, } } } @@ -86,7 +100,7 @@ pub enum ActixAdminViewModelFieldType { Time, DateTime, SelectList, - FileUpload + FileUpload, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -99,11 +113,16 @@ pub struct ActixAdminViewModelField { pub list_sort_position: usize, pub list_hide_column: bool, #[serde(skip_serializing, skip_deserializing)] - pub list_regex_mask: Option + pub list_regex_mask: Option, } impl ActixAdminViewModelFieldType { - pub fn get_field_type(type_path: &str, select_list: String, is_textarea: bool, is_file_upload: bool) -> ActixAdminViewModelFieldType { + pub fn get_field_type( + type_path: &str, + select_list: String, + is_textarea: bool, + is_file_upload: bool, + ) -> ActixAdminViewModelFieldType { if !select_list.is_empty() { return ActixAdminViewModelFieldType::SelectList; } @@ -118,12 +137,12 @@ impl ActixAdminViewModelFieldType { "i32" => ActixAdminViewModelFieldType::Number, "i64" => ActixAdminViewModelFieldType::Number, "usize" => ActixAdminViewModelFieldType::Number, - "String" => ActixAdminViewModelFieldType::Text, - "bool" => ActixAdminViewModelFieldType::Checkbox, + "String" => ActixAdminViewModelFieldType::Text, + "bool" => ActixAdminViewModelFieldType::Checkbox, "DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime, "DateTime" => ActixAdminViewModelFieldType::DateTime, "Date" => ActixAdminViewModelFieldType::Date, - _ => ActixAdminViewModelFieldType::Text + _ => ActixAdminViewModelFieldType::Text, } } -} \ No newline at end of file +} diff --git a/tests/get_request_is_success.rs b/tests/get_request_is_success.rs index 3b16bb7..10bdf42 100644 --- a/tests/get_request_is_success.rs +++ b/tests/get_request_is_success.rs @@ -4,6 +4,8 @@ use test_setup::prelude::*; #[cfg(test)] mod get_request_is_success { extern crate serde_derive; + use super::create_app; + use super::BodyTest; use actix_admin::prelude::*; use actix_web::body::to_bytes; use actix_web::test; @@ -12,8 +14,6 @@ mod get_request_is_success { use sea_orm::EntityTrait; use sea_orm::PaginatorTrait; use sea_orm::QueryOrder; - use super::create_app; - use super::BodyTest; #[actix_web::test] async fn get_admin_index() { @@ -33,12 +33,17 @@ mod get_request_is_success { let db = super::setup_db(true).await; let page = 5; let page_size = 50; // Verify with default size in list.rs - let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Post::get_entity_name(), page, page_size); + let url = format!( + "/admin/{}/list?page={}&entities_per_page={}", + crate::Post::get_entity_name(), + page, + page_size + ); - let entities = crate::Post::find() + let entities = crate::Post::find() .order_by_asc(crate::post::Column::Id) .paginate(&db, page_size) - .fetch_page(page-1) + .fetch_page(page - 1) .await .unwrap(); @@ -49,9 +54,12 @@ mod get_request_is_success { #[actix_web::test] async fn get_post_list_search() { let db = super::setup_db(true).await; - let url = format!("/admin/{}/list?search=Test%20155", crate::Post::get_entity_name()); + let url = format!( + "/admin/{}/list?search=Test%20155", + crate::Post::get_entity_name() + ); - test_response_contains(url.as_str(), &db, vec!("Test 155".to_string())).await + test_response_contains(url.as_str(), &db, vec!["Test 155".to_string()]).await } #[actix_web::test] @@ -59,7 +67,12 @@ mod get_request_is_success { let db = super::setup_db(true).await; let search_string_encoded = "Test%2015"; let entities_per_page = 11; - let url = format!("/admin/{}/list?search={}&entities_per_page={}", crate::Comment::get_entity_name(), search_string_encoded, entities_per_page); + let url = format!( + "/admin/{}/list?search={}&entities_per_page={}", + crate::Comment::get_entity_name(), + search_string_encoded, + entities_per_page + ); let mut elements_to_verify = Vec::new(); elements_to_verify.push("Test 15".to_string()); @@ -76,17 +89,22 @@ mod get_request_is_success { let page = 17; let page_size = 20; // Verify with default size in list.rs - let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Comment::get_entity_name(), page, page_size); + let url = format!( + "/admin/{}/list?page={}&entities_per_page={}", + crate::Comment::get_entity_name(), + page, + page_size + ); let query = if page_size == 5 { crate::Comment::find().order_by_asc(crate::comment::Column::Id) } else { crate::Comment::find().order_by_asc(crate::comment::Column::Id) }; - + let entities = query .paginate(&db, page_size) - .fetch_page(page-1) + .fetch_page(page - 1) .await .unwrap(); @@ -119,11 +137,7 @@ mod get_request_is_success { async fn get_comment_show() { let db = super::setup_db(true).await; - let url = format!( - "/admin/{}/show/{}", - crate::Comment::get_entity_name(), - 1 - ); + let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1); test_get_is_success(url.as_str(), &db).await } @@ -131,39 +145,38 @@ mod get_request_is_success { async fn get_post_show() { let db = super::setup_db(true).await; - let url = format!( - "/admin/{}/show/{}", - crate::Comment::get_entity_name(), - 1 - ); + let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1); test_get_is_success(url.as_str(), &db).await } - async fn test_response_contains(url: &str, db: &DatabaseConnection, elements_to_verify: Vec) { - let app = create_app!(db); + async fn test_response_contains( + url: &str, + db: &DatabaseConnection, + elements_to_verify: Vec, + ) { + let app = create_app!(db); - let req = test::TestRequest::get() - .uri(url) - .to_request(); + let req = test::TestRequest::get().uri(url).to_request(); let resp = test::call_service(&app, req).await; let body = to_bytes(resp.into_body()).await.unwrap(); let body = body.as_str(); for element in elements_to_verify { - assert!(body.contains(&element), "Body did not contain element {}: \n{}", element, body); + assert!( + body.contains(&element), + "Body did not contain element {}: \n{}", + element, + body + ); } - } async fn test_get_is_success(url: &str, db: &DatabaseConnection) { - let app = create_app!(db); + let app = create_app!(db); - let req = test::TestRequest::get() - .uri(url) - .to_request(); + let req = test::TestRequest::get().uri(url).to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); } } - diff --git a/tests/post_create_and_edit_is_success.rs b/tests/post_create_and_edit_is_success.rs index 641ddb5..13445bd 100644 --- a/tests/post_create_and_edit_is_success.rs +++ b/tests/post_create_and_edit_is_success.rs @@ -4,16 +4,12 @@ use test_setup::prelude::*; #[cfg(test)] mod post_create_and_edit_is_success { use actix_admin::prelude::*; - use actix_web::{ - test, - App, - http::header::ContentType - }; - use chrono::{ NaiveDateTime, NaiveDate }; - use serde::{Serialize}; - use sea_orm::{ PaginatorTrait, EntityTrait, prelude::Decimal}; - - use crate::{create_app}; + use actix_web::{http::header::ContentType, test, App}; + use chrono::{NaiveDate, NaiveDateTime}; + use sea_orm::{prelude::Decimal, EntityTrait, PaginatorTrait}; + use serde::Serialize; + + use crate::create_app; #[derive(Serialize, Clone)] pub struct CommentModel { @@ -23,7 +19,7 @@ mod post_create_and_edit_is_success { user: &'static str, is_visible: &'static str, post_id: Option<&'static str>, - my_decimal: &'static str + my_decimal: &'static str, } #[derive(Serialize, Clone)] @@ -48,9 +44,9 @@ mod post_create_and_edit_is_success { user: "test", is_visible: "true", post_id: None, - my_decimal: "113.141" // must be larger than 100 + my_decimal: "113.141", // must be larger than 100 }; - + let req = test::TestRequest::post() .insert_header(ContentType::form_url_encoded()) .uri("/admin/comment/create_post_from_plaintext") @@ -69,12 +65,15 @@ mod post_create_and_edit_is_success { assert_eq!(entities.len(), 1, "After post, db does not contain 1 model"); let entity = entities.first().unwrap(); assert_eq!(entity.id, 1); - assert_eq!(entity.comment,"test"); + assert_eq!(entity.comment, "test"); assert_eq!(entity.user, "test"); assert!(entity.is_visible); assert!(entity.post_id.is_none()); assert_eq!(entity.my_decimal, Decimal::new(113141, 3)); - assert_eq!(entity.insert_date, NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()); + assert_eq!( + entity.insert_date, + NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap() + ); // update entity model.my_decimal = "213.141"; @@ -98,7 +97,11 @@ mod post_create_and_edit_is_success { .await .expect("could not retrieve entities"); - assert_eq!(entities.len(), 1, "After edit post, db does not contain 1 model"); + assert_eq!( + entities.len(), + 1, + "After edit post, db does not contain 1 model" + ); let entity = entities.first().unwrap(); assert_eq!(entity.id, 1); assert_eq!(entity.comment, "updated"); @@ -106,9 +109,12 @@ mod post_create_and_edit_is_success { assert!(!entity.is_visible); assert!(entity.post_id.is_none()); assert_eq!(entity.my_decimal, Decimal::new(213141, 3)); - assert_eq!(entity.insert_date, NaiveDateTime::parse_from_str("1987-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()); + assert_eq!( + entity.insert_date, + NaiveDateTime::parse_from_str("1987-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap() + ); } - + #[actix_web::test] async fn post_create_and_edit() { let db = super::setup_db(false).await; @@ -119,7 +125,7 @@ mod post_create_and_edit_is_success { insert_date: "1977-04-01", title: "test", text: "test", - tea_mandatory: "EverydayTea" + tea_mandatory: "EverydayTea", }; let req = test::TestRequest::post() @@ -140,10 +146,16 @@ mod post_create_and_edit_is_success { assert_eq!(entities.len(), 1, "After post, db does not contain 1 model"); let entity = entities.first().unwrap(); assert_eq!(entity.id, 1); - assert_eq!(entity.tea_mandatory, super::test_setup::post::Tea::EverydayTea); + assert_eq!( + entity.tea_mandatory, + super::test_setup::post::Tea::EverydayTea + ); assert_eq!(entity.title, model.title); assert_eq!(entity.text, model.text); - assert_eq!(entity.insert_date, NaiveDate::parse_from_str("1977-04-01", "%Y-%m-%d").unwrap()); + assert_eq!( + entity.insert_date, + NaiveDate::parse_from_str("1977-04-01", "%Y-%m-%d").unwrap() + ); // update entity model.tea_mandatory = "BreakfastTea"; @@ -166,11 +178,18 @@ mod post_create_and_edit_is_success { .await .expect("could not retrieve entities"); - assert_eq!(entities.len(), 1, "After edit post, db does not contain 1 model"); + assert_eq!( + entities.len(), + 1, + "After edit post, db does not contain 1 model" + ); let entity = entities.first().unwrap(); assert_eq!(entity.id, 1); assert_eq!(entity.text, "updated"); assert_eq!(entity.title, "updated"); - assert_eq!(entity.insert_date, NaiveDate::parse_from_str("1987-04-01", "%Y-%m-%d").unwrap()); + assert_eq!( + entity.insert_date, + NaiveDate::parse_from_str("1987-04-01", "%Y-%m-%d").unwrap() + ); } } diff --git a/tests/test_setup/comment.rs b/tests/test_setup/comment.rs index b8513d2..dde08cb 100644 --- a/tests/test_setup/comment.rs +++ b/tests/test_setup/comment.rs @@ -1,10 +1,18 @@ +use super::Post; +use actix_admin::prelude::*; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -use actix_admin::prelude::*; -use super::Post; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, - DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel +#[derive( + Clone, + Debug, + PartialEq, + DeriveEntityModel, + Deserialize, + Serialize, + DeriveActixAdmin, + DeriveActixAdminModel, + DeriveActixAdminViewModel, )] #[sea_orm(table_name = "comment")] pub struct Model { @@ -20,9 +28,9 @@ pub struct Model { #[sea_orm(column_type = "DateTime")] pub insert_date: DateTime, pub is_visible: bool, - #[actix_admin(select_list="Post")] + #[actix_admin(select_list = "Post")] pub post_id: Option, - pub my_decimal: Decimal + pub my_decimal: Decimal, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -47,10 +55,13 @@ impl ActixAdminModelValidationTrait for Entity { fn validate(model: &ActiveModel) -> HashMap { let mut errors = HashMap::new(); if model.my_decimal.clone().unwrap() < Decimal::from(100 as i16) { - errors.insert("my_decimal".to_string(), "Must be larger than 100".to_string()); + errors.insert( + "my_decimal".to_string(), + "Must be larger than 100".to_string(), + ); } errors } } -impl ActixAdminModelFilterTrait for Entity {} \ No newline at end of file +impl ActixAdminModelFilterTrait for Entity {} diff --git a/tests/test_setup/helper.rs b/tests/test_setup/helper.rs index 4ce7827..f6472d7 100644 --- a/tests/test_setup/helper.rs +++ b/tests/test_setup/helper.rs @@ -1,10 +1,10 @@ use actix_admin::prelude::*; use actix_session::Session; -use actix_web::HttpRequest; use actix_web::web; -use actix_web::Error; -use actix_web::HttpResponse; use actix_web::web::Bytes; +use actix_web::Error; +use actix_web::HttpRequest; +use actix_web::HttpResponse; use chrono::Local; use sea_orm::prelude::Decimal; use sea_orm::{ConnectOptions, DatabaseConnection, EntityTrait, Set}; @@ -78,7 +78,7 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder { login_link: None, logout_link: None, file_upload_directory: "./file_uploads", - navbar_title: "test" + navbar_title: "test", }; let mut admin_builder = ActixAdminBuilder::new(configuration); @@ -157,4 +157,4 @@ impl BodyTest for Bytes { fn as_str(&self) -> &str { std::str::from_utf8(self).unwrap() } -} \ No newline at end of file +} diff --git a/tests/test_setup/mod.rs b/tests/test_setup/mod.rs index a9ed404..5c1af81 100644 --- a/tests/test_setup/mod.rs +++ b/tests/test_setup/mod.rs @@ -1,22 +1,18 @@ // setup -use sea_orm::sea_query::{ForeignKeyCreateStatement, ColumnDef, TableCreateStatement}; +use sea_orm::sea_query::{ColumnDef, ForeignKeyCreateStatement, TableCreateStatement}; use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult}; pub mod comment; -pub mod post; pub mod helper; +pub mod post; pub use comment::Entity as Comment; pub use post::Entity as Post; pub mod prelude { - pub use crate::test_setup::helper::{ - create_actix_admin_builder, - setup_db, - BodyTest - }; pub use super::comment; pub use super::post; pub use super::Comment; pub use super::Post; + pub use crate::test_setup::helper::{create_actix_admin_builder, setup_db, BodyTest}; } // setup @@ -38,7 +34,11 @@ pub async fn create_tables(db: &DbConn) -> Result { ) .col(ColumnDef::new(post::Column::Title).string().not_null()) .col(ColumnDef::new(post::Column::Text).string().not_null()) - .col(ColumnDef::new(post::Column::TeaMandatory).string().not_null()) + .col( + ColumnDef::new(post::Column::TeaMandatory) + .string() + .not_null(), + ) .col(ColumnDef::new(post::Column::TeaOptional).string()) .col(ColumnDef::new(post::Column::InsertDate).date()) .to_owned(); @@ -57,9 +57,21 @@ pub async fn create_tables(db: &DbConn) -> Result { ) .col(ColumnDef::new(comment::Column::Comment).string().not_null()) .col(ColumnDef::new(comment::Column::User).string().not_null()) - .col(ColumnDef::new(comment::Column::InsertDate).date_time().not_null()) - .col(ColumnDef::new(comment::Column::IsVisible).boolean().not_null()) - .col(ColumnDef::new(comment::Column::MyDecimal).decimal().not_null()) + .col( + ColumnDef::new(comment::Column::InsertDate) + .date_time() + .not_null(), + ) + .col( + ColumnDef::new(comment::Column::IsVisible) + .boolean() + .not_null(), + ) + .col( + ColumnDef::new(comment::Column::MyDecimal) + .decimal() + .not_null(), + ) .col(ColumnDef::new(comment::Column::PostId).integer()) .foreign_key( ForeignKeyCreateStatement::new() diff --git a/tests/test_setup/post.rs b/tests/test_setup/post.rs index 900a4a5..d955f44 100644 --- a/tests/test_setup/post.rs +++ b/tests/test_setup/post.rs @@ -1,11 +1,22 @@ +use actix_admin::prelude::*; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -use actix_admin::prelude::*; use std::fmt; use std::fmt::Display; use std::str::FromStr; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminViewModel, DeriveActixAdminModel, DeriveActixAdminModelSelectList)] +#[derive( + Clone, + Debug, + PartialEq, + DeriveEntityModel, + Deserialize, + Serialize, + DeriveActixAdmin, + DeriveActixAdminViewModel, + DeriveActixAdminModel, + DeriveActixAdminModelSelectList, +)] #[sea_orm(table_name = "post")] pub struct Model { #[sea_orm(primary_key)] @@ -17,9 +28,9 @@ pub struct Model { #[sea_orm(column_type = "Text")] #[actix_admin(searchable, textarea)] pub text: String, - #[actix_admin(select_list="Tea")] + #[actix_admin(select_list = "Tea")] pub tea_mandatory: Tea, - #[actix_admin(select_list="Tea")] + #[actix_admin(select_list = "Tea")] pub tea_optional: Option, pub insert_date: Date, } @@ -27,7 +38,7 @@ pub struct Model { impl Display for Model { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match &*self { - _ => write!(formatter, "{} {}", &self.title, &self.insert_date), + _ => write!(formatter, "{} {}", &self.title, &self.insert_date), } } } @@ -46,7 +57,20 @@ impl Related for Entity { impl ActiveModelBehavior for ActiveModel {} +<<<<<<< HEAD #[derive(Debug, Clone, PartialEq, EnumIter, DeriveDisplay, DeriveActiveEnum, Deserialize, Serialize, DeriveActixAdminEnumSelectList)] +======= +#[derive( + Debug, + Clone, + PartialEq, + EnumIter, + DeriveActiveEnum, + Deserialize, + Serialize, + DeriveActixAdminEnumSelectList, +)] +>>>>>>> 7db2971 (Trim input name) #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")] pub enum Tea { #[sea_orm(string_value = "EverydayTea")] @@ -60,9 +84,9 @@ impl FromStr for Tea { fn from_str(input: &str) -> Result { match input { - "EverydayTea" => Ok(Tea::EverydayTea), - "BreakfastTea" => Ok(Tea::BreakfastTea), - _ => Err(()), + "EverydayTea" => Ok(Tea::EverydayTea), + "BreakfastTea" => Ok(Tea::BreakfastTea), + _ => Err(()), } } }