Trim input name
This commit is contained in:
parent
ad79a3852a
commit
40fd0863fc
193
src/builder.rs
193
src/builder.rs
@ -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 =
|
||||||
|
15
src/lib.rs
15
src/lib.rs
@ -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())
|
||||||
|
67
src/model.rs
67
src/model.rs
@ -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),
|
||||||
|
@ -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)
|
||||||
})?;
|
})?;
|
||||||
|
@ -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", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
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)
|
||||||
})?;
|
})?;
|
||||||
|
@ -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());
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
@ -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", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user