Trim input name

This commit is contained in:
Adrian Woźniak 2023-07-31 13:55:20 +02:00
parent ad79a3852a
commit 40fd0863fc
20 changed files with 663 additions and 310 deletions

View File

@ -3,8 +3,13 @@ use actix_web::{web, Route };
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use crate::routes::{ 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 /// Represents a builder entity which helps generating the ActixAdmin configuration
pub struct ActixAdminBuilder { pub struct ActixAdminBuilder {
@ -21,9 +26,7 @@ pub trait ActixAdminBuilderTrait {
&mut self, &mut self,
view_model: &ActixAdminViewModel, view_model: &ActixAdminViewModel,
); );
fn add_entity_to_category< fn add_entity_to_category<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
view_model: &ActixAdminViewModel, view_model: &ActixAdminViewModel,
category_name: &str, category_name: &str,
@ -41,20 +44,16 @@ pub trait ActixAdminBuilderTrait {
path: &str, path: &str,
route: Route, route: Route,
add_to_menu: bool, add_to_menu: bool,
category: &str category: &str,
); );
fn add_custom_handler_for_entity< fn add_custom_handler_for_entity<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
menu_element_name: &str, menu_element_name: &str,
path: &str, path: &str,
route: Route, route: Route,
add_to_menu: bool add_to_menu: bool,
); );
fn add_custom_handler_for_entity_in_category< fn add_custom_handler_for_entity_in_category<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
menu_element_name: &str, menu_element_name: &str,
path: &str, path: &str,
@ -67,6 +66,139 @@ pub trait ActixAdminBuilderTrait {
fn get_actix_admin(&self) -> ActixAdmin; fn get_actix_admin(&self) -> ActixAdmin;
} }
fn get_html_input_class<S: BuildHasher>(
value: &tera::Value,
_: &HashMap<String, tera::Value, S>,
) -> Result<tera::Value> {
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<S: BuildHasher>(
value: &tera::Value,
_: &HashMap<String, tera::Value, S>,
) -> Result<tera::Value> {
let field = try_get_value!("get_icon", "value", String, value);
let font_awesome_icon = match field.as_str() {
"true" => "<i class=\"fa-solid fa-check\"></i>",
"false" => "<i class=\"fa-solid fa-xmark\"></i>",
_ => panic!("not implemented icon"),
};
Ok(to_value(font_awesome_icon).unwrap())
}
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),
}
}
fn get_html_input_type<S: BuildHasher>(
value: &tera::Value,
_: &HashMap<String, tera::Value, S>,
) -> Result<tera::Value> {
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 { impl ActixAdminBuilderTrait for ActixAdminBuilder {
fn new(configuration: ActixAdminConfiguration) -> Self { fn new(configuration: ActixAdminConfiguration) -> Self {
ActixAdminBuilder { ActixAdminBuilder {
@ -89,9 +221,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
let _ = &self.add_entity_to_category::<E>(view_model, ""); let _ = &self.add_entity_to_category::<E>(view_model, "");
} }
fn add_entity_to_category< fn add_entity_to_category<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
view_model: &ActixAdminViewModel, view_model: &ActixAdminViewModel,
category_name: &str, category_name: &str,
@ -108,11 +238,19 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
.route("/delete/{id}", web::delete().to(delete::<E>)) .route("/delete/{id}", web::delete().to(delete::<E>))
.route("/show/{id}", web::get().to(show::<E>)) .route("/show/{id}", web::get().to(show::<E>))
.route("/file/{id}/{column_name}", web::get().to(download::<E>)) .route("/file/{id}/{column_name}", web::get().to(download::<E>))
.route("/file/{id}/{column_name}", web::delete().to(delete_file::<E>)) .route(
.default_service(web::to(not_found)) "/file/{id}/{column_name}",
); web::delete().to(delete_file::<E>),
)
.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 category = self.actix_admin.entity_names.get_mut(category_name);
let menu_element = ActixAdminMenuElement { let menu_element = ActixAdminMenuElement {
@ -145,7 +283,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
path: &str, path: &str,
route: Route, route: Route,
add_to_menu: bool, add_to_menu: bool,
category_name: &str category_name: &str,
) { ) {
self.custom_routes.push((path.to_string(), route)); self.custom_routes.push((path.to_string(), route));
@ -168,7 +306,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
self.actix_admin self.actix_admin
.entity_names .entity_names
.insert(category_name.to_string(), entity_list); .insert(category_name.to_string(), entity_list);
}, }
} }
} }
} }
@ -178,14 +316,12 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
menu_element_name: &str, menu_element_name: &str,
path: &str, path: &str,
route: Route, route: Route,
add_to_menu: bool add_to_menu: bool,
) { ) {
self.add_custom_handler_to_category(menu_element_name, path, route, add_to_menu, ""); self.add_custom_handler_to_category(menu_element_name, path, route, add_to_menu, "");
} }
fn add_custom_handler_for_entity< fn add_custom_handler_for_entity<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
menu_element_name: &str, menu_element_name: &str,
path: &str, path: &str,
@ -201,9 +337,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
); );
} }
fn add_custom_handler_for_entity_in_category< fn add_custom_handler_for_entity_in_category<E: ActixAdminViewModelTrait + 'static>(
E: ActixAdminViewModelTrait + 'static,
>(
&mut self, &mut self,
menu_element_name: &str, menu_element_name: &str,
path: &str, path: &str,
@ -222,8 +356,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
match existing_scope { match existing_scope {
Some(scope) => { Some(scope) => {
let existing_scope = scope.route(path, route); let existing_scope = scope.route(path, route);
self.scopes self.scopes.insert(E::get_entity_name(), existing_scope);
.insert(E::get_entity_name(), existing_scope);
} }
_ => { _ => {
let new_scope = let new_scope =

View File

@ -14,8 +14,8 @@ use async_trait::async_trait;
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use serde_derive::Serialize; use serde_derive::Serialize;
use tera::Tera;
use std::collections::HashMap; use std::collections::HashMap;
use tera::Tera;
pub mod builder; pub mod builder;
pub mod model; pub mod model;
@ -25,11 +25,14 @@ pub mod tera_templates;
pub mod prelude { pub mod prelude {
pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait}; 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::routes::{create_or_edit_post, get_admin_ctx, SortOrder};
pub use crate::view_model::{ pub use crate::view_model::{
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType, ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
ActixAdminViewModelSerializable, ActixAdminViewModelTrait, ActixAdminViewModelFilter ActixAdminViewModelFilter, ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
}; };
pub use crate::{hashmap, ActixAdminSelectListTrait}; pub use crate::{hashmap, ActixAdminSelectListTrait};
pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError}; pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError};
@ -69,7 +72,7 @@ pub struct ActixAdminConfiguration {
pub login_link: Option<String>, pub login_link: Option<String>,
pub logout_link: Option<String>, pub logout_link: Option<String>,
pub file_upload_directory: &'static str, pub file_upload_directory: &'static str,
pub navbar_title: &'static str pub navbar_title: &'static str,
} }
#[derive(Clone)] #[derive(Clone)]
@ -77,7 +80,7 @@ pub struct ActixAdmin {
pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>, pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>,
pub view_models: HashMap<String, ActixAdminViewModel>, pub view_models: HashMap<String, ActixAdminViewModel>,
pub configuration: ActixAdminConfiguration, pub configuration: ActixAdminConfiguration,
pub tera: Tera pub tera: Tera,
} }
#[derive(PartialEq, Eq, Clone, Serialize)] #[derive(PartialEq, Eq, Clone, Serialize)]
@ -117,7 +120,7 @@ pub enum ActixAdminError {
impl error::ResponseError for ActixAdminError { impl error::ResponseError for ActixAdminError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::debug!("{self:#?}"); tracing::debug!("{self:#?}");
HttpResponse::build(self.status_code()) HttpResponse::build(self.status_code())
.insert_header(ContentType::html()) .insert_header(ContentType::html())

View File

@ -22,7 +22,7 @@ pub trait ActixAdminModelTrait {
filter_values: HashMap<String, Option<String>>, filter_values: HashMap<String, Option<String>>,
search: &str, search: &str,
sort_by: &str, sort_by: &str,
sort_order: &SortOrder sort_order: &SortOrder,
) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>; ) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>;
fn get_fields() -> &'static [ActixAdminViewModelField]; fn get_fields() -> &'static [ActixAdminViewModelField];
fn validate_model(model: &mut ActixAdminModel); fn validate_model(model: &mut ActixAdminModel);
@ -38,7 +38,7 @@ pub struct ActixAdminModelFilter<E: EntityTrait> {
pub name: String, pub name: String,
pub filter_type: ActixAdminModelFilterType, pub filter_type: ActixAdminModelFilterType,
pub filter: fn(sea_orm::Select<E>, Option<String>) -> sea_orm::Select<E>, pub filter: fn(sea_orm::Select<E>, Option<String>) -> sea_orm::Select<E>,
pub values: Option<Vec<(String, String)>> pub values: Option<Vec<(String, String)>>,
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -47,7 +47,7 @@ pub enum ActixAdminModelFilterType {
SelectList, SelectList,
Date, Date,
DateTime, DateTime,
Checkbox Checkbox,
} }
#[async_trait] #[async_trait]
@ -55,7 +55,10 @@ pub trait ActixAdminModelFilterTrait<E: EntityTrait> {
fn get_filter() -> Vec<ActixAdminModelFilter<E>> { fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
Vec::new() Vec::new()
} }
async fn get_filter_values(_filter: &ActixAdminModelFilter<E>, _db: &DatabaseConnection)-> Option<Vec<(String, String)>> { async fn get_filter_values(
_filter: &ActixAdminModelFilter<E>,
_db: &DatabaseConnection,
) -> Option<Vec<(String, String)>> {
None None
} }
} }
@ -66,7 +69,7 @@ impl<T: EntityTrait> From<ActixAdminModelFilter<T>> for ActixAdminViewModelFilte
name: filter.name, name: filter.name,
value: None, value: None,
values: 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( pub async fn create_from_payload(
mut payload: Multipart, file_upload_folder: &str mut payload: Multipart,
file_upload_folder: &str,
) -> Result<ActixAdminModel, MultipartError> { ) -> Result<ActixAdminModel, MultipartError> {
let mut hashmap = HashMap::<String, String>::new(); let mut hashmap = HashMap::<String, String>::new();
@ -115,21 +119,28 @@ impl ActixAdminModel {
let file_exists = std::path::Path::new(&file_path).exists(); let file_exists = std::path::Path::new(&file_path).exists();
// Avoid overwriting existing files // Avoid overwriting existing files
if file_exists { 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); file_path = format!("{}/{}", file_upload_folder, filename);
} }
let file = File::create(file_path); let file = File::create(file_path);
let _res = file.unwrap().write_all(&binary_data); let _res = file.unwrap().write_all(&binary_data);
hashmap.insert( hashmap.insert(field.name().to_string(), filename.clone());
field.name().to_string(),
filename.clone()
);
} else { } else {
let res_string = String::from_utf8(binary_data); let res_string = String::from_utf8(binary_data);
if res_string.is_ok() { 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, &self,
key: &str, key: &str,
is_option_or_string: bool, is_option_or_string: bool,
is_allowed_to_be_empty: bool is_allowed_to_be_empty: bool,
) -> Result<Option<T>, String> { ) -> Result<Option<T>, String> {
self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| val.parse::<T>()) self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
val.parse::<T>()
})
} }
pub fn get_datetime( pub fn get_datetime(
&self, &self,
key: &str, key: &str,
is_option_or_string: bool, is_option_or_string: bool,
is_allowed_to_be_empty: bool is_allowed_to_be_empty: bool,
) -> Result<Option<NaiveDateTime>, String> { ) -> Result<Option<NaiveDateTime>, String> {
self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { 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") NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M")
@ -166,21 +179,27 @@ impl ActixAdminModel {
&self, &self,
key: &str, key: &str,
is_option_or_string: bool, is_option_or_string: bool,
is_allowed_to_be_empty: bool is_allowed_to_be_empty: bool,
) -> Result<Option<NaiveDate>, String> { ) -> Result<Option<NaiveDate>, String> {
self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| { self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
NaiveDate::parse_from_str(val, "%Y-%m-%d") 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<Option<bool>, String> { pub fn get_bool(
let val = self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty ,|val| { &self,
if !val.is_empty() && (val == "true" || val == "yes") { key: &str,
Ok(true) is_option_or_string: bool,
} else { is_allowed_to_be_empty: bool,
Ok(false) ) -> Result<Option<bool>, 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 // not selected bool field equals to false and not to missing
match val { match val {
Ok(val) => Ok(val), Ok(val) => Ok(val),

View File

@ -1,14 +1,14 @@
use actix_web::{error, web, Error, HttpRequest, HttpResponse}; use crate::prelude::*;
use sea_orm::DatabaseConnection;
use tera::{Context};
use actix_session::{Session};
use crate::ActixAdminError; use crate::ActixAdminError;
use crate::ActixAdminNotification; 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::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<E: ActixAdminViewModelTrait>( pub async fn create_get<E: ActixAdminViewModelTrait>(
session: Session, session: Session,
@ -20,7 +20,7 @@ pub async fn create_get<E: ActixAdminViewModelTrait>(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let db = db.get_ref(); let db = db.get_ref();
let model = ActixAdminModel::create_empty(); let model = ActixAdminModel::create_empty();
create_or_edit_get::<E>(&session, req, &data, db, Ok(model)).await create_or_edit_get::<E>(&session, req, &data, db, Ok(model)).await
} }
@ -30,7 +30,7 @@ pub async fn edit_get<E: ActixAdminViewModelTrait>(
data: web::Data<ActixAdmin>, data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection>, db: web::Data<DatabaseConnection>,
_text: String, _text: String,
id: web::Path<i32> id: web::Path<i32>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let db = db.get_ref(); let db = db.get_ref();
let model = E::get_entity(db, id.into_inner()).await; let model = E::get_entity(db, id.into_inner()).await;
@ -38,7 +38,13 @@ pub async fn edit_get<E: ActixAdminViewModelTrait>(
create_or_edit_get::<E>(&session, req, &data, db, model).await create_or_edit_get::<E>(&session, req, &data, db, model).await
} }
async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req: HttpRequest, data: &web::Data<ActixAdmin>, db: &sea_orm::DatabaseConnection, model_result: Result<ActixAdminModel, ActixAdminError>) -> Result<HttpResponse, Error>{ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(
session: &Session,
req: HttpRequest,
data: &web::Data<ActixAdmin>,
db: &sea_orm::DatabaseConnection,
model_result: Result<ActixAdminModel, ActixAdminError>,
) -> Result<HttpResponse, Error> {
let actix_admin = &data.get_ref(); let actix_admin = &data.get_ref();
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);
@ -57,9 +63,9 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
match model_result { match model_result {
Ok(res) => { Ok(res) => {
model = res; model = res;
}, }
Err(e) => { Err(e) => {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{e}"); tracing::error!("{e}");
errors.push(e); errors.push(e);
model = ActixAdminModel::create_empty(); model = ActixAdminModel::create_empty();
@ -69,8 +75,9 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
let mut http_response_code = match errors.is_empty() { let mut http_response_code = match errors.is_empty() {
true => HttpResponse::Ok(), true => HttpResponse::Ok(),
false => HttpResponse::InternalServerError(), false => HttpResponse::InternalServerError(),
}; };
let notifications: Vec<ActixAdminNotification> = errors.into_iter() let notifications: Vec<ActixAdminNotification> = errors
.into_iter()
.map(ActixAdminNotification::from) .map(ActixAdminNotification::from)
.collect(); .collect();
@ -82,10 +89,16 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE); .unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
let render_partial = req.headers().contains_key("HX-Target"); let render_partial = req.headers().contains_key("HX-Target");
let search = params.search.clone().unwrap_or(String::new()); 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); 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("select_lists", &E::get_select_lists(db).await?);
ctx.insert("base_path", &E::get_base_path(&entity_name)); ctx.insert("base_path", &E::get_base_path(&entity_name));
ctx.insert("model", &model); ctx.insert("model", &model);
@ -96,11 +109,12 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
ctx.insert("sort_by", &sort_by); ctx.insert("sort_by", &sort_by);
ctx.insert("sort_order", &sort_order); ctx.insert("sort_order", &sort_order);
ctx.insert("page", &page); ctx.insert("page", &page);
let body = actix_admin.tera let body = actix_admin
.tera
.render("create_or_edit.html", &ctx) .render("create_or_edit.html", &ctx)
.map_err(|err| { .map_err(|err| {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{err}"); tracing::error!("{err}");
error::ErrorInternalServerError(err) error::ErrorInternalServerError(err)
})?; })?;

View File

@ -50,15 +50,7 @@ pub async fn edit_post<E: ActixAdminViewModelTrait>(
), ),
) )
.await; .await;
create_or_edit_post::<E>( create_or_edit_post::<E>(&session, req, db, model, Some(id.into_inner()), actix_admin).await
&session,
req,
db,
model,
Some(id.into_inner()),
actix_admin,
)
.await
} }
pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>( pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
@ -82,7 +74,7 @@ pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
let db = db.get_ref(); let db = db.get_ref();
let mut model = model_res.unwrap(); let mut model = model_res.unwrap();
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
{ {
tracing::debug!("Entity model: {:#?}", model); tracing::debug!("Entity model: {:#?}", model);
} }
@ -90,7 +82,7 @@ pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
if model.has_errors() { if model.has_errors() {
errors.push(ActixAdminError::ValidationErrors); errors.push(ActixAdminError::ValidationErrors);
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
{ {
tracing::error!("OP errors: {errors:#?}"); tracing::error!("OP errors: {errors:#?}");
tracing::debug!("Model errors: {:#?}", model.errors); tracing::debug!("Model errors: {:#?}", model.errors);
@ -134,7 +126,7 @@ pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
.finish()) .finish())
} }
Err(e) => { Err(e) => {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{e}"); tracing::error!("{e}");
errors.push(e); errors.push(e);
render_form::<E>( render_form::<E>(
@ -197,17 +189,18 @@ async fn render_form<E: ActixAdminViewModelTrait>(
let notifications: Vec<ActixAdminNotification> = errors let notifications: Vec<ActixAdminNotification> = errors
.into_iter() .into_iter()
.map(|err| { .map(|err| {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{err}"); tracing::error!("{err}");
ActixAdminNotification::from(err) ActixAdminNotification::from(err)
}) })
.collect(); .collect();
ctx.insert("notifications", &notifications); ctx.insert("notifications", &notifications);
let body = actix_admin.tera let body = actix_admin
.tera
.render("create_or_edit.html", &ctx) .render("create_or_edit.html", &ctx)
.map_err(|err| { .map_err(|err| {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{err}"); tracing::error!("{err}");
error::ErrorInternalServerError(err) error::ErrorInternalServerError(err)
})?; })?;

View File

@ -77,7 +77,11 @@ pub async fn delete_many<E: ActixAdminViewModelTrait>(
let db = &db.get_ref(); let db = &db.get_ref();
let entity_name = E::get_entity_name(); let entity_name = E::get_entity_name();
let ids: Vec<i32> = form.iter().filter(|el| el.0 == "ids").map(|el| el.1.parse::<i32>().unwrap()).collect(); let ids: Vec<i32> = form
.iter()
.filter(|el| el.0 == "ids")
.map(|el| el.1.parse::<i32>().unwrap())
.collect();
// TODO: implement delete_many // TODO: implement delete_many
for id in ids { for id in ids {
@ -107,23 +111,28 @@ pub async fn delete_many<E: ActixAdminViewModelTrait>(
} }
} }
let entities_per_page = form.iter() let entities_per_page = form
.iter()
.find(|el| el.0 == "entities_per_page") .find(|el| el.0 == "entities_per_page")
.map(|e| e.1.to_string()) .map(|e| e.1.to_string())
.unwrap_or("10".to_string()); .unwrap_or("10".to_string());
let search = form.iter() let search = form
.iter()
.find(|el| el.0 == "search") .find(|el| el.0 == "search")
.map(|e| e.1.to_string()) .map(|e| e.1.to_string())
.unwrap_or_default(); .unwrap_or_default();
let sort_by = form.iter() let sort_by = form
.iter()
.find(|el| el.0 == "sort_by") .find(|el| el.0 == "sort_by")
.map(|e| e.1.to_string()) .map(|e| e.1.to_string())
.unwrap_or("id".to_string()); .unwrap_or("id".to_string());
let sort_order = form.iter() let sort_order = form
.iter()
.find(|el| el.0 == "sort_order") .find(|el| el.0 == "sort_order")
.map(|e| e.1.to_string()) .map(|e| e.1.to_string())
.unwrap_or("Asc".to_string()); .unwrap_or("Asc".to_string());
let page = form.iter() let page = form
.iter()
.find(|el| el.0 == "page") .find(|el| el.0 == "page")
.map(|e| e.1.to_string()) .map(|e| e.1.to_string())
.unwrap_or("1".to_string()); .unwrap_or("1".to_string());

View File

@ -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 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<E: ActixAdminViewModelTrait>(req: HttpRequest, session: Session, data: web::Data<ActixAdmin>, db: web::Data<DatabaseConnection>, params: web::Path<(i32, String)>) -> Result<HttpResponse, Error> { pub async fn download<E: ActixAdminViewModelTrait>(
req: HttpRequest,
session: Session,
data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection>,
params: web::Path<(i32, String)>,
) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner(); let actix_admin = &data.into_inner();
let db = &db.into_inner(); let db = &db.into_inner();
@ -16,7 +22,7 @@ pub async fn download<E: ActixAdminViewModelTrait>(req: HttpRequest, session: Se
if !user_can_access_page(&session, actix_admin, view_model) { if !user_can_access_page(&session, actix_admin, view_model) {
return render_unauthorized(&ctx, &actix_admin); return render_unauthorized(&ctx, &actix_admin);
} }
let (id, column_name) = params.into_inner(); let (id, column_name) = params.into_inner();
let mut errors: Vec<crate::ActixAdminError> = Vec::new(); let mut errors: Vec<crate::ActixAdminError> = Vec::new();
let result = E::get_entity(db, id).await; let result = E::get_entity(db, id).await;
@ -24,25 +30,36 @@ pub async fn download<E: ActixAdminViewModelTrait>(req: HttpRequest, session: Se
match result { match result {
Ok(res) => { Ok(res) => {
model = res; model = res;
}, }
Err(e) => { Err(e) => {
errors.push(e); errors.push(e);
model = ActixAdminModel::create_empty(); model = ActixAdminModel::create_empty();
} }
} }
let file_name = model.get_value::<String>(&column_name, true, true).unwrap_or_default(); let file_name = model
let file_path = format!("{}/{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name(), file_name.unwrap_or_default()); .get_value::<String>(&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; let file = actix_files::NamedFile::open_async(file_path).await;
match file { match file {
Ok(file) => Ok(file.into_response(&req)), 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<E: ActixAdminViewModelTrait>(session: Session, data: web::Data<ActixAdmin>, db: web::Data<DatabaseConnection>, params: web::Path<(i32, String)>) -> Result<HttpResponse, Error> { pub async fn delete_file<E: ActixAdminViewModelTrait>(
session: Session,
data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection>,
params: web::Path<(i32, String)>,
) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner(); let actix_admin = &data.into_inner();
let mut ctx = Context::new(); let mut ctx = Context::new();
@ -51,7 +68,7 @@ pub async fn delete_file<E: ActixAdminViewModelTrait>(session: Session, data: we
if !user_can_access_page(&session, actix_admin, view_model) { if !user_can_access_page(&session, actix_admin, view_model) {
return render_unauthorized(&ctx, &actix_admin); return render_unauthorized(&ctx, &actix_admin);
} }
let (id, column_name) = params.into_inner(); let (id, column_name) = params.into_inner();
let mut errors: Vec<crate::ActixAdminError> = Vec::new(); let mut errors: Vec<crate::ActixAdminError> = Vec::new();
let result = E::get_entity(db.get_ref(), id).await; let result = E::get_entity(db.get_ref(), id).await;
@ -59,27 +76,38 @@ pub async fn delete_file<E: ActixAdminViewModelTrait>(session: Session, data: we
match result { match result {
Ok(res) => { Ok(res) => {
model = res; model = res;
}, }
Err(e) => { Err(e) => {
errors.push(e); errors.push(e);
model = ActixAdminModel::create_empty(); model = ActixAdminModel::create_empty();
} }
} }
let file_name = model.get_value::<String>(&column_name, true, true).unwrap_or_default(); let file_name = model
let file_path = format!("{}/{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name(), file_name.unwrap_or_default()); .get_value::<String>(&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(); std::fs::remove_file(file_path).unwrap();
model.values.remove(&column_name); model.values.remove(&column_name);
let _edit_res = E::edit_entity(db.get_ref(), id, model.clone()).await; 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("model_field", view_model_field);
ctx.insert("base_path", &E::get_base_path(&entity_name)); ctx.insert("base_path", &E::get_base_path(&entity_name));
ctx.insert("model", &model); ctx.insert("model", &model);
let body = actix_admin.tera let body = actix_admin
.tera
.render("form_elements/input.html", &ctx) .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)) Ok(HttpResponse::Ok().content_type("text/html").body(body))
}
}

View File

@ -1,10 +1,9 @@
use actix_session::{Session}; use actix_session::Session;
use tera::{Context}; use tera::Context;
use crate::prelude::*; use crate::prelude::*;
use actix_web::{error, Error, HttpResponse}; use actix_web::{error, Error, HttpResponse};
pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut Context) { pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut Context) {
let enable_auth = &actix_admin.configuration.enable_auth; let enable_auth = &actix_admin.configuration.enable_auth;
ctx.insert("enable_auth", &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 { if *enable_auth {
let func = &actix_admin.configuration.user_is_logged_in.unwrap(); let func = &actix_admin.configuration.user_is_logged_in.unwrap();
ctx.insert("user_is_logged_in", &func(session)); 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(
ctx.insert("logout_link", &actix_admin.configuration.logout_link.as_ref().unwrap_or(&String::new())); "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 auth_is_enabled = &actix_admin.configuration.enable_auth;
let user_is_logged_in = &actix_admin.configuration.user_is_logged_in; let user_is_logged_in = &actix_admin.configuration.user_is_logged_in;
let user_can_access_view_model = &view_model.user_can_access; let user_can_access_view_model = &view_model.user_can_access;
match (auth_is_enabled, user_is_logged_in, user_can_access_view_model) { match (
(true, Some(auth_func), Some(view_model_access_func)) => auth_func(session) && view_model_access_func(session), 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), (true, Some(auth_func), _) => auth_func(session),
(_, _, _) => !auth_is_enabled, (_, _, _) => !auth_is_enabled,
} }
} }
pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result<HttpResponse, Error> { pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result<HttpResponse, Error> {
let body = actix_admin.tera let body = actix_admin
.render("unauthorized.html", &ctx) .tera
.map_err(|err| error::ErrorInternalServerError(err))?; .render("unauthorized.html", &ctx)
Ok(HttpResponse::Unauthorized().content_type("text/html").body(body)) .map_err(|err| error::ErrorInternalServerError(err))?;
} Ok(HttpResponse::Unauthorized()
.content_type("text/html")
.body(body))
}

View File

@ -1,10 +1,10 @@
use actix_session::Session;
use actix_web::{error, web, Error, HttpResponse}; use actix_web::{error, web, Error, HttpResponse};
use actix_session::{Session}; use tera::Context;
use tera::{Context};
use crate::prelude::*; use crate::prelude::*;
use super::{ add_auth_context }; use super::add_auth_context;
pub fn get_admin_ctx(session: Session, data: &web::Data<ActixAdmin>) -> Context { pub fn get_admin_ctx(session: Session, data: &web::Data<ActixAdmin>) -> Context {
let actix_admin = data.get_ref(); let actix_admin = data.get_ref();
@ -23,28 +23,29 @@ pub async fn index(session: Session, data: web::Data<ActixAdmin>) -> Result<Http
let mut ctx = Context::new(); let mut ctx = Context::new();
ctx.insert("entity_names", &actix_admin.entity_names); ctx.insert("entity_names", &actix_admin.entity_names);
ctx.insert("notifications", &notifications); ctx.insert("notifications", &notifications);
add_auth_context(&session, actix_admin, &mut ctx); add_auth_context(&session, actix_admin, &mut ctx);
let body = actix_admin.tera let body = actix_admin.tera.render("index.html", &ctx).map_err(|e| {
.render("index.html", &ctx) #[cfg(feature = "enable-tracing")]
.map_err(|e| { tracing::error!("{}", e);
#[cfg(feature="enable-tracing")] error::ErrorInternalServerError("Template error")
tracing::error!("{}", e); })?;
error::ErrorInternalServerError("Template error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(body)) Ok(HttpResponse::Ok().content_type("text/html").body(body))
} }
pub async fn not_found(data: web::Data<ActixAdmin>) -> Result<HttpResponse, Error> { pub async fn not_found(data: web::Data<ActixAdmin>) -> Result<HttpResponse, Error> {
let body = data.get_ref().tera let body = data
.get_ref()
.tera
.render("not_found.html", &Context::new()) .render("not_found.html", &Context::new())
.map_err(|e| { .map_err(|e| {
#[cfg(feature="enable-tracing")] #[cfg(feature = "enable-tracing")]
tracing::error!("{}", e); tracing::error!("{}", e);
error::ErrorInternalServerError("Template error") error::ErrorInternalServerError("Template error")
})?; })?;
Ok(HttpResponse::NotFound().content_type("text/html").body(body)) Ok(HttpResponse::NotFound()
.content_type("text/html")
.body(body))
} }

View File

@ -1,10 +1,10 @@
use std::fmt;
use sea_orm::DatabaseConnection;
use urlencoding::decode;
use crate::prelude::*; use crate::prelude::*;
use actix_web::{error, web, Error, HttpRequest, HttpResponse}; 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 tera::Context;
use urlencoding::decode;
use super::{ use super::{
add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE, add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE,
@ -50,7 +50,7 @@ pub async fn list<E: ActixAdminViewModelTrait>(
session: Session, session: Session,
req: HttpRequest, req: HttpRequest,
data: web::Data<ActixAdmin>, data: web::Data<ActixAdmin>,
db: web::Data<DatabaseConnection> db: web::Data<DatabaseConnection>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner(); let actix_admin = &data.into_inner();
let entity_name = E::get_entity_name(); let entity_name = E::get_entity_name();
@ -88,15 +88,30 @@ pub async fn list<E: ActixAdminViewModelTrait>(
.map(|f| { .map(|f| {
let mut kv = f.split("="); let mut kv = f.split("=");
let af = ActixAdminViewModelFilter { 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()), value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
values: None, values: None,
filter_type: None filter_type: None,
}; };
af 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 { match result {
Ok(res) => { Ok(res) => {

View File

@ -2,34 +2,34 @@ mod create_or_edit_get;
pub use create_or_edit_get::{create_get, edit_get}; pub use create_or_edit_get::{create_get, edit_get};
mod create_or_edit_post; 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; mod index;
pub use index::{ index, not_found, get_admin_ctx }; pub use index::{get_admin_ctx, index, not_found};
mod list; mod list;
pub use list::{ list, SortOrder }; pub use list::{list, SortOrder};
mod show; mod show;
pub use show::show; pub use show::show;
mod delete; mod delete;
pub use delete::{ delete, delete_many }; pub use delete::{delete, delete_many};
mod helpers; 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; 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)] #[derive(Debug, Deserialize)]
pub struct Params { pub struct Params {
page: Option<u64>, page: Option<u64>,
entities_per_page: Option<u64>, entities_per_page: Option<u64>,
search: Option<String>, search: Option<String>,
sort_by: Option<String>, sort_by: Option<String>,
sort_order: Option<SortOrder> sort_order: Option<SortOrder>,
} }
const DEFAULT_ENTITIES_PER_PAGE: u64 = 10; const DEFAULT_ENTITIES_PER_PAGE: u64 = 10;

View File

@ -1,17 +1,21 @@
use actix_session::Session;
use actix_web::HttpRequest; use actix_web::HttpRequest;
use actix_web::{error, web, Error, HttpResponse}; use actix_web::{error, web, Error, HttpResponse};
use actix_session::{Session};
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use tera::{Context}; use tera::Context;
use crate::ActixAdminNotification;
use crate::prelude::*; 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::{Params, DEFAULT_ENTITIES_PER_PAGE};
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
pub async fn show<E: ActixAdminViewModelTrait>( pub async fn show<E: ActixAdminViewModelTrait>(
session: Session, req: HttpRequest, data: web::Data<ActixAdmin>, id: web::Path<i32>, db: web::Data<DatabaseConnection> session: Session,
req: HttpRequest,
data: web::Data<ActixAdmin>,
id: web::Path<i32>,
db: web::Data<DatabaseConnection>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let actix_admin = &data.into_inner(); let actix_admin = &data.into_inner();
@ -21,14 +25,14 @@ pub async fn show<E: ActixAdminViewModelTrait>(
if !user_can_access_page(&session, actix_admin, view_model) { if !user_can_access_page(&session, actix_admin, view_model) {
return render_unauthorized(&ctx, &actix_admin); return render_unauthorized(&ctx, &actix_admin);
} }
let mut errors: Vec<crate::ActixAdminError> = Vec::new(); let mut errors: Vec<crate::ActixAdminError> = Vec::new();
let result = E::get_entity(&db, id.into_inner()).await; let result = E::get_entity(&db, id.into_inner()).await;
let model; let model;
match result { match result {
Ok(res) => { Ok(res) => {
model = res; model = res;
}, }
Err(e) => { Err(e) => {
errors.push(e); errors.push(e);
model = ActixAdminModel::create_empty(); model = ActixAdminModel::create_empty();
@ -37,9 +41,10 @@ pub async fn show<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();
@ -51,11 +56,17 @@ pub async fn show<E: ActixAdminViewModelTrait>(
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE); .unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
let render_partial = req.headers().contains_key("HX-Target"); let render_partial = req.headers().contains_key("HX-Target");
let search = params.search.clone().unwrap_or(String::new()); 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); let sort_order = params.sort_order.as_ref().unwrap_or(&SortOrder::Asc);
ctx.insert("model", &model); 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("base_path", &E::get_base_path(&entity_name));
ctx.insert("entity_names", &actix_admin.entity_names); ctx.insert("entity_names", &actix_admin.entity_names);
ctx.insert("notifications", &notifications); ctx.insert("notifications", &notifications);
@ -68,12 +79,10 @@ pub async fn show<E: ActixAdminViewModelTrait>(
add_auth_context(&session, actix_admin, &mut ctx); add_auth_context(&session, actix_admin, &mut ctx);
let body = actix_admin.tera let body = actix_admin.tera.render("show.html", &ctx).map_err(|err| {
.render("show.html", &ctx) #[cfg(enable_tracing)]
.map_err(|err| { tracing::error!("{err}");
#[cfg(enable_tracing)] error::ErrorInternalServerError(format!("{:?}", err))
tracing::error!("{err}"); })?;
error::ErrorInternalServerError(format!("{:?}", err))
})?;
Ok(http_response_code.content_type("text/html").body(body)) Ok(http_response_code.content_type("text/html").body(body))
} }

View File

@ -12,21 +12,26 @@
aria-label="{{ model_field.field_name }}">{{ model.values | get(key=model_field.field_name, default="") }}</textarea> aria-label="{{ model_field.field_name }}">{{ model.values | get(key=model_field.field_name, default="") }}</textarea>
{% elif model_field.field_type == "FileUpload" and 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="") != "" %}
<div> <div>
<a hx-disable href="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}">{{ model.values | <a hx-disable href="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}">
get(key=model_field.field_name, default="") }}</a> {{ model.values | get(key=model_field.field_name, default="") }}
</a>
<a class="is-pulled-right" hx-target="closest div" hx-push-url="false" hx-delete="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}" <a class="is-pulled-right" hx-target="closest div" hx-push-url="false" hx-delete="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}"
hx-confirm="Are you sure?"><i class="fa-solid fa-trash"></i></a> hx-confirm="Are you sure?"><i class="fa-solid fa-trash"></i></a>
</div> </div>
{% else %} {% else %}
<input class="{{ model_field | get_html_input_class }} <input class="{{ model_field | get_html_input_class }}
{% if model.errors | length > 0 or model.custom_errors | length > 0 %} {% if model.errors | length > 0 or model.custom_errors | length > 0 %}
{% if {% if model.errors | get(key=model_field.field_name, default="" ) !="" or model.custom_errors | get(key=model_field.field_name, default="" ) !="" %}
model.errors | get(key=model_field.field_name, default="" ) !="" is-danger
or {% else %}
model.custom_errors | get(key=model_field.field_name, default="" ) !="" is-success
%}is-danger{% else %}is-success{% endif %} {% endif %}
{% 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 }}" type="{{ model_field | get_html_input_type }}"
placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}"> 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 %} {% endif %}

View File

@ -1,12 +1,12 @@
use crate::ActixAdminError;
use crate::{model::ActixAdminModelFilterType, ActixAdminModel, SortOrder};
use actix_session::Session;
use async_trait::async_trait; use async_trait::async_trait;
use regex::Regex; use regex::Regex;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use serde_derive::{Serialize, Deserialize}; use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ActixAdminModel, SortOrder, model::ActixAdminModelFilterType};
use actix_session::{Session};
use std::convert::From; use std::convert::From;
use crate::ActixAdminError;
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait ActixAdminViewModelTrait { pub trait ActixAdminViewModelTrait {
@ -17,16 +17,30 @@ pub trait ActixAdminViewModelTrait {
viewmodel_filter: Vec<ActixAdminViewModelFilter>, viewmodel_filter: Vec<ActixAdminViewModelFilter>,
search: &str, search: &str,
sort_by: &str, sort_by: &str,
sort_order: &SortOrder sort_order: &SortOrder,
) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>; ) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>;
// TODO: Replace return value with proper Result Type containing Ok or Err // TODO: Replace return value with proper Result Type containing Ok or Err
async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError>; async fn create_entity(
db: &DatabaseConnection,
model: ActixAdminModel,
) -> Result<ActixAdminModel, ActixAdminError>;
async fn delete_entity(db: &DatabaseConnection, id: i32) -> Result<bool, ActixAdminError>; async fn delete_entity(db: &DatabaseConnection, id: i32) -> Result<bool, ActixAdminError>;
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError>; async fn get_entity(
async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError>; db: &DatabaseConnection,
async fn get_select_lists(db: &DatabaseConnection) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError>; id: i32,
async fn get_viewmodel_filter(db: &DatabaseConnection) -> HashMap<String, ActixAdminViewModelFilter>; ) -> Result<ActixAdminModel, ActixAdminError>;
async fn edit_entity(
db: &DatabaseConnection,
id: i32,
model: ActixAdminModel,
) -> Result<ActixAdminModel, ActixAdminError>;
async fn get_select_lists(
db: &DatabaseConnection,
) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError>;
async fn get_viewmodel_filter(
db: &DatabaseConnection,
) -> HashMap<String, ActixAdminViewModelFilter>;
fn validate_entity(model: &mut ActixAdminModel); fn validate_entity(model: &mut ActixAdminModel);
fn get_entity_name() -> String; fn get_entity_name() -> String;
@ -40,10 +54,10 @@ pub trait ActixAdminViewModelTrait {
pub struct ActixAdminViewModel { pub struct ActixAdminViewModel {
pub entity_name: String, pub entity_name: String,
pub primary_key: String, pub primary_key: String,
pub fields: &'static[ActixAdminViewModelField], pub fields: &'static [ActixAdminViewModelField],
pub show_search: bool, pub show_search: bool,
pub user_can_access: Option<fn(&Session) -> bool>, pub user_can_access: Option<fn(&Session) -> bool>,
pub default_show_aside: bool pub default_show_aside: bool,
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -52,7 +66,7 @@ pub struct ActixAdminViewModelSerializable {
pub primary_key: String, pub primary_key: String,
pub fields: &'static [ActixAdminViewModelField], pub fields: &'static [ActixAdminViewModelField],
pub show_search: bool, pub show_search: bool,
pub default_show_aside: bool pub default_show_aside: bool,
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -60,7 +74,7 @@ pub struct ActixAdminViewModelFilter {
pub name: String, pub name: String,
pub value: Option<String>, pub value: Option<String>,
pub values: Option<Vec<(String, String)>>, pub values: Option<Vec<(String, String)>>,
pub filter_type: Option<ActixAdminModelFilterType> pub filter_type: Option<ActixAdminModelFilterType>,
} }
// TODO: better alternative to serialize only specific fields for ActixAdminViewModel // TODO: better alternative to serialize only specific fields for ActixAdminViewModel
@ -71,7 +85,7 @@ impl From<ActixAdminViewModel> for ActixAdminViewModelSerializable {
primary_key: entity.primary_key, primary_key: entity.primary_key,
fields: entity.fields, fields: entity.fields,
show_search: entity.show_search, 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, Time,
DateTime, DateTime,
SelectList, SelectList,
FileUpload FileUpload,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -99,11 +113,16 @@ pub struct ActixAdminViewModelField {
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)] #[serde(skip_serializing, skip_deserializing)]
pub list_regex_mask: Option<Regex> pub list_regex_mask: Option<Regex>,
} }
impl ActixAdminViewModelFieldType { 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() { if !select_list.is_empty() {
return ActixAdminViewModelFieldType::SelectList; return ActixAdminViewModelFieldType::SelectList;
} }
@ -118,12 +137,12 @@ impl ActixAdminViewModelFieldType {
"i32" => ActixAdminViewModelFieldType::Number, "i32" => ActixAdminViewModelFieldType::Number,
"i64" => ActixAdminViewModelFieldType::Number, "i64" => ActixAdminViewModelFieldType::Number,
"usize" => ActixAdminViewModelFieldType::Number, "usize" => ActixAdminViewModelFieldType::Number,
"String" => ActixAdminViewModelFieldType::Text, "String" => ActixAdminViewModelFieldType::Text,
"bool" => ActixAdminViewModelFieldType::Checkbox, "bool" => ActixAdminViewModelFieldType::Checkbox,
"DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime, "DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime,
"DateTime" => ActixAdminViewModelFieldType::DateTime, "DateTime" => ActixAdminViewModelFieldType::DateTime,
"Date" => ActixAdminViewModelFieldType::Date, "Date" => ActixAdminViewModelFieldType::Date,
_ => ActixAdminViewModelFieldType::Text _ => ActixAdminViewModelFieldType::Text,
} }
} }
} }

View File

@ -4,6 +4,8 @@ use test_setup::prelude::*;
#[cfg(test)] #[cfg(test)]
mod get_request_is_success { mod get_request_is_success {
extern crate serde_derive; extern crate serde_derive;
use super::create_app;
use super::BodyTest;
use actix_admin::prelude::*; use actix_admin::prelude::*;
use actix_web::body::to_bytes; use actix_web::body::to_bytes;
use actix_web::test; use actix_web::test;
@ -12,8 +14,6 @@ mod get_request_is_success {
use sea_orm::EntityTrait; use sea_orm::EntityTrait;
use sea_orm::PaginatorTrait; use sea_orm::PaginatorTrait;
use sea_orm::QueryOrder; use sea_orm::QueryOrder;
use super::create_app;
use super::BodyTest;
#[actix_web::test] #[actix_web::test]
async fn get_admin_index() { async fn get_admin_index() {
@ -33,12 +33,17 @@ mod get_request_is_success {
let db = super::setup_db(true).await; let db = super::setup_db(true).await;
let page = 5; let page = 5;
let page_size = 50; // Verify with default size in list.rs 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) .order_by_asc(crate::post::Column::Id)
.paginate(&db, page_size) .paginate(&db, page_size)
.fetch_page(page-1) .fetch_page(page - 1)
.await .await
.unwrap(); .unwrap();
@ -49,9 +54,12 @@ mod get_request_is_success {
#[actix_web::test] #[actix_web::test]
async fn get_post_list_search() { async fn get_post_list_search() {
let db = super::setup_db(true).await; 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] #[actix_web::test]
@ -59,7 +67,12 @@ mod get_request_is_success {
let db = super::setup_db(true).await; let db = super::setup_db(true).await;
let search_string_encoded = "Test%2015"; let search_string_encoded = "Test%2015";
let entities_per_page = 11; 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(); let mut elements_to_verify = Vec::new();
elements_to_verify.push("Test 15".to_string()); elements_to_verify.push("Test 15".to_string());
@ -76,17 +89,22 @@ mod get_request_is_success {
let page = 17; let page = 17;
let page_size = 20; // Verify with default size in list.rs 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 { let query = if page_size == 5 {
crate::Comment::find().order_by_asc(crate::comment::Column::Id) crate::Comment::find().order_by_asc(crate::comment::Column::Id)
} else { } else {
crate::Comment::find().order_by_asc(crate::comment::Column::Id) crate::Comment::find().order_by_asc(crate::comment::Column::Id)
}; };
let entities = query let entities = query
.paginate(&db, page_size) .paginate(&db, page_size)
.fetch_page(page-1) .fetch_page(page - 1)
.await .await
.unwrap(); .unwrap();
@ -119,11 +137,7 @@ mod get_request_is_success {
async fn get_comment_show() { async fn get_comment_show() {
let db = super::setup_db(true).await; let db = super::setup_db(true).await;
let url = format!( let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1);
"/admin/{}/show/{}",
crate::Comment::get_entity_name(),
1
);
test_get_is_success(url.as_str(), &db).await test_get_is_success(url.as_str(), &db).await
} }
@ -131,39 +145,38 @@ mod get_request_is_success {
async fn get_post_show() { async fn get_post_show() {
let db = super::setup_db(true).await; let db = super::setup_db(true).await;
let url = format!( let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1);
"/admin/{}/show/{}",
crate::Comment::get_entity_name(),
1
);
test_get_is_success(url.as_str(), &db).await test_get_is_success(url.as_str(), &db).await
} }
async fn test_response_contains(url: &str, db: &DatabaseConnection, elements_to_verify: Vec<String>) { async fn test_response_contains(
let app = create_app!(db); url: &str,
db: &DatabaseConnection,
elements_to_verify: Vec<String>,
) {
let app = create_app!(db);
let req = test::TestRequest::get() let req = test::TestRequest::get().uri(url).to_request();
.uri(url)
.to_request();
let resp = test::call_service(&app, req).await; let resp = test::call_service(&app, req).await;
let body = to_bytes(resp.into_body()).await.unwrap(); let body = to_bytes(resp.into_body()).await.unwrap();
let body = body.as_str(); let body = body.as_str();
for element in elements_to_verify { 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) { 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() let req = test::TestRequest::get().uri(url).to_request();
.uri(url)
.to_request();
let resp = test::call_service(&app, req).await; let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success()); assert!(resp.status().is_success());
} }
} }

View File

@ -4,16 +4,12 @@ use test_setup::prelude::*;
#[cfg(test)] #[cfg(test)]
mod post_create_and_edit_is_success { mod post_create_and_edit_is_success {
use actix_admin::prelude::*; use actix_admin::prelude::*;
use actix_web::{ use actix_web::{http::header::ContentType, test, App};
test, use chrono::{NaiveDate, NaiveDateTime};
App, use sea_orm::{prelude::Decimal, EntityTrait, PaginatorTrait};
http::header::ContentType use serde::Serialize;
};
use chrono::{ NaiveDateTime, NaiveDate }; use crate::create_app;
use serde::{Serialize};
use sea_orm::{ PaginatorTrait, EntityTrait, prelude::Decimal};
use crate::{create_app};
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct CommentModel { pub struct CommentModel {
@ -23,7 +19,7 @@ mod post_create_and_edit_is_success {
user: &'static str, user: &'static str,
is_visible: &'static str, is_visible: &'static str,
post_id: Option<&'static str>, post_id: Option<&'static str>,
my_decimal: &'static str my_decimal: &'static str,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
@ -48,9 +44,9 @@ mod post_create_and_edit_is_success {
user: "test", user: "test",
is_visible: "true", is_visible: "true",
post_id: None, 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() let req = test::TestRequest::post()
.insert_header(ContentType::form_url_encoded()) .insert_header(ContentType::form_url_encoded())
.uri("/admin/comment/create_post_from_plaintext") .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"); assert_eq!(entities.len(), 1, "After post, db does not contain 1 model");
let entity = entities.first().unwrap(); let entity = entities.first().unwrap();
assert_eq!(entity.id, 1); assert_eq!(entity.id, 1);
assert_eq!(entity.comment,"test"); assert_eq!(entity.comment, "test");
assert_eq!(entity.user, "test"); assert_eq!(entity.user, "test");
assert!(entity.is_visible); assert!(entity.is_visible);
assert!(entity.post_id.is_none()); assert!(entity.post_id.is_none());
assert_eq!(entity.my_decimal, Decimal::new(113141, 3)); 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 // update entity
model.my_decimal = "213.141"; model.my_decimal = "213.141";
@ -98,7 +97,11 @@ mod post_create_and_edit_is_success {
.await .await
.expect("could not retrieve entities"); .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(); let entity = entities.first().unwrap();
assert_eq!(entity.id, 1); assert_eq!(entity.id, 1);
assert_eq!(entity.comment, "updated"); assert_eq!(entity.comment, "updated");
@ -106,9 +109,12 @@ mod post_create_and_edit_is_success {
assert!(!entity.is_visible); assert!(!entity.is_visible);
assert!(entity.post_id.is_none()); assert!(entity.post_id.is_none());
assert_eq!(entity.my_decimal, Decimal::new(213141, 3)); 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] #[actix_web::test]
async fn post_create_and_edit() { async fn post_create_and_edit() {
let db = super::setup_db(false).await; let db = super::setup_db(false).await;
@ -119,7 +125,7 @@ mod post_create_and_edit_is_success {
insert_date: "1977-04-01", insert_date: "1977-04-01",
title: "test", title: "test",
text: "test", text: "test",
tea_mandatory: "EverydayTea" tea_mandatory: "EverydayTea",
}; };
let req = test::TestRequest::post() 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"); assert_eq!(entities.len(), 1, "After post, db does not contain 1 model");
let entity = entities.first().unwrap(); let entity = entities.first().unwrap();
assert_eq!(entity.id, 1); 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.title, model.title);
assert_eq!(entity.text, model.text); 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 // update entity
model.tea_mandatory = "BreakfastTea"; model.tea_mandatory = "BreakfastTea";
@ -166,11 +178,18 @@ mod post_create_and_edit_is_success {
.await .await
.expect("could not retrieve entities"); .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(); let entity = entities.first().unwrap();
assert_eq!(entity.id, 1); assert_eq!(entity.id, 1);
assert_eq!(entity.text, "updated"); assert_eq!(entity.text, "updated");
assert_eq!(entity.title, "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()
);
} }
} }

View File

@ -1,10 +1,18 @@
use super::Post;
use actix_admin::prelude::*;
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use actix_admin::prelude::*;
use super::Post;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, #[derive(
DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel Clone,
Debug,
PartialEq,
DeriveEntityModel,
Deserialize,
Serialize,
DeriveActixAdmin,
DeriveActixAdminModel,
DeriveActixAdminViewModel,
)] )]
#[sea_orm(table_name = "comment")] #[sea_orm(table_name = "comment")]
pub struct Model { pub struct Model {
@ -20,9 +28,9 @@ pub struct Model {
#[sea_orm(column_type = "DateTime")] #[sea_orm(column_type = "DateTime")]
pub insert_date: DateTime, pub insert_date: DateTime,
pub is_visible: bool, pub is_visible: bool,
#[actix_admin(select_list="Post")] #[actix_admin(select_list = "Post")]
pub post_id: Option<i32>, pub post_id: Option<i32>,
pub my_decimal: Decimal pub my_decimal: Decimal,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@ -47,10 +55,13 @@ impl ActixAdminModelValidationTrait<ActiveModel> for Entity {
fn validate(model: &ActiveModel) -> HashMap<String, String> { fn validate(model: &ActiveModel) -> HashMap<String, String> {
let mut errors = HashMap::new(); let mut errors = HashMap::new();
if model.my_decimal.clone().unwrap() < Decimal::from(100 as i16) { 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 errors
} }
} }
impl ActixAdminModelFilterTrait<Entity> for Entity {} impl ActixAdminModelFilterTrait<Entity> for Entity {}

View File

@ -1,10 +1,10 @@
use actix_admin::prelude::*; use actix_admin::prelude::*;
use actix_session::Session; use actix_session::Session;
use actix_web::HttpRequest;
use actix_web::web; use actix_web::web;
use actix_web::Error;
use actix_web::HttpResponse;
use actix_web::web::Bytes; use actix_web::web::Bytes;
use actix_web::Error;
use actix_web::HttpRequest;
use actix_web::HttpResponse;
use chrono::Local; use chrono::Local;
use sea_orm::prelude::Decimal; use sea_orm::prelude::Decimal;
use sea_orm::{ConnectOptions, DatabaseConnection, EntityTrait, Set}; use sea_orm::{ConnectOptions, DatabaseConnection, EntityTrait, Set};
@ -78,7 +78,7 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder {
login_link: None, login_link: None,
logout_link: None, logout_link: None,
file_upload_directory: "./file_uploads", file_upload_directory: "./file_uploads",
navbar_title: "test" navbar_title: "test",
}; };
let mut admin_builder = ActixAdminBuilder::new(configuration); let mut admin_builder = ActixAdminBuilder::new(configuration);
@ -157,4 +157,4 @@ impl BodyTest for Bytes {
fn as_str(&self) -> &str { fn as_str(&self) -> &str {
std::str::from_utf8(self).unwrap() std::str::from_utf8(self).unwrap()
} }
} }

View File

@ -1,22 +1,18 @@
// setup // 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}; use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult};
pub mod comment; pub mod comment;
pub mod post;
pub mod helper; pub mod helper;
pub mod post;
pub use comment::Entity as Comment; pub use comment::Entity as Comment;
pub use post::Entity as Post; pub use post::Entity as Post;
pub mod prelude { pub mod prelude {
pub use crate::test_setup::helper::{
create_actix_admin_builder,
setup_db,
BodyTest
};
pub use super::comment; pub use super::comment;
pub use super::post; pub use super::post;
pub use super::Comment; pub use super::Comment;
pub use super::Post; pub use super::Post;
pub use crate::test_setup::helper::{create_actix_admin_builder, setup_db, BodyTest};
} }
// setup // setup
@ -38,7 +34,11 @@ pub async fn create_tables(db: &DbConn) -> Result<ExecResult, DbErr> {
) )
.col(ColumnDef::new(post::Column::Title).string().not_null()) .col(ColumnDef::new(post::Column::Title).string().not_null())
.col(ColumnDef::new(post::Column::Text).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::TeaOptional).string())
.col(ColumnDef::new(post::Column::InsertDate).date()) .col(ColumnDef::new(post::Column::InsertDate).date())
.to_owned(); .to_owned();
@ -57,9 +57,21 @@ pub async fn create_tables(db: &DbConn) -> Result<ExecResult, DbErr> {
) )
.col(ColumnDef::new(comment::Column::Comment).string().not_null()) .col(ColumnDef::new(comment::Column::Comment).string().not_null())
.col(ColumnDef::new(comment::Column::User).string().not_null()) .col(ColumnDef::new(comment::Column::User).string().not_null())
.col(ColumnDef::new(comment::Column::InsertDate).date_time().not_null()) .col(
.col(ColumnDef::new(comment::Column::IsVisible).boolean().not_null()) ColumnDef::new(comment::Column::InsertDate)
.col(ColumnDef::new(comment::Column::MyDecimal).decimal().not_null()) .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()) .col(ColumnDef::new(comment::Column::PostId).integer())
.foreign_key( .foreign_key(
ForeignKeyCreateStatement::new() ForeignKeyCreateStatement::new()

View File

@ -1,11 +1,22 @@
use actix_admin::prelude::*;
use sea_orm::entity::prelude::*; use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use actix_admin::prelude::*;
use std::fmt; use std::fmt;
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr; 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")] #[sea_orm(table_name = "post")]
pub struct Model { pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
@ -17,9 +28,9 @@ pub struct Model {
#[sea_orm(column_type = "Text")] #[sea_orm(column_type = "Text")]
#[actix_admin(searchable, textarea)] #[actix_admin(searchable, textarea)]
pub text: String, pub text: String,
#[actix_admin(select_list="Tea")] #[actix_admin(select_list = "Tea")]
pub tea_mandatory: Tea, pub tea_mandatory: Tea,
#[actix_admin(select_list="Tea")] #[actix_admin(select_list = "Tea")]
pub tea_optional: Option<Tea>, pub tea_optional: Option<Tea>,
pub insert_date: Date, pub insert_date: Date,
} }
@ -27,7 +38,7 @@ pub struct Model {
impl Display for Model { impl Display for Model {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &*self { match &*self {
_ => write!(formatter, "{} {}", &self.title, &self.insert_date), _ => write!(formatter, "{} {}", &self.title, &self.insert_date),
} }
} }
} }
@ -46,7 +57,20 @@ impl Related<super::comment::Entity> for Entity {
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
<<<<<<< HEAD
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveDisplay, DeriveActiveEnum, Deserialize, Serialize, DeriveActixAdminEnumSelectList)] #[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")] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea { pub enum Tea {
#[sea_orm(string_value = "EverydayTea")] #[sea_orm(string_value = "EverydayTea")]
@ -60,9 +84,9 @@ impl FromStr for Tea {
fn from_str(input: &str) -> Result<Tea, Self::Err> { fn from_str(input: &str) -> Result<Tea, Self::Err> {
match input { match input {
"EverydayTea" => Ok(Tea::EverydayTea), "EverydayTea" => Ok(Tea::EverydayTea),
"BreakfastTea" => Ok(Tea::BreakfastTea), "BreakfastTea" => Ok(Tea::BreakfastTea),
_ => Err(()), _ => Err(()),
} }
} }
} }