Trim input name
This commit is contained in:
parent
ad79a3852a
commit
40fd0863fc
191
src/builder.rs
191
src/builder.rs
@ -3,8 +3,13 @@ use actix_web::{web, Route };
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use crate::routes::{
|
||||
create_get, create_post, delete, delete_many, edit_get, edit_post, index, list, not_found, show, download
|
||||
create_get, create_post, delete, delete_many, download, edit_get, edit_post, index, list,
|
||||
not_found, show,
|
||||
};
|
||||
use crate::{prelude::*, routes::delete_file, ActixAdminMenuElement};
|
||||
use std::hash::BuildHasher;
|
||||
use tera::Tera;
|
||||
use tera::{to_value, try_get_value, Result};
|
||||
|
||||
/// Represents a builder entity which helps generating the ActixAdmin configuration
|
||||
pub struct ActixAdminBuilder {
|
||||
@ -21,9 +26,7 @@ pub trait ActixAdminBuilderTrait {
|
||||
&mut self,
|
||||
view_model: &ActixAdminViewModel,
|
||||
);
|
||||
fn add_entity_to_category<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_entity_to_category<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
view_model: &ActixAdminViewModel,
|
||||
category_name: &str,
|
||||
@ -41,20 +44,16 @@ pub trait ActixAdminBuilderTrait {
|
||||
path: &str,
|
||||
route: Route,
|
||||
add_to_menu: bool,
|
||||
category: &str
|
||||
category: &str,
|
||||
);
|
||||
fn add_custom_handler_for_entity<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_custom_handler_for_entity<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
menu_element_name: &str,
|
||||
path: &str,
|
||||
route: Route,
|
||||
add_to_menu: bool
|
||||
add_to_menu: bool,
|
||||
);
|
||||
fn add_custom_handler_for_entity_in_category<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_custom_handler_for_entity_in_category<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
menu_element_name: &str,
|
||||
path: &str,
|
||||
@ -67,6 +66,139 @@ pub trait ActixAdminBuilderTrait {
|
||||
fn get_actix_admin(&self) -> ActixAdmin;
|
||||
}
|
||||
|
||||
fn get_html_input_class<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 {
|
||||
fn new(configuration: ActixAdminConfiguration) -> Self {
|
||||
ActixAdminBuilder {
|
||||
@ -89,9 +221,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
let _ = &self.add_entity_to_category::<E>(view_model, "");
|
||||
}
|
||||
|
||||
fn add_entity_to_category<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_entity_to_category<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
view_model: &ActixAdminViewModel,
|
||||
category_name: &str,
|
||||
@ -108,11 +238,19 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
.route("/delete/{id}", web::delete().to(delete::<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::delete().to(delete_file::<E>))
|
||||
.default_service(web::to(not_found))
|
||||
.route(
|
||||
"/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 menu_element = ActixAdminMenuElement {
|
||||
@ -145,7 +283,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
path: &str,
|
||||
route: Route,
|
||||
add_to_menu: bool,
|
||||
category_name: &str
|
||||
category_name: &str,
|
||||
) {
|
||||
self.custom_routes.push((path.to_string(), route));
|
||||
|
||||
@ -168,7 +306,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
self.actix_admin
|
||||
.entity_names
|
||||
.insert(category_name.to_string(), entity_list);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,14 +316,12 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
menu_element_name: &str,
|
||||
path: &str,
|
||||
route: Route,
|
||||
add_to_menu: bool
|
||||
add_to_menu: bool,
|
||||
) {
|
||||
self.add_custom_handler_to_category(menu_element_name, path, route, add_to_menu, "");
|
||||
}
|
||||
|
||||
fn add_custom_handler_for_entity<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_custom_handler_for_entity<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
menu_element_name: &str,
|
||||
path: &str,
|
||||
@ -201,9 +337,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
fn add_custom_handler_for_entity_in_category<
|
||||
E: ActixAdminViewModelTrait + 'static,
|
||||
>(
|
||||
fn add_custom_handler_for_entity_in_category<E: ActixAdminViewModelTrait + 'static>(
|
||||
&mut self,
|
||||
menu_element_name: &str,
|
||||
path: &str,
|
||||
@ -222,8 +356,7 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
||||
match existing_scope {
|
||||
Some(scope) => {
|
||||
let existing_scope = scope.route(path, route);
|
||||
self.scopes
|
||||
.insert(E::get_entity_name(), existing_scope);
|
||||
self.scopes.insert(E::get_entity_name(), existing_scope);
|
||||
}
|
||||
_ => {
|
||||
let new_scope =
|
||||
|
15
src/lib.rs
15
src/lib.rs
@ -14,8 +14,8 @@ use async_trait::async_trait;
|
||||
use derive_more::{Display, Error};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde_derive::Serialize;
|
||||
use tera::Tera;
|
||||
use std::collections::HashMap;
|
||||
use tera::Tera;
|
||||
|
||||
pub mod builder;
|
||||
pub mod model;
|
||||
@ -25,11 +25,14 @@ pub mod tera_templates;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait};
|
||||
pub use crate::model::{ActixAdminModel, ActixAdminModelTrait, ActixAdminModelValidationTrait, ActixAdminModelFilter, ActixAdminModelFilterTrait, ActixAdminModelFilterType};
|
||||
pub use crate::model::{
|
||||
ActixAdminModel, ActixAdminModelFilter, ActixAdminModelFilterTrait,
|
||||
ActixAdminModelFilterType, ActixAdminModelTrait, ActixAdminModelValidationTrait,
|
||||
};
|
||||
pub use crate::routes::{create_or_edit_post, get_admin_ctx, SortOrder};
|
||||
pub use crate::view_model::{
|
||||
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
||||
ActixAdminViewModelSerializable, ActixAdminViewModelTrait, ActixAdminViewModelFilter
|
||||
ActixAdminViewModelFilter, ActixAdminViewModelSerializable, ActixAdminViewModelTrait,
|
||||
};
|
||||
pub use crate::{hashmap, ActixAdminSelectListTrait};
|
||||
pub use crate::{ActixAdmin, ActixAdminConfiguration, ActixAdminError};
|
||||
@ -69,7 +72,7 @@ pub struct ActixAdminConfiguration {
|
||||
pub login_link: Option<String>,
|
||||
pub logout_link: Option<String>,
|
||||
pub file_upload_directory: &'static str,
|
||||
pub navbar_title: &'static str
|
||||
pub navbar_title: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -77,7 +80,7 @@ pub struct ActixAdmin {
|
||||
pub entity_names: HashMap<String, Vec<ActixAdminMenuElement>>,
|
||||
pub view_models: HashMap<String, ActixAdminViewModel>,
|
||||
pub configuration: ActixAdminConfiguration,
|
||||
pub tera: Tera
|
||||
pub tera: Tera,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Serialize)]
|
||||
@ -117,7 +120,7 @@ pub enum ActixAdminError {
|
||||
|
||||
impl error::ResponseError for ActixAdminError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::debug!("{self:#?}");
|
||||
HttpResponse::build(self.status_code())
|
||||
.insert_header(ContentType::html())
|
||||
|
55
src/model.rs
55
src/model.rs
@ -22,7 +22,7 @@ pub trait ActixAdminModelTrait {
|
||||
filter_values: HashMap<String, Option<String>>,
|
||||
search: &str,
|
||||
sort_by: &str,
|
||||
sort_order: &SortOrder
|
||||
sort_order: &SortOrder,
|
||||
) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>;
|
||||
fn get_fields() -> &'static [ActixAdminViewModelField];
|
||||
fn validate_model(model: &mut ActixAdminModel);
|
||||
@ -38,7 +38,7 @@ pub struct ActixAdminModelFilter<E: EntityTrait> {
|
||||
pub name: String,
|
||||
pub filter_type: ActixAdminModelFilterType,
|
||||
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)]
|
||||
@ -47,7 +47,7 @@ pub enum ActixAdminModelFilterType {
|
||||
SelectList,
|
||||
Date,
|
||||
DateTime,
|
||||
Checkbox
|
||||
Checkbox,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -55,7 +55,10 @@ pub trait ActixAdminModelFilterTrait<E: EntityTrait> {
|
||||
fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -66,7 +69,7 @@ impl<T: EntityTrait> From<ActixAdminModelFilter<T>> for ActixAdminViewModelFilte
|
||||
name: filter.name,
|
||||
value: None,
|
||||
values: None,
|
||||
filter_type: Some(filter.filter_type)
|
||||
filter_type: Some(filter.filter_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +93,8 @@ impl ActixAdminModel {
|
||||
}
|
||||
|
||||
pub async fn create_from_payload(
|
||||
mut payload: Multipart, file_upload_folder: &str
|
||||
mut payload: Multipart,
|
||||
file_upload_folder: &str,
|
||||
) -> Result<ActixAdminModel, MultipartError> {
|
||||
let mut hashmap = HashMap::<String, String>::new();
|
||||
|
||||
@ -115,21 +119,28 @@ impl ActixAdminModel {
|
||||
let file_exists = std::path::Path::new(&file_path).exists();
|
||||
// Avoid overwriting existing files
|
||||
if file_exists {
|
||||
filename = format!("{}_{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), filename);
|
||||
filename = format!(
|
||||
"{}_{}",
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
filename
|
||||
);
|
||||
file_path = format!("{}/{}", file_upload_folder, filename);
|
||||
}
|
||||
|
||||
let file = File::create(file_path);
|
||||
let _res = file.unwrap().write_all(&binary_data);
|
||||
|
||||
hashmap.insert(
|
||||
field.name().to_string(),
|
||||
filename.clone()
|
||||
);
|
||||
hashmap.insert(field.name().to_string(), filename.clone());
|
||||
} else {
|
||||
let res_string = String::from_utf8(binary_data);
|
||||
if res_string.is_ok() {
|
||||
hashmap.insert(field.name().to_string(), res_string.unwrap());
|
||||
hashmap.insert(
|
||||
field.name().to_string().trim().to_string(),
|
||||
res_string.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,16 +157,18 @@ impl ActixAdminModel {
|
||||
&self,
|
||||
key: &str,
|
||||
is_option_or_string: bool,
|
||||
is_allowed_to_be_empty: bool
|
||||
is_allowed_to_be_empty: bool,
|
||||
) -> Result<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(
|
||||
&self,
|
||||
key: &str,
|
||||
is_option_or_string: bool,
|
||||
is_allowed_to_be_empty: bool
|
||||
is_allowed_to_be_empty: bool,
|
||||
) -> Result<Option<NaiveDateTime>, String> {
|
||||
self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
|
||||
NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M")
|
||||
@ -166,15 +179,21 @@ impl ActixAdminModel {
|
||||
&self,
|
||||
key: &str,
|
||||
is_option_or_string: bool,
|
||||
is_allowed_to_be_empty: bool
|
||||
is_allowed_to_be_empty: bool,
|
||||
) -> Result<Option<NaiveDate>, String> {
|
||||
self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty, |val| {
|
||||
NaiveDate::parse_from_str(val, "%Y-%m-%d")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, key: &str, is_option_or_string: bool, is_allowed_to_be_empty: bool) -> Result<Option<bool>, String> {
|
||||
let val = self.get_value_by_closure(key, is_option_or_string, is_allowed_to_be_empty ,|val| {
|
||||
pub fn get_bool(
|
||||
&self,
|
||||
key: &str,
|
||||
is_option_or_string: bool,
|
||||
is_allowed_to_be_empty: bool,
|
||||
) -> 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 {
|
||||
|
@ -1,14 +1,14 @@
|
||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::{Context};
|
||||
use actix_session::{Session};
|
||||
use crate::prelude::*;
|
||||
use crate::ActixAdminError;
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::prelude::*;
|
||||
use actix_session::Session;
|
||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::Context;
|
||||
|
||||
use super::DEFAULT_ENTITIES_PER_PAGE;
|
||||
use super::Params;
|
||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||
use super::DEFAULT_ENTITIES_PER_PAGE;
|
||||
use super::{add_auth_context, render_unauthorized, user_can_access_page};
|
||||
|
||||
pub async fn create_get<E: ActixAdminViewModelTrait>(
|
||||
session: Session,
|
||||
@ -30,7 +30,7 @@ pub async fn edit_get<E: ActixAdminViewModelTrait>(
|
||||
data: web::Data<ActixAdmin>,
|
||||
db: web::Data<DatabaseConnection>,
|
||||
_text: String,
|
||||
id: web::Path<i32>
|
||||
id: web::Path<i32>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let db = db.get_ref();
|
||||
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
|
||||
}
|
||||
|
||||
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 mut ctx = Context::new();
|
||||
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 {
|
||||
Ok(res) => {
|
||||
model = res;
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{e}");
|
||||
errors.push(e);
|
||||
model = ActixAdminModel::create_empty();
|
||||
@ -70,7 +76,8 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
|
||||
true => HttpResponse::Ok(),
|
||||
false => HttpResponse::InternalServerError(),
|
||||
};
|
||||
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||
let notifications: Vec<ActixAdminNotification> = errors
|
||||
.into_iter()
|
||||
.map(ActixAdminNotification::from)
|
||||
.collect();
|
||||
|
||||
@ -82,10 +89,16 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
|
||||
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
|
||||
let render_partial = req.headers().contains_key("HX-Target");
|
||||
let search = params.search.clone().unwrap_or(String::new());
|
||||
let sort_by = params.sort_by.clone().unwrap_or(view_model.primary_key.to_string());
|
||||
let sort_by = params
|
||||
.sort_by
|
||||
.clone()
|
||||
.unwrap_or(view_model.primary_key.to_string());
|
||||
let sort_order = params.sort_order.as_ref().unwrap_or(&SortOrder::Asc);
|
||||
|
||||
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));
|
||||
ctx.insert(
|
||||
"view_model",
|
||||
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
||||
);
|
||||
ctx.insert("select_lists", &E::get_select_lists(db).await?);
|
||||
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||
ctx.insert("model", &model);
|
||||
@ -97,10 +110,11 @@ async fn create_or_edit_get<E: ActixAdminViewModelTrait>(session: &Session, req:
|
||||
ctx.insert("sort_order", &sort_order);
|
||||
ctx.insert("page", &page);
|
||||
|
||||
let body = actix_admin.tera
|
||||
let body = actix_admin
|
||||
.tera
|
||||
.render("create_or_edit.html", &ctx)
|
||||
.map_err(|err| {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{err}");
|
||||
error::ErrorInternalServerError(err)
|
||||
})?;
|
||||
|
@ -50,15 +50,7 @@ pub async fn edit_post<E: ActixAdminViewModelTrait>(
|
||||
),
|
||||
)
|
||||
.await;
|
||||
create_or_edit_post::<E>(
|
||||
&session,
|
||||
req,
|
||||
db,
|
||||
model,
|
||||
Some(id.into_inner()),
|
||||
actix_admin,
|
||||
)
|
||||
.await
|
||||
create_or_edit_post::<E>(&session, req, db, model, Some(id.into_inner()), actix_admin).await
|
||||
}
|
||||
|
||||
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 mut model = model_res.unwrap();
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
{
|
||||
tracing::debug!("Entity model: {:#?}", model);
|
||||
}
|
||||
@ -90,7 +82,7 @@ pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
|
||||
|
||||
if model.has_errors() {
|
||||
errors.push(ActixAdminError::ValidationErrors);
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
{
|
||||
tracing::error!("OP errors: {errors:#?}");
|
||||
tracing::debug!("Model errors: {:#?}", model.errors);
|
||||
@ -134,7 +126,7 @@ pub async fn create_or_edit_post<E: ActixAdminViewModelTrait>(
|
||||
.finish())
|
||||
}
|
||||
Err(e) => {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{e}");
|
||||
errors.push(e);
|
||||
render_form::<E>(
|
||||
@ -197,17 +189,18 @@ async fn render_form<E: ActixAdminViewModelTrait>(
|
||||
let notifications: Vec<ActixAdminNotification> = errors
|
||||
.into_iter()
|
||||
.map(|err| {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{err}");
|
||||
ActixAdminNotification::from(err)
|
||||
})
|
||||
.collect();
|
||||
|
||||
ctx.insert("notifications", ¬ifications);
|
||||
let body = actix_admin.tera
|
||||
let body = actix_admin
|
||||
.tera
|
||||
.render("create_or_edit.html", &ctx)
|
||||
.map_err(|err| {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{err}");
|
||||
error::ErrorInternalServerError(err)
|
||||
})?;
|
||||
|
@ -77,7 +77,11 @@ pub async fn delete_many<E: ActixAdminViewModelTrait>(
|
||||
let db = &db.get_ref();
|
||||
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
|
||||
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")
|
||||
.map(|e| e.1.to_string())
|
||||
.unwrap_or("10".to_string());
|
||||
let search = form.iter()
|
||||
let search = form
|
||||
.iter()
|
||||
.find(|el| el.0 == "search")
|
||||
.map(|e| e.1.to_string())
|
||||
.unwrap_or_default();
|
||||
let sort_by = form.iter()
|
||||
let sort_by = form
|
||||
.iter()
|
||||
.find(|el| el.0 == "sort_by")
|
||||
.map(|e| e.1.to_string())
|
||||
.unwrap_or("id".to_string());
|
||||
let sort_order = form.iter()
|
||||
let sort_order = form
|
||||
.iter()
|
||||
.find(|el| el.0 == "sort_order")
|
||||
.map(|e| e.1.to_string())
|
||||
.unwrap_or("Asc".to_string());
|
||||
let page = form.iter()
|
||||
let page = form
|
||||
.iter()
|
||||
.find(|el| el.0 == "page")
|
||||
.map(|e| e.1.to_string())
|
||||
.unwrap_or("1".to_string());
|
||||
|
@ -1,12 +1,18 @@
|
||||
use actix_web::{web, error, Error, HttpResponse, HttpRequest};
|
||||
use actix_session::{Session};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::{Context};
|
||||
use crate::prelude::*;
|
||||
use actix_session::Session;
|
||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::Context;
|
||||
|
||||
use super::{ user_can_access_page, render_unauthorized};
|
||||
use super::{render_unauthorized, user_can_access_page};
|
||||
|
||||
pub async fn download<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 db = &db.into_inner();
|
||||
|
||||
@ -24,25 +30,36 @@ pub async fn download<E: ActixAdminViewModelTrait>(req: HttpRequest, session: Se
|
||||
match result {
|
||||
Ok(res) => {
|
||||
model = res;
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
model = ActixAdminModel::create_empty();
|
||||
}
|
||||
}
|
||||
|
||||
let file_name = model.get_value::<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_name = model
|
||||
.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;
|
||||
|
||||
match file {
|
||||
Ok(file) => Ok(file.into_response(&req)),
|
||||
Err(_e) => Ok(HttpResponse::NotFound().content_type("text/html").body(""))
|
||||
Err(_e) => Ok(HttpResponse::NotFound().content_type("text/html").body("")),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub async fn delete_file<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 mut ctx = Context::new();
|
||||
@ -59,27 +76,38 @@ pub async fn delete_file<E: ActixAdminViewModelTrait>(session: Session, data: we
|
||||
match result {
|
||||
Ok(res) => {
|
||||
model = res;
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
model = ActixAdminModel::create_empty();
|
||||
}
|
||||
}
|
||||
|
||||
let file_name = model.get_value::<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_name = model
|
||||
.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();
|
||||
model.values.remove(&column_name);
|
||||
let _edit_res = E::edit_entity(db.get_ref(), id, model.clone()).await;
|
||||
|
||||
let view_model_field = &view_model.fields.iter().find(|field| field.field_name == column_name).unwrap();
|
||||
let view_model_field = &view_model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.field_name == column_name)
|
||||
.unwrap();
|
||||
ctx.insert("model_field", view_model_field);
|
||||
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||
ctx.insert("model", &model);
|
||||
|
||||
let body = actix_admin.tera
|
||||
let body = actix_admin
|
||||
.tera
|
||||
.render("form_elements/input.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))? ;
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
use actix_session::{Session};
|
||||
use tera::{Context};
|
||||
use actix_session::Session;
|
||||
use tera::Context;
|
||||
|
||||
use crate::prelude::*;
|
||||
use actix_web::{error, Error, HttpResponse};
|
||||
|
||||
|
||||
pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut Context) {
|
||||
let enable_auth = &actix_admin.configuration.enable_auth;
|
||||
ctx.insert("enable_auth", &enable_auth);
|
||||
@ -12,26 +11,53 @@ pub fn add_auth_context(session: &Session, actix_admin: &ActixAdmin, ctx: &mut C
|
||||
if *enable_auth {
|
||||
let func = &actix_admin.configuration.user_is_logged_in.unwrap();
|
||||
ctx.insert("user_is_logged_in", &func(session));
|
||||
ctx.insert("login_link", &actix_admin.configuration.login_link.as_ref().unwrap_or(&String::new()));
|
||||
ctx.insert("logout_link", &actix_admin.configuration.logout_link.as_ref().unwrap_or(&String::new()));
|
||||
ctx.insert(
|
||||
"login_link",
|
||||
&actix_admin
|
||||
.configuration
|
||||
.login_link
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
ctx.insert(
|
||||
"logout_link",
|
||||
&actix_admin
|
||||
.configuration
|
||||
.logout_link
|
||||
.as_ref()
|
||||
.unwrap_or(&String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn user_can_access_page(session: &Session, actix_admin: &ActixAdmin, view_model: &ActixAdminViewModel) -> bool {
|
||||
pub fn user_can_access_page(
|
||||
session: &Session,
|
||||
actix_admin: &ActixAdmin,
|
||||
view_model: &ActixAdminViewModel,
|
||||
) -> bool {
|
||||
let auth_is_enabled = &actix_admin.configuration.enable_auth;
|
||||
let user_is_logged_in = &actix_admin.configuration.user_is_logged_in;
|
||||
let user_can_access_view_model = &view_model.user_can_access;
|
||||
|
||||
match (auth_is_enabled, user_is_logged_in, user_can_access_view_model) {
|
||||
(true, Some(auth_func), Some(view_model_access_func)) => auth_func(session) && view_model_access_func(session),
|
||||
match (
|
||||
auth_is_enabled,
|
||||
user_is_logged_in,
|
||||
user_can_access_view_model,
|
||||
) {
|
||||
(true, Some(auth_func), Some(view_model_access_func)) => {
|
||||
auth_func(session) && view_model_access_func(session)
|
||||
}
|
||||
(true, Some(auth_func), _) => auth_func(session),
|
||||
(_, _, _) => !auth_is_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_unauthorized(ctx: &Context, actix_admin: &ActixAdmin) -> Result<HttpResponse, Error> {
|
||||
let body = actix_admin.tera
|
||||
let body = actix_admin
|
||||
.tera
|
||||
.render("unauthorized.html", &ctx)
|
||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||
Ok(HttpResponse::Unauthorized().content_type("text/html").body(body))
|
||||
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_session::{Session};
|
||||
use tera::{Context};
|
||||
use tera::Context;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{ add_auth_context };
|
||||
use super::add_auth_context;
|
||||
|
||||
pub fn get_admin_ctx(session: Session, data: &web::Data<ActixAdmin>) -> Context {
|
||||
let actix_admin = data.get_ref();
|
||||
@ -27,10 +27,8 @@ pub async fn index(session: Session, data: web::Data<ActixAdmin>) -> Result<Http
|
||||
|
||||
add_auth_context(&session, actix_admin, &mut ctx);
|
||||
|
||||
let body = actix_admin.tera
|
||||
.render("index.html", &ctx)
|
||||
.map_err(|e| {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
let body = actix_admin.tera.render("index.html", &ctx).map_err(|e| {
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{}", e);
|
||||
error::ErrorInternalServerError("Template error")
|
||||
})?;
|
||||
@ -38,13 +36,16 @@ pub async fn index(session: Session, data: web::Data<ActixAdmin>) -> Result<Http
|
||||
}
|
||||
|
||||
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())
|
||||
.map_err(|e| {
|
||||
#[cfg(feature="enable-tracing")]
|
||||
#[cfg(feature = "enable-tracing")]
|
||||
tracing::error!("{}", e);
|
||||
error::ErrorInternalServerError("Template error")
|
||||
})?;
|
||||
Ok(HttpResponse::NotFound().content_type("text/html").body(body))
|
||||
Ok(HttpResponse::NotFound()
|
||||
.content_type("text/html")
|
||||
.body(body))
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use std::fmt;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use urlencoding::decode;
|
||||
use crate::prelude::*;
|
||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use tera::Context;
|
||||
use urlencoding::decode;
|
||||
|
||||
use super::{
|
||||
add_auth_context, render_unauthorized, user_can_access_page, Params, DEFAULT_ENTITIES_PER_PAGE,
|
||||
@ -50,7 +50,7 @@ pub async fn list<E: ActixAdminViewModelTrait>(
|
||||
session: Session,
|
||||
req: HttpRequest,
|
||||
data: web::Data<ActixAdmin>,
|
||||
db: web::Data<DatabaseConnection>
|
||||
db: web::Data<DatabaseConnection>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let actix_admin = &data.into_inner();
|
||||
let entity_name = E::get_entity_name();
|
||||
@ -88,15 +88,30 @@ pub async fn list<E: ActixAdminViewModelTrait>(
|
||||
.map(|f| {
|
||||
let mut kv = f.split("=");
|
||||
let af = ActixAdminViewModelFilter {
|
||||
name: kv.next().unwrap().strip_prefix("filter_").unwrap_or_default().to_string(),
|
||||
name: kv
|
||||
.next()
|
||||
.unwrap()
|
||||
.strip_prefix("filter_")
|
||||
.unwrap_or_default()
|
||||
.to_string(),
|
||||
value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
|
||||
values: None,
|
||||
filter_type: None
|
||||
filter_type: None,
|
||||
};
|
||||
af
|
||||
}).collect();
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result = E::list(&db, page, entities_per_page, actixadminfilters, &search, &sort_by, &sort_order).await;
|
||||
let result = E::list(
|
||||
&db,
|
||||
page,
|
||||
entities_per_page,
|
||||
actixadminfilters,
|
||||
&search,
|
||||
&sort_by,
|
||||
&sort_order,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(res) => {
|
||||
|
@ -2,34 +2,34 @@ mod create_or_edit_get;
|
||||
pub use create_or_edit_get::{create_get, edit_get};
|
||||
|
||||
mod create_or_edit_post;
|
||||
pub use create_or_edit_post::{ create_post, edit_post, create_or_edit_post };
|
||||
pub use create_or_edit_post::{create_or_edit_post, create_post, edit_post};
|
||||
|
||||
mod index;
|
||||
pub use index::{ index, not_found, get_admin_ctx };
|
||||
pub use index::{get_admin_ctx, index, not_found};
|
||||
|
||||
mod list;
|
||||
pub use list::{ list, SortOrder };
|
||||
pub use list::{list, SortOrder};
|
||||
|
||||
mod show;
|
||||
pub use show::show;
|
||||
|
||||
mod delete;
|
||||
pub use delete::{ delete, delete_many };
|
||||
pub use delete::{delete, delete_many};
|
||||
|
||||
mod helpers;
|
||||
pub use helpers::{ add_auth_context, user_can_access_page, render_unauthorized };
|
||||
pub use helpers::{add_auth_context, render_unauthorized, user_can_access_page};
|
||||
|
||||
mod file;
|
||||
pub use file::{download, delete_file};
|
||||
pub use file::{delete_file, download};
|
||||
|
||||
use serde_derive::{Deserialize};
|
||||
use serde_derive::Deserialize;
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Params {
|
||||
page: Option<u64>,
|
||||
entities_per_page: Option<u64>,
|
||||
search: Option<String>,
|
||||
sort_by: Option<String>,
|
||||
sort_order: Option<SortOrder>
|
||||
sort_order: Option<SortOrder>,
|
||||
}
|
||||
|
||||
const DEFAULT_ENTITIES_PER_PAGE: u64 = 10;
|
@ -1,17 +1,21 @@
|
||||
use actix_session::Session;
|
||||
use actix_web::HttpRequest;
|
||||
use actix_web::{error, web, Error, HttpResponse};
|
||||
use actix_session::{Session};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tera::{Context};
|
||||
use tera::Context;
|
||||
|
||||
use crate::ActixAdminNotification;
|
||||
use crate::prelude::*;
|
||||
use crate::ActixAdminNotification;
|
||||
|
||||
use super::{add_auth_context, render_unauthorized, user_can_access_page};
|
||||
use super::{Params, DEFAULT_ENTITIES_PER_PAGE};
|
||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||
|
||||
pub async fn show<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> {
|
||||
let actix_admin = &data.into_inner();
|
||||
|
||||
@ -28,7 +32,7 @@ pub async fn show<E: ActixAdminViewModelTrait>(
|
||||
match result {
|
||||
Ok(res) => {
|
||||
model = res;
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(e);
|
||||
model = ActixAdminModel::create_empty();
|
||||
@ -37,9 +41,10 @@ pub async fn show<E: ActixAdminViewModelTrait>(
|
||||
|
||||
let mut http_response_code = match errors.is_empty() {
|
||||
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))
|
||||
.collect();
|
||||
|
||||
@ -51,11 +56,17 @@ pub async fn show<E: ActixAdminViewModelTrait>(
|
||||
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
|
||||
let render_partial = req.headers().contains_key("HX-Target");
|
||||
let search = params.search.clone().unwrap_or(String::new());
|
||||
let sort_by = params.sort_by.clone().unwrap_or(view_model.primary_key.to_string());
|
||||
let sort_by = params
|
||||
.sort_by
|
||||
.clone()
|
||||
.unwrap_or(view_model.primary_key.to_string());
|
||||
let sort_order = params.sort_order.as_ref().unwrap_or(&SortOrder::Asc);
|
||||
|
||||
ctx.insert("model", &model);
|
||||
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));
|
||||
ctx.insert(
|
||||
"view_model",
|
||||
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
||||
);
|
||||
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||
ctx.insert("entity_names", &actix_admin.entity_names);
|
||||
ctx.insert("notifications", ¬ifications);
|
||||
@ -68,9 +79,7 @@ pub async fn show<E: ActixAdminViewModelTrait>(
|
||||
|
||||
add_auth_context(&session, actix_admin, &mut ctx);
|
||||
|
||||
let body = actix_admin.tera
|
||||
.render("show.html", &ctx)
|
||||
.map_err(|err| {
|
||||
let body = actix_admin.tera.render("show.html", &ctx).map_err(|err| {
|
||||
#[cfg(enable_tracing)]
|
||||
tracing::error!("{err}");
|
||||
error::ErrorInternalServerError(format!("{:?}", err))
|
||||
|
@ -12,21 +12,26 @@
|
||||
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="") != "" %}
|
||||
<div>
|
||||
<a hx-disable href="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}">{{ model.values |
|
||||
get(key=model_field.field_name, default="") }}</a>
|
||||
<a hx-disable href="{{ base_path }}/file/{{ model.primary_key }}/{{ model_field.field_name }}">
|
||||
{{ 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 }}"
|
||||
hx-confirm="Are you sure?"><i class="fa-solid fa-trash"></i></a>
|
||||
</div>
|
||||
{% else %}
|
||||
<input class="{{ model_field | get_html_input_class }}
|
||||
{% if model.errors | length > 0 or model.custom_errors | length > 0 %}
|
||||
{% if
|
||||
model.errors | get(key=model_field.field_name, default="" ) !=""
|
||||
or
|
||||
model.custom_errors | get(key=model_field.field_name, default="" ) !=""
|
||||
%}is-danger{% else %}is-success{% endif %}
|
||||
{% if model.errors | get(key=model_field.field_name, default="" ) !="" or model.custom_errors | get(key=model_field.field_name, default="" ) !="" %}
|
||||
is-danger
|
||||
{% else %}
|
||||
is-success
|
||||
{% endif %}
|
||||
" type="{{ model_field | get_html_input_type }}"
|
||||
value="{{ model.values | get(key=model_field.field_name, default="") }}" name="{{ model_field.field_name | trim }}"
|
||||
placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}">
|
||||
{% endif %}
|
||||
"
|
||||
type="{{ model_field | get_html_input_type }}"
|
||||
value="{{ model.values | get(key=model_field.field_name, default="") }}"
|
||||
name="{{ model_field.field_name | trim }}"
|
||||
placeholder="{{ model_field.field_name }}"
|
||||
aria-label="{{ model_field.field_name }}"
|
||||
>
|
||||
{% endif %}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::ActixAdminError;
|
||||
use crate::{model::ActixAdminModelFilterType, ActixAdminModel, SortOrder};
|
||||
use actix_session::Session;
|
||||
use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use crate::{ActixAdminModel, SortOrder, model::ActixAdminModelFilterType};
|
||||
use actix_session::{Session};
|
||||
use std::convert::From;
|
||||
use crate::ActixAdminError;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait ActixAdminViewModelTrait {
|
||||
@ -17,16 +17,30 @@ pub trait ActixAdminViewModelTrait {
|
||||
viewmodel_filter: Vec<ActixAdminViewModelFilter>,
|
||||
search: &str,
|
||||
sort_by: &str,
|
||||
sort_order: &SortOrder
|
||||
sort_order: &SortOrder,
|
||||
) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError>;
|
||||
|
||||
// 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 get_entity(db: &DatabaseConnection, id: i32) -> 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>;
|
||||
async fn get_entity(
|
||||
db: &DatabaseConnection,
|
||||
id: i32,
|
||||
) -> 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 get_entity_name() -> String;
|
||||
@ -40,10 +54,10 @@ pub trait ActixAdminViewModelTrait {
|
||||
pub struct ActixAdminViewModel {
|
||||
pub entity_name: String,
|
||||
pub primary_key: String,
|
||||
pub fields: &'static[ActixAdminViewModelField],
|
||||
pub fields: &'static [ActixAdminViewModelField],
|
||||
pub show_search: bool,
|
||||
pub user_can_access: Option<fn(&Session) -> bool>,
|
||||
pub default_show_aside: bool
|
||||
pub default_show_aside: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
@ -52,7 +66,7 @@ pub struct ActixAdminViewModelSerializable {
|
||||
pub primary_key: String,
|
||||
pub fields: &'static [ActixAdminViewModelField],
|
||||
pub show_search: bool,
|
||||
pub default_show_aside: bool
|
||||
pub default_show_aside: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
@ -60,7 +74,7 @@ pub struct ActixAdminViewModelFilter {
|
||||
pub name: String,
|
||||
pub value: Option<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
|
||||
@ -71,7 +85,7 @@ impl From<ActixAdminViewModel> for ActixAdminViewModelSerializable {
|
||||
primary_key: entity.primary_key,
|
||||
fields: entity.fields,
|
||||
show_search: entity.show_search,
|
||||
default_show_aside: entity.default_show_aside
|
||||
default_show_aside: entity.default_show_aside,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,7 +100,7 @@ pub enum ActixAdminViewModelFieldType {
|
||||
Time,
|
||||
DateTime,
|
||||
SelectList,
|
||||
FileUpload
|
||||
FileUpload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@ -99,11 +113,16 @@ pub struct ActixAdminViewModelField {
|
||||
pub list_sort_position: usize,
|
||||
pub list_hide_column: bool,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub list_regex_mask: Option<Regex>
|
||||
pub list_regex_mask: Option<Regex>,
|
||||
}
|
||||
|
||||
impl ActixAdminViewModelFieldType {
|
||||
pub fn get_field_type(type_path: &str, select_list: String, is_textarea: bool, is_file_upload: bool) -> ActixAdminViewModelFieldType {
|
||||
pub fn get_field_type(
|
||||
type_path: &str,
|
||||
select_list: String,
|
||||
is_textarea: bool,
|
||||
is_file_upload: bool,
|
||||
) -> ActixAdminViewModelFieldType {
|
||||
if !select_list.is_empty() {
|
||||
return ActixAdminViewModelFieldType::SelectList;
|
||||
}
|
||||
@ -123,7 +142,7 @@ impl ActixAdminViewModelFieldType {
|
||||
"DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime,
|
||||
"DateTime" => ActixAdminViewModelFieldType::DateTime,
|
||||
"Date" => ActixAdminViewModelFieldType::Date,
|
||||
_ => ActixAdminViewModelFieldType::Text
|
||||
_ => ActixAdminViewModelFieldType::Text,
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ use test_setup::prelude::*;
|
||||
#[cfg(test)]
|
||||
mod get_request_is_success {
|
||||
extern crate serde_derive;
|
||||
use super::create_app;
|
||||
use super::BodyTest;
|
||||
use actix_admin::prelude::*;
|
||||
use actix_web::body::to_bytes;
|
||||
use actix_web::test;
|
||||
@ -12,8 +14,6 @@ mod get_request_is_success {
|
||||
use sea_orm::EntityTrait;
|
||||
use sea_orm::PaginatorTrait;
|
||||
use sea_orm::QueryOrder;
|
||||
use super::create_app;
|
||||
use super::BodyTest;
|
||||
|
||||
#[actix_web::test]
|
||||
async fn get_admin_index() {
|
||||
@ -33,12 +33,17 @@ mod get_request_is_success {
|
||||
let db = super::setup_db(true).await;
|
||||
let page = 5;
|
||||
let page_size = 50; // Verify with default size in list.rs
|
||||
let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Post::get_entity_name(), page, page_size);
|
||||
let url = format!(
|
||||
"/admin/{}/list?page={}&entities_per_page={}",
|
||||
crate::Post::get_entity_name(),
|
||||
page,
|
||||
page_size
|
||||
);
|
||||
|
||||
let entities = crate::Post::find()
|
||||
.order_by_asc(crate::post::Column::Id)
|
||||
.paginate(&db, page_size)
|
||||
.fetch_page(page-1)
|
||||
.fetch_page(page - 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -49,9 +54,12 @@ mod get_request_is_success {
|
||||
#[actix_web::test]
|
||||
async fn get_post_list_search() {
|
||||
let db = super::setup_db(true).await;
|
||||
let url = format!("/admin/{}/list?search=Test%20155", crate::Post::get_entity_name());
|
||||
let url = format!(
|
||||
"/admin/{}/list?search=Test%20155",
|
||||
crate::Post::get_entity_name()
|
||||
);
|
||||
|
||||
test_response_contains(url.as_str(), &db, vec!("Test 155".to_string())).await
|
||||
test_response_contains(url.as_str(), &db, vec!["Test 155".to_string()]).await
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
@ -59,7 +67,12 @@ mod get_request_is_success {
|
||||
let db = super::setup_db(true).await;
|
||||
let search_string_encoded = "Test%2015";
|
||||
let entities_per_page = 11;
|
||||
let url = format!("/admin/{}/list?search={}&entities_per_page={}", crate::Comment::get_entity_name(), search_string_encoded, entities_per_page);
|
||||
let url = format!(
|
||||
"/admin/{}/list?search={}&entities_per_page={}",
|
||||
crate::Comment::get_entity_name(),
|
||||
search_string_encoded,
|
||||
entities_per_page
|
||||
);
|
||||
|
||||
let mut elements_to_verify = Vec::new();
|
||||
elements_to_verify.push("Test 15".to_string());
|
||||
@ -76,7 +89,12 @@ mod get_request_is_success {
|
||||
let page = 17;
|
||||
|
||||
let page_size = 20; // Verify with default size in list.rs
|
||||
let url = format!("/admin/{}/list?page={}&entities_per_page={}", crate::Comment::get_entity_name(), page, page_size);
|
||||
let url = format!(
|
||||
"/admin/{}/list?page={}&entities_per_page={}",
|
||||
crate::Comment::get_entity_name(),
|
||||
page,
|
||||
page_size
|
||||
);
|
||||
|
||||
let query = if page_size == 5 {
|
||||
crate::Comment::find().order_by_asc(crate::comment::Column::Id)
|
||||
@ -86,7 +104,7 @@ mod get_request_is_success {
|
||||
|
||||
let entities = query
|
||||
.paginate(&db, page_size)
|
||||
.fetch_page(page-1)
|
||||
.fetch_page(page - 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -119,11 +137,7 @@ mod get_request_is_success {
|
||||
async fn get_comment_show() {
|
||||
let db = super::setup_db(true).await;
|
||||
|
||||
let url = format!(
|
||||
"/admin/{}/show/{}",
|
||||
crate::Comment::get_entity_name(),
|
||||
1
|
||||
);
|
||||
let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1);
|
||||
test_get_is_success(url.as_str(), &db).await
|
||||
}
|
||||
|
||||
@ -131,39 +145,38 @@ mod get_request_is_success {
|
||||
async fn get_post_show() {
|
||||
let db = super::setup_db(true).await;
|
||||
|
||||
let url = format!(
|
||||
"/admin/{}/show/{}",
|
||||
crate::Comment::get_entity_name(),
|
||||
1
|
||||
);
|
||||
let url = format!("/admin/{}/show/{}", crate::Comment::get_entity_name(), 1);
|
||||
test_get_is_success(url.as_str(), &db).await
|
||||
}
|
||||
|
||||
async fn test_response_contains(url: &str, db: &DatabaseConnection, elements_to_verify: Vec<String>) {
|
||||
async fn test_response_contains(
|
||||
url: &str,
|
||||
db: &DatabaseConnection,
|
||||
elements_to_verify: Vec<String>,
|
||||
) {
|
||||
let app = create_app!(db);
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri(url)
|
||||
.to_request();
|
||||
let req = test::TestRequest::get().uri(url).to_request();
|
||||
|
||||
let resp = test::call_service(&app, req).await;
|
||||
let body = to_bytes(resp.into_body()).await.unwrap();
|
||||
let body = body.as_str();
|
||||
for element in elements_to_verify {
|
||||
assert!(body.contains(&element), "Body did not contain element {}: \n{}", element, body);
|
||||
assert!(
|
||||
body.contains(&element),
|
||||
"Body did not contain element {}: \n{}",
|
||||
element,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn test_get_is_success(url: &str, db: &DatabaseConnection) {
|
||||
let app = create_app!(db);
|
||||
|
||||
let req = test::TestRequest::get()
|
||||
.uri(url)
|
||||
.to_request();
|
||||
let req = test::TestRequest::get().uri(url).to_request();
|
||||
let resp = test::call_service(&app, req).await;
|
||||
|
||||
assert!(resp.status().is_success());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,16 +4,12 @@ use test_setup::prelude::*;
|
||||
#[cfg(test)]
|
||||
mod post_create_and_edit_is_success {
|
||||
use actix_admin::prelude::*;
|
||||
use actix_web::{
|
||||
test,
|
||||
App,
|
||||
http::header::ContentType
|
||||
};
|
||||
use chrono::{ NaiveDateTime, NaiveDate };
|
||||
use serde::{Serialize};
|
||||
use sea_orm::{ PaginatorTrait, EntityTrait, prelude::Decimal};
|
||||
use actix_web::{http::header::ContentType, test, App};
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use sea_orm::{prelude::Decimal, EntityTrait, PaginatorTrait};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{create_app};
|
||||
use crate::create_app;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct CommentModel {
|
||||
@ -23,7 +19,7 @@ mod post_create_and_edit_is_success {
|
||||
user: &'static str,
|
||||
is_visible: &'static str,
|
||||
post_id: Option<&'static str>,
|
||||
my_decimal: &'static str
|
||||
my_decimal: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
@ -48,7 +44,7 @@ mod post_create_and_edit_is_success {
|
||||
user: "test",
|
||||
is_visible: "true",
|
||||
post_id: None,
|
||||
my_decimal: "113.141" // must be larger than 100
|
||||
my_decimal: "113.141", // must be larger than 100
|
||||
};
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
@ -69,12 +65,15 @@ mod post_create_and_edit_is_success {
|
||||
assert_eq!(entities.len(), 1, "After post, db does not contain 1 model");
|
||||
let entity = entities.first().unwrap();
|
||||
assert_eq!(entity.id, 1);
|
||||
assert_eq!(entity.comment,"test");
|
||||
assert_eq!(entity.comment, "test");
|
||||
assert_eq!(entity.user, "test");
|
||||
assert!(entity.is_visible);
|
||||
assert!(entity.post_id.is_none());
|
||||
assert_eq!(entity.my_decimal, Decimal::new(113141, 3));
|
||||
assert_eq!(entity.insert_date, NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap());
|
||||
assert_eq!(
|
||||
entity.insert_date,
|
||||
NaiveDateTime::parse_from_str("1977-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()
|
||||
);
|
||||
|
||||
// update entity
|
||||
model.my_decimal = "213.141";
|
||||
@ -98,7 +97,11 @@ mod post_create_and_edit_is_success {
|
||||
.await
|
||||
.expect("could not retrieve entities");
|
||||
|
||||
assert_eq!(entities.len(), 1, "After edit post, db does not contain 1 model");
|
||||
assert_eq!(
|
||||
entities.len(),
|
||||
1,
|
||||
"After edit post, db does not contain 1 model"
|
||||
);
|
||||
let entity = entities.first().unwrap();
|
||||
assert_eq!(entity.id, 1);
|
||||
assert_eq!(entity.comment, "updated");
|
||||
@ -106,7 +109,10 @@ mod post_create_and_edit_is_success {
|
||||
assert!(!entity.is_visible);
|
||||
assert!(entity.post_id.is_none());
|
||||
assert_eq!(entity.my_decimal, Decimal::new(213141, 3));
|
||||
assert_eq!(entity.insert_date, NaiveDateTime::parse_from_str("1987-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap());
|
||||
assert_eq!(
|
||||
entity.insert_date,
|
||||
NaiveDateTime::parse_from_str("1987-04-01T14:00", "%Y-%m-%dT%H:%M").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
@ -119,7 +125,7 @@ mod post_create_and_edit_is_success {
|
||||
insert_date: "1977-04-01",
|
||||
title: "test",
|
||||
text: "test",
|
||||
tea_mandatory: "EverydayTea"
|
||||
tea_mandatory: "EverydayTea",
|
||||
};
|
||||
|
||||
let req = test::TestRequest::post()
|
||||
@ -140,10 +146,16 @@ mod post_create_and_edit_is_success {
|
||||
assert_eq!(entities.len(), 1, "After post, db does not contain 1 model");
|
||||
let entity = entities.first().unwrap();
|
||||
assert_eq!(entity.id, 1);
|
||||
assert_eq!(entity.tea_mandatory, super::test_setup::post::Tea::EverydayTea);
|
||||
assert_eq!(
|
||||
entity.tea_mandatory,
|
||||
super::test_setup::post::Tea::EverydayTea
|
||||
);
|
||||
assert_eq!(entity.title, model.title);
|
||||
assert_eq!(entity.text, model.text);
|
||||
assert_eq!(entity.insert_date, NaiveDate::parse_from_str("1977-04-01", "%Y-%m-%d").unwrap());
|
||||
assert_eq!(
|
||||
entity.insert_date,
|
||||
NaiveDate::parse_from_str("1977-04-01", "%Y-%m-%d").unwrap()
|
||||
);
|
||||
|
||||
// update entity
|
||||
model.tea_mandatory = "BreakfastTea";
|
||||
@ -166,11 +178,18 @@ mod post_create_and_edit_is_success {
|
||||
.await
|
||||
.expect("could not retrieve entities");
|
||||
|
||||
assert_eq!(entities.len(), 1, "After edit post, db does not contain 1 model");
|
||||
assert_eq!(
|
||||
entities.len(),
|
||||
1,
|
||||
"After edit post, db does not contain 1 model"
|
||||
);
|
||||
let entity = entities.first().unwrap();
|
||||
assert_eq!(entity.id, 1);
|
||||
assert_eq!(entity.text, "updated");
|
||||
assert_eq!(entity.title, "updated");
|
||||
assert_eq!(entity.insert_date, NaiveDate::parse_from_str("1987-04-01", "%Y-%m-%d").unwrap());
|
||||
assert_eq!(
|
||||
entity.insert_date,
|
||||
NaiveDate::parse_from_str("1987-04-01", "%Y-%m-%d").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
use super::Post;
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use actix_admin::prelude::*;
|
||||
use super::Post;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize,
|
||||
DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveEntityModel,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
#[sea_orm(table_name = "comment")]
|
||||
pub struct Model {
|
||||
@ -20,9 +28,9 @@ pub struct Model {
|
||||
#[sea_orm(column_type = "DateTime")]
|
||||
pub insert_date: DateTime,
|
||||
pub is_visible: bool,
|
||||
#[actix_admin(select_list="Post")]
|
||||
#[actix_admin(select_list = "Post")]
|
||||
pub post_id: Option<i32>,
|
||||
pub my_decimal: Decimal
|
||||
pub my_decimal: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
@ -47,7 +55,10 @@ impl ActixAdminModelValidationTrait<ActiveModel> for Entity {
|
||||
fn validate(model: &ActiveModel) -> HashMap<String, String> {
|
||||
let mut errors = HashMap::new();
|
||||
if model.my_decimal.clone().unwrap() < Decimal::from(100 as i16) {
|
||||
errors.insert("my_decimal".to_string(), "Must be larger than 100".to_string());
|
||||
errors.insert(
|
||||
"my_decimal".to_string(),
|
||||
"Must be larger than 100".to_string(),
|
||||
);
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use actix_admin::prelude::*;
|
||||
use actix_session::Session;
|
||||
use actix_web::HttpRequest;
|
||||
use actix_web::web;
|
||||
use actix_web::Error;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::web::Bytes;
|
||||
use actix_web::Error;
|
||||
use actix_web::HttpRequest;
|
||||
use actix_web::HttpResponse;
|
||||
use chrono::Local;
|
||||
use sea_orm::prelude::Decimal;
|
||||
use sea_orm::{ConnectOptions, DatabaseConnection, EntityTrait, Set};
|
||||
@ -78,7 +78,7 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder {
|
||||
login_link: None,
|
||||
logout_link: None,
|
||||
file_upload_directory: "./file_uploads",
|
||||
navbar_title: "test"
|
||||
navbar_title: "test",
|
||||
};
|
||||
|
||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||
|
@ -1,22 +1,18 @@
|
||||
// setup
|
||||
use sea_orm::sea_query::{ForeignKeyCreateStatement, ColumnDef, TableCreateStatement};
|
||||
use sea_orm::sea_query::{ColumnDef, ForeignKeyCreateStatement, TableCreateStatement};
|
||||
use sea_orm::{error::*, sea_query, ConnectionTrait, DbConn, ExecResult};
|
||||
pub mod comment;
|
||||
pub mod post;
|
||||
pub mod helper;
|
||||
pub mod post;
|
||||
pub use comment::Entity as Comment;
|
||||
pub use post::Entity as Post;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::test_setup::helper::{
|
||||
create_actix_admin_builder,
|
||||
setup_db,
|
||||
BodyTest
|
||||
};
|
||||
pub use super::comment;
|
||||
pub use super::post;
|
||||
pub use super::Comment;
|
||||
pub use super::Post;
|
||||
pub use crate::test_setup::helper::{create_actix_admin_builder, setup_db, BodyTest};
|
||||
}
|
||||
|
||||
// setup
|
||||
@ -38,7 +34,11 @@ pub async fn create_tables(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
)
|
||||
.col(ColumnDef::new(post::Column::Title).string().not_null())
|
||||
.col(ColumnDef::new(post::Column::Text).string().not_null())
|
||||
.col(ColumnDef::new(post::Column::TeaMandatory).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(post::Column::TeaMandatory)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(post::Column::TeaOptional).string())
|
||||
.col(ColumnDef::new(post::Column::InsertDate).date())
|
||||
.to_owned();
|
||||
@ -57,9 +57,21 @@ pub async fn create_tables(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
)
|
||||
.col(ColumnDef::new(comment::Column::Comment).string().not_null())
|
||||
.col(ColumnDef::new(comment::Column::User).string().not_null())
|
||||
.col(ColumnDef::new(comment::Column::InsertDate).date_time().not_null())
|
||||
.col(ColumnDef::new(comment::Column::IsVisible).boolean().not_null())
|
||||
.col(ColumnDef::new(comment::Column::MyDecimal).decimal().not_null())
|
||||
.col(
|
||||
ColumnDef::new(comment::Column::InsertDate)
|
||||
.date_time()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(comment::Column::IsVisible)
|
||||
.boolean()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(comment::Column::MyDecimal)
|
||||
.decimal()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(comment::Column::PostId).integer())
|
||||
.foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
|
@ -1,11 +1,22 @@
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use actix_admin::prelude::*;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminViewModel, DeriveActixAdminModel, DeriveActixAdminModelSelectList)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveEntityModel,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminViewModel,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminModelSelectList,
|
||||
)]
|
||||
#[sea_orm(table_name = "post")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
@ -17,9 +28,9 @@ pub struct Model {
|
||||
#[sea_orm(column_type = "Text")]
|
||||
#[actix_admin(searchable, textarea)]
|
||||
pub text: String,
|
||||
#[actix_admin(select_list="Tea")]
|
||||
#[actix_admin(select_list = "Tea")]
|
||||
pub tea_mandatory: Tea,
|
||||
#[actix_admin(select_list="Tea")]
|
||||
#[actix_admin(select_list = "Tea")]
|
||||
pub tea_optional: Option<Tea>,
|
||||
pub insert_date: Date,
|
||||
}
|
||||
@ -46,7 +57,20 @@ impl Related<super::comment::Entity> for Entity {
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
<<<<<<< HEAD
|
||||
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveDisplay, DeriveActiveEnum, Deserialize, Serialize, DeriveActixAdminEnumSelectList)]
|
||||
=======
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
EnumIter,
|
||||
DeriveActiveEnum,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
DeriveActixAdminEnumSelectList,
|
||||
)]
|
||||
>>>>>>> 7db2971 (Trim input name)
|
||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
|
||||
pub enum Tea {
|
||||
#[sea_orm(string_value = "EverydayTea")]
|
||||
|
Loading…
Reference in New Issue
Block a user