implement file_upload field
This commit is contained in:
parent
ce964220b7
commit
a600ff5c9c
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,3 +27,6 @@ _site/
|
|||||||
# Ignore folders generated by Bundler
|
# Ignore folders generated by Bundler
|
||||||
.bundle/
|
.bundle/
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
# Ignore File Uploads
|
||||||
|
file_uploads/
|
16
Cargo.toml
16
Cargo.toml
@ -6,18 +6,21 @@ version = "0.2.0"
|
|||||||
repository = "https://github.com/mgugger/actix-admin"
|
repository = "https://github.com/mgugger/actix-admin"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
exclude = [
|
exclude = [
|
||||||
"example/*",
|
"examples/*",
|
||||||
"actix_admin_macros/*",
|
"actix_admin_macros/*",
|
||||||
"tests/*",
|
"tests/*",
|
||||||
"README.md",
|
"README.md"
|
||||||
"static/*",
|
|
||||||
"azure_auth/*",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "actix_admin"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "^4.0.1"
|
actix-web = "^4.0.1"
|
||||||
actix-session = { version = "^0.7.1", features = [] }
|
actix-session = { version = "^0.7.1", features = [] }
|
||||||
actix-multipart = "^0.4.0"
|
actix-multipart = "^0.4.0"
|
||||||
|
actix-files = "^0.6.2"
|
||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
chrono = "0.4.20"
|
chrono = "0.4.20"
|
||||||
tera = "^1.16.0"
|
tera = "^1.16.0"
|
||||||
@ -32,3 +35,8 @@ derive_more = "0.99.17"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sea-orm = { version = "^0.9.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
sea-orm = { version = "^0.9.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||||
|
actix-rt = "2.7.0"
|
||||||
|
azure_auth = { path = "./examples/azure_auth/azure_auth" }
|
||||||
|
oauth2 = "4.1"
|
||||||
|
dotenv = "0.15"
|
||||||
|
actix-session = { version = "0.7.1", features = ["cookie-session"] }
|
@ -14,7 +14,9 @@ pub mod derive_attr {
|
|||||||
pub html_input_type: Option<syn::LitStr>,
|
pub html_input_type: Option<syn::LitStr>,
|
||||||
pub select_list: Option<syn::LitStr>,
|
pub select_list: Option<syn::LitStr>,
|
||||||
pub searchable: Option<()>,
|
pub searchable: Option<()>,
|
||||||
pub textarea: Option<()>
|
pub textarea: Option<()>,
|
||||||
|
pub file_upload: Option<()>,
|
||||||
|
pub not_empty: Option<()>
|
||||||
//pub inner_type: Option<syn::Type>,
|
//pub inner_type: Option<syn::Type>,
|
||||||
|
|
||||||
// Anything that implements `syn::parse::Parse` is supported.
|
// Anything that implements `syn::parse::Parse` is supported.
|
||||||
|
@ -154,6 +154,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
let fields_searchable = get_actix_admin_fields_searchable(&fields);
|
let fields_searchable = get_actix_admin_fields_searchable(&fields);
|
||||||
let fields_type_path = get_actix_admin_fields_type_path_string(&fields);
|
let fields_type_path = get_actix_admin_fields_type_path_string(&fields);
|
||||||
let fields_textarea = get_actix_admin_fields_textarea(&fields);
|
let fields_textarea = get_actix_admin_fields_textarea(&fields);
|
||||||
|
let fields_file_upload = get_actix_admin_fields_file_upload(&fields);
|
||||||
|
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
actix_admin::prelude::lazy_static! {
|
actix_admin::prelude::lazy_static! {
|
||||||
@ -187,7 +188,11 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
#(#fields_textarea),*
|
#(#fields_textarea),*
|
||||||
];
|
];
|
||||||
|
|
||||||
for (field_name, html_input_type, select_list, is_option_list, fields_type_path, is_textarea) in actix_admin::prelude::izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths, fields_textareas) {
|
let fields_fileupload = [
|
||||||
|
#(#fields_file_upload),*
|
||||||
|
];
|
||||||
|
|
||||||
|
for (field_name, html_input_type, select_list, is_option_list, fields_type_path, is_textarea, is_file_upload) in actix_admin::prelude::izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths, fields_textareas, fields_fileupload) {
|
||||||
|
|
||||||
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
||||||
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
||||||
@ -198,7 +203,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
html_input_type: html_input_type,
|
html_input_type: html_input_type,
|
||||||
select_list: select_list.clone(),
|
select_list: select_list.clone(),
|
||||||
is_option: is_option_list,
|
is_option: is_option_list,
|
||||||
field_type: ActixAdminViewModelFieldType::get_field_type(fields_type_path, select_list, is_textarea)
|
field_type: ActixAdminViewModelFieldType::get_field_type(fields_type_path, select_list, is_textarea, is_file_upload)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
vec
|
vec
|
||||||
|
@ -7,13 +7,14 @@ pub struct ModelField {
|
|||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
pub ident: proc_macro2::Ident,
|
pub ident: proc_macro2::Ident,
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
// struct field is option<>
|
|
||||||
pub inner_type: Option<Type>,
|
pub inner_type: Option<Type>,
|
||||||
pub primary_key: bool,
|
pub primary_key: bool,
|
||||||
pub html_input_type: String,
|
pub html_input_type: String,
|
||||||
pub select_list: String,
|
pub select_list: String,
|
||||||
pub searchable: bool,
|
pub searchable: bool,
|
||||||
pub textarea: bool
|
pub textarea: bool,
|
||||||
|
pub file_upload: bool,
|
||||||
|
pub not_empty: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelField {
|
impl ModelField {
|
||||||
|
@ -41,6 +41,12 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
let is_textarea = actix_admin_attr
|
let is_textarea = actix_admin_attr
|
||||||
.clone()
|
.clone()
|
||||||
.map_or(false, |attr| attr.textarea.is_some());
|
.map_or(false, |attr| attr.textarea.is_some());
|
||||||
|
let is_file_upload = actix_admin_attr
|
||||||
|
.clone()
|
||||||
|
.map_or(false, |attr| attr.file_upload.is_some());
|
||||||
|
let is_not_empty = actix_admin_attr
|
||||||
|
.clone()
|
||||||
|
.map_or(false, |attr| attr.not_empty.is_some());
|
||||||
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
||||||
attr.select_list.map_or("".to_string(), |attr_field| {
|
attr.select_list.map_or("".to_string(), |attr_field| {
|
||||||
(LitStr::from(attr_field)).value()
|
(LitStr::from(attr_field)).value()
|
||||||
@ -62,7 +68,9 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
html_input_type: html_input_type,
|
html_input_type: html_input_type,
|
||||||
select_list: select_list,
|
select_list: select_list,
|
||||||
searchable: is_searchable,
|
searchable: is_searchable,
|
||||||
textarea: is_textarea
|
textarea: is_textarea,
|
||||||
|
file_upload: is_file_upload,
|
||||||
|
not_empty: is_not_empty
|
||||||
};
|
};
|
||||||
Some(model_field)
|
Some(model_field)
|
||||||
} else {
|
} else {
|
||||||
@ -186,6 +194,20 @@ pub fn get_actix_admin_fields_textarea(fields: &Vec<ModelField>) -> Vec<TokenStr
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_actix_admin_fields_file_upload(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.filter(|model_field| !model_field.primary_key)
|
||||||
|
.map(|model_field| {
|
||||||
|
let is_fileupload = model_field.file_upload;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#is_fileupload
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_actix_admin_fields_searchable(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
pub fn get_actix_admin_fields_searchable(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
@ -275,33 +297,34 @@ pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStrea
|
|||||||
let type_path = model_field.get_type_path_string();
|
let type_path = model_field.get_type_path_string();
|
||||||
|
|
||||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
|
let is_allowed_to_be_empty = !model_field.not_empty;
|
||||||
|
|
||||||
let res = match (model_field.is_option(), type_path.as_str()) {
|
let res = match (model_field.is_option(), type_path.as_str()) {
|
||||||
(_, "DateTime") => {
|
(_, "DateTime") => {
|
||||||
quote! {
|
quote! {
|
||||||
model.get_datetime(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
model.get_datetime(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(_, "Date") => {
|
(_, "Date") => {
|
||||||
quote! {
|
quote! {
|
||||||
model.get_date(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
model.get_date(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(_, "bool") => {
|
(_, "bool") => {
|
||||||
quote! {
|
quote! {
|
||||||
model.get_bool(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
model.get_bool(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// generic
|
// generic
|
||||||
(true, _) => {
|
(true, _) => {
|
||||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
model.get_value::<#inner_ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false, _) => {
|
(false, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
model.get_value::<#ty>(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
model.get_value::<#ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -323,51 +346,52 @@ pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream>
|
|||||||
let type_path = model_field.get_type_path_string();
|
let type_path = model_field.get_type_path_string();
|
||||||
|
|
||||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
|
let is_allowed_to_be_empty = !model_field.not_empty;
|
||||||
|
|
||||||
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
||||||
// is DateTime
|
// is DateTime
|
||||||
(true , _, "DateTime") => {
|
(true , _, "DateTime") => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap())
|
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false , _, "DateTime") => {
|
(false , _, "DateTime") => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap().unwrap())
|
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(true , _, "Date") => {
|
(true , _, "Date") => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_date(#ident_name, #is_option_or_string).unwrap())
|
#ident: Set(model.get_date(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false , _, "Date") => {
|
(false , _, "Date") => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_date(#ident_name, #is_option_or_string).unwrap().unwrap())
|
#ident: Set(model.get_date(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(_ , _, "bool") => {
|
(_ , _, "bool") => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_bool(#ident_name, #is_option_or_string).unwrap().unwrap())
|
#ident: Set(model.get_bool(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Default fields
|
// Default fields
|
||||||
(true, _, _) => {
|
(true, _, _) => {
|
||||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
#ident: Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// is string which can be empty
|
// is string which can be empty
|
||||||
(false, true, _) => {
|
(false, true, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap_or(String::new()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// no string
|
// no string
|
||||||
(false, false, _) => {
|
(false, false, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -389,47 +413,48 @@ pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
|||||||
let type_path = model_field.get_type_path_string();
|
let type_path = model_field.get_type_path_string();
|
||||||
|
|
||||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||||
|
let is_allowed_to_be_empty = !model_field.not_empty;
|
||||||
|
|
||||||
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
||||||
(_, _, "bool") => {
|
(_, _, "bool") => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_bool(#ident_name, #is_option_or_string).unwrap().unwrap())
|
entity.#ident = Set(model.get_bool(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(true , _, "DateTime") => {
|
(true , _, "DateTime") => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap())
|
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false , _, "DateTime") => {
|
(false , _, "DateTime") => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap().unwrap())
|
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(true , _, "Date") => {
|
(true , _, "Date") => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_date(#ident_name, #is_option_or_string).unwrap())
|
entity.#ident = Set(model.get_date(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false , _, "Date") => {
|
(false , _, "Date") => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_date(#ident_name, #is_option_or_string).unwrap().unwrap())
|
entity.#ident = Set(model.get_date(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(true, _, _) => {
|
(true, _, _) => {
|
||||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false, true, _) => {
|
(false, true, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap_or(String::new()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(false, false, _) => {
|
(false, false, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string, #is_allowed_to_be_empty).unwrap().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# Create a .env file in the same folder and add variables below with correct oauth credentials
|
||||||
OAUTH2_CLIENT_SECRET= "TODO"
|
OAUTH2_CLIENT_SECRET= "TODO"
|
||||||
OAUTH2_CLIENT_ID= "TODO"
|
OAUTH2_CLIENT_ID= "TODO"
|
||||||
OAUTH2_SERVER= "login.microsoftonline.com/a5f5xxxx-xxxx-414a-8463-xxxxxxxxxxxxx(tenantId)"
|
OAUTH2_SERVER= "login.microsoftonline.com/a5f5xxxx-xxxx-414a-8463-xxxxxxxxxxxxx(tenantId)"
|
@ -109,7 +109,8 @@ fn create_actix_admin_builder() -> ActixAdminBuilder {
|
|||||||
user_info.is_some()
|
user_info.is_some()
|
||||||
}),
|
}),
|
||||||
login_link: Some("/azure-auth/login".to_string()),
|
login_link: Some("/azure-auth/login".to_string()),
|
||||||
logout_link: Some("/azure-auth/logout".to_string())
|
logout_link: Some("/azure-auth/logout".to_string()),
|
||||||
|
file_upload_directory: "./file_uploads"
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||||
@ -135,7 +136,8 @@ fn create_actix_admin_builder() -> ActixAdminBuilder {
|
|||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv::dotenv().ok();
|
dotenv::from_filename("./examples/azure_auth/.env.example").ok();
|
||||||
|
dotenv::from_filename("./examples/azure_auth/.env").ok();
|
||||||
|
|
||||||
let oauth2_client_id = env::var("OAUTH2_CLIENT_ID").expect("Missing the OAUTH2_CLIENT_ID environment variable.");
|
let oauth2_client_id = env::var("OAUTH2_CLIENT_ID").expect("Missing the OAUTH2_CLIENT_ID environment variable.");
|
||||||
let oauth2_client_secret = env::var("OAUTH2_CLIENT_SECRET").expect("Missing the OAUTH2_CLIENT_SECRET environment variable.");
|
let oauth2_client_secret = env::var("OAUTH2_CLIENT_SECRET").expect("Missing the OAUTH2_CLIENT_SECRET environment variable.");
|
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "actix-admin-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
actix-web = "4.0.1"
|
|
||||||
actix-rt = "2.7.0"
|
|
||||||
serde = "1.0.136"
|
|
||||||
serde_derive = "1.0.136"
|
|
||||||
sea-orm = { version = "^0.9.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
|
||||||
actix-admin = { path = "../../" }
|
|
@ -28,6 +28,7 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
|||||||
.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())
|
||||||
|
.col(ColumnDef::new(post::Column::Attachment).string())
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let _result = create_table(db, &stmt).await;
|
let _result = create_table(db, &stmt).await;
|
@ -12,7 +12,7 @@ pub struct Model {
|
|||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
#[actix_admin(primary_key)]
|
#[actix_admin(primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
#[actix_admin(searchable)]
|
#[actix_admin(searchable, not_empty)]
|
||||||
pub title: String,
|
pub title: String,
|
||||||
#[sea_orm(column_type = "Text")]
|
#[sea_orm(column_type = "Text")]
|
||||||
#[actix_admin(searchable, textarea)]
|
#[actix_admin(searchable, textarea)]
|
||||||
@ -22,6 +22,8 @@ pub struct Model {
|
|||||||
#[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,
|
||||||
|
#[actix_admin(file_upload)]
|
||||||
|
pub attachment: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Model {
|
impl Display for Model {
|
@ -31,7 +31,8 @@ fn create_actix_admin_builder() -> ActixAdminBuilder {
|
|||||||
enable_auth: false,
|
enable_auth: false,
|
||||||
user_is_logged_in: None,
|
user_is_logged_in: None,
|
||||||
login_link: None,
|
login_link: None,
|
||||||
logout_link: None
|
logout_link: None,
|
||||||
|
file_upload_directory: "./file_uploads"
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Actix Admin Example</title>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/admin/">Go to Actix-Admin</a></li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "actix-admin-example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
actix-web = "4.0.1"
|
|
||||||
actix-rt = "2.7.0"
|
|
||||||
actix-session = { version = "0.7.1", features = ["cookie-session"] }
|
|
||||||
tera = "1.15.0"
|
|
||||||
oauth2 = "4.1"
|
|
||||||
dotenv = "0.15"
|
|
||||||
serde = "1.0.136"
|
|
||||||
serde_derive = "1.0.136"
|
|
||||||
sea-orm = { version = "^0.9.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
|
||||||
actix-admin = { path = "../../" }
|
|
||||||
azure_auth = { path = "./azure_auth" }
|
|
@ -1 +0,0 @@
|
|||||||
Rename .env.example to .env and update the oauth client credentials
|
|
@ -1,9 +1,10 @@
|
|||||||
use crate::{prelude::*, ActixAdminMenuElement};
|
use crate::{prelude::*, ActixAdminMenuElement, routes::delete_static_content};
|
||||||
use actix_web::{web, Route};
|
use actix_web::{web, Route};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
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,
|
create_get, create_post, delete, delete_many, edit_get, edit_post, index, list, not_found, show, download
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a builder entity which helps generating the ActixAdmin configuration
|
/// Represents a builder entity which helps generating the ActixAdmin configuration
|
||||||
@ -102,9 +103,13 @@ impl ActixAdminBuilderTrait for ActixAdminBuilder {
|
|||||||
.route("/delete", web::delete().to(delete_many::<T, E>))
|
.route("/delete", web::delete().to(delete_many::<T, E>))
|
||||||
.route("/delete/{id}", web::delete().to(delete::<T, E>))
|
.route("/delete/{id}", web::delete().to(delete::<T, E>))
|
||||||
.route("/show/{id}", web::get().to(show::<T, E>))
|
.route("/show/{id}", web::get().to(show::<T, E>))
|
||||||
|
.route("/static_content/{id}/{column_name}", web::get().to(download::<T, E>))
|
||||||
|
.route("/static_content/{id}/{column_name}", web::delete().to(delete_static_content::<T, E>))
|
||||||
.default_service(web::to(not_found)),
|
.default_service(web::to(not_found)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 {
|
||||||
name: E::get_entity_name(),
|
name: E::get_entity_name(),
|
||||||
|
@ -96,6 +96,7 @@ pub fn get_html_input_type<S: BuildHasher>(value: &tera::Value, _: &HashMap<Stri
|
|||||||
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
||||||
ActixAdminViewModelFieldType::Date => "date",
|
ActixAdminViewModelFieldType::Date => "date",
|
||||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||||
|
ActixAdminViewModelFieldType::FileUpload => "file",
|
||||||
_ => "text"
|
_ => "text"
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,7 +121,8 @@ pub struct ActixAdminConfiguration {
|
|||||||
pub enable_auth: bool,
|
pub enable_auth: bool,
|
||||||
pub user_is_logged_in: Option<for<'a> fn(&'a Session) -> bool>,
|
pub user_is_logged_in: Option<for<'a> fn(&'a Session) -> bool>,
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
125
src/model.rs
125
src/model.rs
@ -1,11 +1,15 @@
|
|||||||
use crate::{ ActixAdminViewModelField, ActixAdminError};
|
use crate::{ActixAdminError, ActixAdminViewModelField};
|
||||||
|
use actix_multipart::{Multipart, MultipartError};
|
||||||
|
use actix_web::web::Bytes;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use chrono::{NaiveDate, NaiveDateTime};
|
||||||
|
use futures_util::stream::StreamExt as _;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use actix_multipart:: {Multipart, MultipartError} ;
|
use std::fs::File;
|
||||||
use futures_util::stream::StreamExt as _;
|
use std::io::Write;
|
||||||
use chrono::{NaiveDateTime, NaiveDate};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ActixAdminModelTrait {
|
pub trait ActixAdminModelTrait {
|
||||||
@ -13,9 +17,9 @@ pub trait ActixAdminModelTrait {
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
page: usize,
|
page: usize,
|
||||||
posts_per_page: usize,
|
posts_per_page: usize,
|
||||||
search: &String
|
search: &String,
|
||||||
) -> Result<(usize, Vec<ActixAdminModel>), ActixAdminError>;
|
) -> Result<(usize, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,10 +34,9 @@ pub struct ActixAdminModel {
|
|||||||
pub primary_key: Option<String>,
|
pub primary_key: Option<String>,
|
||||||
pub values: HashMap<String, String>,
|
pub values: HashMap<String, String>,
|
||||||
pub errors: HashMap<String, String>,
|
pub errors: HashMap<String, String>,
|
||||||
pub custom_errors: HashMap<String, String>
|
pub custom_errors: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl ActixAdminModel {
|
impl ActixAdminModel {
|
||||||
pub fn create_empty() -> ActixAdminModel {
|
pub fn create_empty() -> ActixAdminModel {
|
||||||
ActixAdminModel {
|
ActixAdminModel {
|
||||||
@ -44,21 +47,47 @@ impl ActixAdminModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_from_payload(mut payload: Multipart) -> Result<ActixAdminModel, MultipartError> {
|
pub async fn create_from_payload(
|
||||||
|
mut payload: Multipart, file_upload_folder: &str
|
||||||
|
) -> Result<ActixAdminModel, MultipartError> {
|
||||||
let mut hashmap = HashMap::<String, String>::new();
|
let mut hashmap = HashMap::<String, String>::new();
|
||||||
|
|
||||||
while let Some(item) = payload.next().await {
|
while let Some(item) = payload.next().await {
|
||||||
let mut field = item?;
|
let mut field = item?;
|
||||||
|
|
||||||
// TODO: how to handle binary chunks?
|
let mut binary_data: Vec<Bytes> = Vec::new();
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
|
binary_data.push(chunk.unwrap());
|
||||||
//println!("-- CHUNK: \n{:?}", String::from_utf8(chunk.map_or(Vec::new(), |c| c.to_vec())));
|
//println!("-- CHUNK: \n{:?}", String::from_utf8(chunk.map_or(Vec::new(), |c| c.to_vec())));
|
||||||
let res_string = String::from_utf8(chunk.map_or(Vec::new(), |c| c.to_vec()));
|
// let res_string = String::from_utf8(chunk.map_or(Vec::new(), |c| c.to_vec()));
|
||||||
|
}
|
||||||
|
let binary_data = binary_data.concat();
|
||||||
|
if field.content_disposition().get_filename().is_some() {
|
||||||
|
let mut filename = field
|
||||||
|
.content_disposition()
|
||||||
|
.get_filename()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut file_path = format!("{}/{}", file_upload_folder, filename);
|
||||||
|
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);
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let res_string = String::from_utf8(binary_data);
|
||||||
if res_string.is_ok() {
|
if res_string.is_ok() {
|
||||||
hashmap.insert(
|
hashmap.insert(field.name().to_string(), res_string.unwrap());
|
||||||
field.name().to_string(),
|
|
||||||
res_string.unwrap()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,35 +100,68 @@ impl ActixAdminModel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str, is_option_or_string: bool) -> Result<Option<T>, String> {
|
pub fn get_value<T: std::str::FromStr>(
|
||||||
self.get_value_by_closure(key, is_option_or_string, |val| val.parse::<T>())
|
&self,
|
||||||
|
key: &str,
|
||||||
|
is_option_or_string: 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>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_datetime(&self, key: &str, is_option_or_string: bool) -> Result<Option<NaiveDateTime>, String> {
|
pub fn get_datetime(
|
||||||
self.get_value_by_closure(key, is_option_or_string, |val| NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M"))
|
&self,
|
||||||
|
key: &str,
|
||||||
|
is_option_or_string: 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")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_date(&self, key: &str, is_option_or_string: bool) -> Result<Option<NaiveDate>, String> {
|
pub fn get_date(
|
||||||
self.get_value_by_closure(key, is_option_or_string, |val| NaiveDate::parse_from_str(val, "%Y-%m-%d"))
|
&self,
|
||||||
|
key: &str,
|
||||||
|
is_option_or_string: 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) -> Result<Option<bool>, String> {
|
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, |val| if !val.is_empty() && (val == "true" || val == "yes") { Ok(true) } else { Ok(false) });
|
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),
|
||||||
Err(_) => Ok(Some(false))
|
Err(_) => Ok(Some(false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_value_by_closure<T: std::str::FromStr>(&self, key: &str, is_option_or_string: bool, f: impl Fn(&String) -> Result<T, <T as std::str::FromStr>::Err>) -> Result<Option<T>, String> {
|
fn get_value_by_closure<T: std::str::FromStr>(
|
||||||
|
&self,
|
||||||
|
key: &str,
|
||||||
|
is_option_or_string: bool,
|
||||||
|
is_allowed_to_be_empty: bool,
|
||||||
|
f: impl Fn(&String) -> Result<T, <T as std::str::FromStr>::Err>,
|
||||||
|
) -> Result<Option<T>, String> {
|
||||||
let value = self.values.get(key);
|
let value = self.values.get(key);
|
||||||
|
|
||||||
let res: Result<Option<T>, String> = match value {
|
let res: Result<Option<T>, String> = match value {
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
if val.is_empty() && is_option_or_string {
|
match (val.is_empty(), is_option_or_string, is_allowed_to_be_empty) {
|
||||||
return Ok(None);
|
(true, true, true) => return Ok(None),
|
||||||
}
|
(true, true, false) => return Err("Cannot be empty".to_string()),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
let parsed_val = f(val);
|
let parsed_val = f(val);
|
||||||
|
|
||||||
@ -109,9 +171,10 @@ impl ActixAdminModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
match is_option_or_string {
|
match (is_option_or_string, is_allowed_to_be_empty) {
|
||||||
true => Ok(None),
|
(true, true) => Ok(None),
|
||||||
false => Err("Invalid Value".to_string()) // a missing value in the form for a non-optional value
|
(true, false) => Err("Cannot be empty".to_string()),
|
||||||
|
(false, _) => Err("Invalid Value".to_string()), // a missing value in the form for a non-optional value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -70,7 +70,7 @@ async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTra
|
|||||||
|
|
||||||
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("list_link", &E::get_list_link(&entity_name));
|
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||||
ctx.insert("model", &model);
|
ctx.insert("model", &model);
|
||||||
ctx.insert("notifications", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
use super::{render_unauthorized, user_can_access_page};
|
use super::{render_unauthorized, user_can_access_page};
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::ActixAdminError;
|
use crate::ActixAdminError;
|
||||||
use crate::ActixAdminNotification;
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
use actix_multipart::Multipart;
|
||||||
use actix_multipart::MultipartError;
|
use actix_multipart::MultipartError;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_web::{error, web, Error, HttpResponse};
|
use actix_web::{error, web, Error, HttpResponse};
|
||||||
use tera::Context;
|
|
||||||
use actix_multipart::Multipart;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
session: Session,
|
session: Session,
|
||||||
data: web::Data<T>,
|
data: web::Data<T>,
|
||||||
payload: Multipart,
|
payload: Multipart,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let model = ActixAdminModel::create_from_payload(payload).await;
|
let actix_admin = data.get_actix_admin();
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, None).await
|
let model = ActixAdminModel::create_from_payload(
|
||||||
|
payload,
|
||||||
|
&format!("{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name())
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
create_or_edit_post::<T, E>(&session, &data, model, None, actix_admin).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
@ -26,8 +31,13 @@ pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
payload: Multipart,
|
payload: Multipart,
|
||||||
id: web::Path<i32>,
|
id: web::Path<i32>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let model = ActixAdminModel::create_from_payload(payload).await;
|
let actix_admin = data.get_actix_admin();
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, Some(id.into_inner())).await
|
let model = ActixAdminModel::create_from_payload(
|
||||||
|
payload,
|
||||||
|
&format!("{}/{}", actix_admin.configuration.file_upload_directory, E::get_entity_name()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
create_or_edit_post::<T, E>(&session, &data, model, Some(id.into_inner()), actix_admin).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
@ -35,8 +45,8 @@ pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewMod
|
|||||||
data: &web::Data<T>,
|
data: &web::Data<T>,
|
||||||
model_res: Result<ActixAdminModel, MultipartError>,
|
model_res: Result<ActixAdminModel, MultipartError>,
|
||||||
id: Option<i32>,
|
id: Option<i32>,
|
||||||
|
actix_admin: &ActixAdmin,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let actix_admin = data.get_actix_admin();
|
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
|
|
||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
@ -62,14 +72,12 @@ pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewMod
|
|||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => Ok(HttpResponse::SeeOther()
|
||||||
Ok(HttpResponse::SeeOther()
|
|
||||||
.append_header((
|
.append_header((
|
||||||
header::LOCATION,
|
header::LOCATION,
|
||||||
format!("/admin/{}/list", view_model.entity_name),
|
format!("/admin/{}/list", view_model.entity_name),
|
||||||
))
|
))
|
||||||
.finish())
|
.finish()),
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
errors.push(e);
|
errors.push(e);
|
||||||
render_form::<E>(actix_admin, view_model, db, entity_name, &model, errors).await
|
render_form::<E>(actix_admin, view_model, db, entity_name, &model, errors).await
|
||||||
@ -78,7 +86,14 @@ pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewMod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render_form<E: ActixAdminViewModelTrait>(actix_admin: &ActixAdmin, view_model: &ActixAdminViewModel, db: &&sea_orm::DatabaseConnection, entity_name: String, model: &ActixAdminModel, errors: Vec<ActixAdminError>) -> Result<HttpResponse, Error> {
|
async fn render_form<E: ActixAdminViewModelTrait>(
|
||||||
|
actix_admin: &ActixAdmin,
|
||||||
|
view_model: &ActixAdminViewModel,
|
||||||
|
db: &&sea_orm::DatabaseConnection,
|
||||||
|
entity_name: String,
|
||||||
|
model: &ActixAdminModel,
|
||||||
|
errors: Vec<ActixAdminError>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
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(
|
ctx.insert(
|
||||||
@ -86,10 +101,11 @@ async fn render_form<E: ActixAdminViewModelTrait>(actix_admin: &ActixAdmin, view
|
|||||||
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
&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("list_link", &E::get_list_link(&entity_name));
|
ctx.insert("base_path", &E::get_base_path(&entity_name));
|
||||||
ctx.insert("model", model);
|
ctx.insert("model", model);
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*};
|
||||||
use tera::{Context};
|
use tera::{Context};
|
||||||
use super::{ user_can_access_page, render_unauthorized};
|
use super::{ user_can_access_page, render_unauthorized};
|
||||||
|
|
||||||
@ -24,11 +24,23 @@ pub async fn delete<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let result = E::delete_entity(db, id.into_inner()).await;
|
let id = id.into_inner();
|
||||||
|
let model_result = E::get_entity(db, id).await;
|
||||||
|
let delete_result = E::delete_entity(db, id).await;
|
||||||
|
|
||||||
match result {
|
match (model_result, delete_result) {
|
||||||
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
(Ok(model), Ok(_)) => {
|
||||||
Err(_) => Ok(HttpResponse::InternalServerError().finish())
|
for field in view_model.fields {
|
||||||
|
if field.field_type == ActixAdminViewModelFieldType::FileUpload {
|
||||||
|
let file_name = model.get_value::<String>(&field.field_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)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
},
|
||||||
|
(_,_) => Ok(HttpResponse::InternalServerError().finish())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +72,20 @@ pub async fn delete_many<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
|
|
||||||
// TODO: implement delete_many
|
// TODO: implement delete_many
|
||||||
for id in entity_ids {
|
for id in entity_ids {
|
||||||
let result = E::delete_entity(db, id).await;
|
let model_result = E::get_entity(db, id).await;
|
||||||
match result {
|
let delete_result = E::delete_entity(db, id).await;
|
||||||
Err(e) => errors.push(e),
|
match (delete_result, model_result) {
|
||||||
_ => {}
|
(Err(e), _) => errors.push(e),
|
||||||
|
(Ok(_), Ok(model)) => {
|
||||||
|
for field in view_model.fields {
|
||||||
|
if field.field_type == ActixAdminViewModelFieldType::FileUpload {
|
||||||
|
let file_name = model.get_value::<String>(&field.field_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)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Ok(_), Err(e)) => errors.push(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,3 +18,6 @@ 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, user_can_access_page, render_unauthorized };
|
||||||
|
|
||||||
|
mod static_content;
|
||||||
|
pub use static_content::{download, delete_static_content};
|
@ -6,17 +6,22 @@ use crate::ActixAdminNotification;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||||
use super::{ add_auth_context };
|
|
||||||
|
|
||||||
pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(session: Session, data: web::Data<T>, id: web::Path<i32>) -> Result<HttpResponse, Error> {
|
pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(session: Session, data: web::Data<T>, id: web::Path<i32>) -> Result<HttpResponse, Error> {
|
||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let result = E::get_entity(db, id.into_inner()).await;
|
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
let entity_name = E::get_entity_name();
|
||||||
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
|
return render_unauthorized(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
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 model;
|
let model;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
model = res;
|
model = res;
|
||||||
@ -27,9 +32,6 @@ pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(sessio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity_name = E::get_entity_name();
|
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
|
||||||
|
|
||||||
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()
|
||||||
@ -38,10 +40,9 @@ pub async fn show<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(sessio
|
|||||||
.map(|err| ActixAdminNotification::from(err))
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
|
||||||
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("list_link", &E::get_list_link(&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);
|
||||||
|
|
||||||
|
85
src/routes/static_content.rs
Normal file
85
src/routes/static_content.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use actix_web::{web, error, Error, HttpResponse, HttpRequest};
|
||||||
|
use actix_session::{Session};
|
||||||
|
use tera::{Context};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use super::{ user_can_access_page, render_unauthorized};
|
||||||
|
|
||||||
|
pub async fn download<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(req: HttpRequest, session: Session, data: web::Data<T>, params: web::Path<(i32, String)>) -> Result<HttpResponse, Error> {
|
||||||
|
let actix_admin = data.get_actix_admin();
|
||||||
|
let db = &data.get_db();
|
||||||
|
|
||||||
|
let ctx = Context::new();
|
||||||
|
let entity_name = E::get_entity_name();
|
||||||
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
|
return render_unauthorized(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (id, column_name) = params.into_inner();
|
||||||
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
|
let result = E::get_entity(db, id).await;
|
||||||
|
let model;
|
||||||
|
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 = 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(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_static_content<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(session: Session, data: web::Data<T>, params: web::Path<(i32, String)>) -> Result<HttpResponse, Error> {
|
||||||
|
let actix_admin = data.get_actix_admin();
|
||||||
|
let db = &data.get_db();
|
||||||
|
|
||||||
|
let mut ctx = Context::new();
|
||||||
|
let entity_name = E::get_entity_name();
|
||||||
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
|
return render_unauthorized(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (id, column_name) = params.into_inner();
|
||||||
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
|
let result = E::get_entity(db, id).await;
|
||||||
|
let mut model;
|
||||||
|
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());
|
||||||
|
std::fs::remove_file(file_path).unwrap();
|
||||||
|
model.values.remove(&column_name);
|
||||||
|
let _edit_res = E::edit_entity(db, id, model.clone()).await;
|
||||||
|
|
||||||
|
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 = TERA
|
||||||
|
.render("form_elements/input.html", &ctx)
|
||||||
|
.map_err(|err| error::ErrorInternalServerError(err))? ;
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
|
||||||
|
}
|
@ -26,8 +26,8 @@ pub trait ActixAdminViewModelTrait {
|
|||||||
|
|
||||||
fn get_entity_name() -> String;
|
fn get_entity_name() -> String;
|
||||||
|
|
||||||
fn get_list_link(entity_name: &String) -> String {
|
fn get_base_path(entity_name: &String) -> String {
|
||||||
format!("/admin/{}/list", entity_name)
|
format!("/admin/{}", entity_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ impl From<ActixAdminViewModel> for ActixAdminViewModelSerializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub enum ActixAdminViewModelFieldType {
|
pub enum ActixAdminViewModelFieldType {
|
||||||
Number,
|
Number,
|
||||||
Text,
|
Text,
|
||||||
@ -70,7 +70,8 @@ pub enum ActixAdminViewModelFieldType {
|
|||||||
Date,
|
Date,
|
||||||
Time,
|
Time,
|
||||||
DateTime,
|
DateTime,
|
||||||
SelectList
|
SelectList,
|
||||||
|
FileUpload
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -83,13 +84,16 @@ pub struct ActixAdminViewModelField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActixAdminViewModelFieldType {
|
impl ActixAdminViewModelFieldType {
|
||||||
pub fn get_field_type(type_path: &str, select_list: String, is_textarea: 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;
|
||||||
}
|
}
|
||||||
if is_textarea {
|
if is_textarea {
|
||||||
return ActixAdminViewModelFieldType::TextArea;
|
return ActixAdminViewModelFieldType::TextArea;
|
||||||
}
|
}
|
||||||
|
if is_file_upload {
|
||||||
|
return ActixAdminViewModelFieldType::FileUpload;
|
||||||
|
}
|
||||||
|
|
||||||
match type_path {
|
match type_path {
|
||||||
"i32" => ActixAdminViewModelFieldType::Number,
|
"i32" => ActixAdminViewModelFieldType::Number,
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<button class="button is-link" type="submit">Save</i></button>
|
<button class="button is-link" type="submit">Save</i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a class="button is-link is-light" href="{{ list_link }}">Cancel</a>
|
<a class="button is-link is-light" href="{{ base_path }}/list">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{% if model_field.field_type == "TextArea" %}
|
{% if model_field.field_type == "TextArea" %}
|
||||||
<textarea
|
<textarea class="{{ model_field | get_html_input_class }}
|
||||||
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="" ) !=""
|
model.errors | get(key=model_field.field_name, default="" ) !=""
|
||||||
@ -8,15 +7,18 @@
|
|||||||
model.custom_errors | get(key=model_field.field_name, default="" ) !=""
|
model.custom_errors | get(key=model_field.field_name, default="" ) !=""
|
||||||
%}is-danger{% else %}is-success{% endif %}
|
%}is-danger{% else %}is-success{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
" type="{{ model_field | get_html_input_type }}" name="{{ model_field.field_name }}"
|
||||||
type="{{ model_field | get_html_input_type }}"
|
|
||||||
name="{{ model_field.field_name }}"
|
|
||||||
placeholder="{{ model_field.field_name }}"
|
placeholder="{{ model_field.field_name }}"
|
||||||
aria-label="{{ model_field.field_name }}"
|
aria-label="{{ model_field.field_name }}">{{ model.values | get(key=model_field.field_name, default="") }}</textarea>
|
||||||
>{{ 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 }}/static_content/{{ 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 }}/static_content/{{ model.primary_key }}/{{ model_field.field_name }}"
|
||||||
|
hx-confirm="Are you sure?"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input
|
<input class="{{ model_field | get_html_input_class }}
|
||||||
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="" ) !=""
|
model.errors | get(key=model_field.field_name, default="" ) !=""
|
||||||
@ -24,11 +26,7 @@
|
|||||||
model.custom_errors | get(key=model_field.field_name, default="" ) !=""
|
model.custom_errors | get(key=model_field.field_name, default="" ) !=""
|
||||||
%}is-danger{% else %}is-success{% endif %}
|
%}is-danger{% else %}is-success{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
" type="{{ model_field | get_html_input_type }}"
|
||||||
type="{{ model_field | get_html_input_type }}"
|
value="{{ model.values | get(key=model_field.field_name, default="") }}" name="{{ model_field.field_name }}"
|
||||||
value="{{ model.values | get(key=model_field.field_name, default="") }}"
|
placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}">
|
||||||
name="{{ model_field.field_name }}"
|
|
||||||
placeholder="{{ model_field.field_name }}"
|
|
||||||
aria-label="{{ model_field.field_name }}"
|
|
||||||
>
|
|
||||||
{% endif %}
|
{% endif %}
|
@ -94,6 +94,8 @@
|
|||||||
{% for model_field in view_model.fields -%}
|
{% for model_field in view_model.fields -%}
|
||||||
{% if model_field.field_type == "Checkbox" %}
|
{% if model_field.field_type == "Checkbox" %}
|
||||||
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
||||||
|
{% elif model_field.field_type == "FileUpload" %}
|
||||||
|
<td><a href="static_content/{{ entity.primary_key }}/{{ model_field.field_name }}">{{ entity.values | get(key=model_field.field_name) }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
<p>
|
<p>
|
||||||
{% if model_field.field_type == "Checkbox" %}
|
{% if model_field.field_type == "Checkbox" %}
|
||||||
<td>{{ model.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
<td>{{ model.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
||||||
|
{% elif model_field.field_type == "FileUpload" %}
|
||||||
|
<td><a href="static_content/{{ entity.primary_key }}/{{ model_field.field_name }}">{{ entity.values | get(key=model_field.field_name) }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ model.values | get(key=model_field.field_name) }}</td>
|
<td>{{ model.values | get(key=model_field.field_name) }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -19,7 +21,7 @@
|
|||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a class="button is-link is-light" href="{{ list_link }}">Back</a>
|
<a class="button is-link is-light" href="{{ base_path }}/list">Back</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,6 +41,7 @@ pub fn create_actix_admin_builder() -> ActixAdminBuilder {
|
|||||||
user_is_logged_in: None,
|
user_is_logged_in: None,
|
||||||
login_link: None,
|
login_link: None,
|
||||||
logout_link: None,
|
logout_link: None,
|
||||||
|
file_upload_directory: "./file_uploads"
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||||
@ -78,8 +79,9 @@ async fn create_post_from_plaintext<
|
|||||||
data: web::Data<T>,
|
data: web::Data<T>,
|
||||||
text: String,
|
text: String,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let actix_admin = data.get_actix_admin();
|
||||||
let model = ActixAdminModel::from(text);
|
let model = ActixAdminModel::from(text);
|
||||||
create_or_edit_post::<T, E>(&session, &data, Ok(model), None).await
|
create_or_edit_post::<T, E>(&session, &data, Ok(model), None, actix_admin).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_post_from_plaintext<
|
async fn edit_post_from_plaintext<
|
||||||
@ -91,6 +93,7 @@ async fn edit_post_from_plaintext<
|
|||||||
text: String,
|
text: String,
|
||||||
id: web::Path<i32>,
|
id: web::Path<i32>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let actix_admin = data.get_actix_admin();
|
||||||
let model = ActixAdminModel::from(text);
|
let model = ActixAdminModel::from(text);
|
||||||
create_or_edit_post::<T, E>(&session, &data, Ok(model), Some(id.into_inner())).await
|
create_or_edit_post::<T, E>(&session, &data, Ok(model), Some(id.into_inner()), actix_admin).await
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user