implement error handling through notifications
This commit is contained in:
parent
5f515c96eb
commit
859c139f8b
@ -27,6 +27,7 @@ serde = "1.0.136"
|
|||||||
serde_derive = "1.0.136"
|
serde_derive = "1.0.136"
|
||||||
sea-orm = { version = "^0.9.1", features = [], default-features = false }
|
sea-orm = { version = "^0.9.1", features = [], default-features = false }
|
||||||
actix-admin-macros = { version = "0.1.0", path = "actix_admin_macros" }
|
actix-admin-macros = { version = "0.1.0", path = "actix_admin_macros" }
|
||||||
|
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 }
|
@ -67,7 +67,7 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
|||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl ActixAdminViewModelTrait for Entity {
|
impl ActixAdminViewModelTrait for Entity {
|
||||||
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize, search: &String) -> (usize, Vec<ActixAdminModel>) {
|
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize, search: &String) -> Result<(usize, Vec<ActixAdminModel>), ActixAdminError> {
|
||||||
let entities = Entity::list_model(db, page, entities_per_page, search).await;
|
let entities = Entity::list_model(db, page, entities_per_page, search).await;
|
||||||
entities
|
entities
|
||||||
}
|
}
|
||||||
@ -82,43 +82,51 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> ActixAdminModel {
|
async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError> {
|
||||||
let new_model = ActiveModel::from(model.clone());
|
let new_model = ActiveModel::from(model.clone());
|
||||||
let insert_operation = Entity::insert(new_model).exec(db).await;
|
let insert_operation = Entity::insert(new_model).exec(db).await?;
|
||||||
|
|
||||||
model
|
model.primary_key = Some(insert_operation.last_insert_id.to_string());
|
||||||
|
|
||||||
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_entity(db: &DatabaseConnection, id: i32) -> ActixAdminModel {
|
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError> {
|
||||||
// TODO: separate primary key from other keys
|
// TODO: separate primary key from other keys
|
||||||
let entity = Entity::find_by_id(id).one(db).await.unwrap().unwrap();
|
let entity = Entity::find_by_id(id).one(db).await?;
|
||||||
let model = ActixAdminModel::from(entity);
|
match entity {
|
||||||
model
|
Some(e) => Ok(ActixAdminModel::from(e)),
|
||||||
|
_ => Err(ActixAdminError::EntityDoesNotExistError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_entity(db: &DatabaseConnection, id: i32, mut model: ActixAdminModel) -> ActixAdminModel {
|
async fn edit_entity(db: &DatabaseConnection, id: i32, mut model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError> {
|
||||||
let entity: Option<Model> = Entity::find_by_id(id).one(db).await.unwrap();
|
let entity: Option<Model> = Entity::find_by_id(id).one(db).await?;
|
||||||
let mut entity: ActiveModel = entity.unwrap().into();
|
|
||||||
|
|
||||||
|
match entity {
|
||||||
|
Some(e) => {
|
||||||
|
let mut entity: ActiveModel = e.into();
|
||||||
#(#fields_for_edit_model);*;
|
#(#fields_for_edit_model);*;
|
||||||
let entity: Model = entity.update(db).await.unwrap();
|
let entity: Model = entity.update(db).await?;
|
||||||
|
Ok(model)
|
||||||
model
|
},
|
||||||
|
_ => Err(ActixAdminError::EntityDoesNotExistError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_entity(db: &DatabaseConnection, id: i32) -> bool {
|
async fn delete_entity(db: &DatabaseConnection, id: i32) -> Result<bool, ActixAdminError> {
|
||||||
let result = Entity::delete_by_id(id).exec(db).await;
|
let result = Entity::delete_by_id(id).exec(db).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => true,
|
Ok(_) => Ok(true),
|
||||||
Err(_) => false
|
Err(_) => Err(ActixAdminError::DeleteError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_select_lists(db: &DatabaseConnection) -> HashMap<String, Vec<(String, String)>> {
|
async fn get_select_lists(db: &DatabaseConnection) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError> {
|
||||||
hashmap![
|
Ok(hashmap![
|
||||||
#(#select_lists),*
|
#(#select_lists),*
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_entity_name() -> String {
|
fn get_entity_name() -> String {
|
||||||
@ -173,7 +181,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActixAdminModelTrait for Entity {
|
impl ActixAdminModelTrait for Entity {
|
||||||
async fn list_model(db: &DatabaseConnection, page: usize, posts_per_page: usize, search: &String) -> (usize, Vec<ActixAdminModel>) {
|
async fn list_model(db: &DatabaseConnection, page: usize, posts_per_page: usize, search: &String) -> Result<(usize, Vec<ActixAdminModel>), ActixAdminError> {
|
||||||
use sea_orm::{ query::* };
|
use sea_orm::{ query::* };
|
||||||
let paginator = Entity::find()
|
let paginator = Entity::find()
|
||||||
.filter(
|
.filter(
|
||||||
@ -182,11 +190,10 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
)
|
)
|
||||||
.order_by_asc(Column::Id)
|
.order_by_asc(Column::Id)
|
||||||
.paginate(db, posts_per_page);
|
.paginate(db, posts_per_page);
|
||||||
let num_pages = paginator.num_pages().await.ok().unwrap();
|
let num_pages = paginator.num_pages().await?;
|
||||||
let entities = paginator
|
let entities = paginator
|
||||||
.fetch_page(page - 1)
|
.fetch_page(page - 1)
|
||||||
.await
|
.await?;
|
||||||
.expect("could not retrieve entities");
|
|
||||||
let mut model_entities = Vec::new();
|
let mut model_entities = Vec::new();
|
||||||
for entity in entities {
|
for entity in entities {
|
||||||
model_entities.push(
|
model_entities.push(
|
||||||
@ -194,7 +201,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(num_pages, model_entities)
|
Ok((num_pages, model_entities))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_model(model: &mut ActixAdminModel) {
|
fn validate_model(model: &mut ActixAdminModel) {
|
||||||
|
@ -12,15 +12,15 @@ pub fn get_select_list_from_model(_input: proc_macro::TokenStream) -> proc_macro
|
|||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActixAdminSelectListTrait for Entity {
|
impl ActixAdminSelectListTrait for Entity {
|
||||||
async fn get_key_value(db: &DatabaseConnection) -> Vec<(String, String)> {
|
async fn get_key_value(db: &DatabaseConnection) -> Result<Vec<(String, String)>, ActixAdminError> {
|
||||||
let entities = Entity::find().order_by_asc(Column::Id).all(db).await;
|
let entities = Entity::find().order_by_asc(Column::Id).all(db).await?;
|
||||||
let mut key_value = Vec::new();
|
let mut key_value = Vec::new();
|
||||||
|
|
||||||
for entity in entities.unwrap() {
|
for entity in entities {
|
||||||
key_value.push((entity.id.to_string(), entity.to_string()));
|
key_value.push((entity.id.to_string(), entity.to_string()));
|
||||||
};
|
};
|
||||||
key_value.sort_by(|a, b| a.1.cmp(&b.1));
|
key_value.sort_by(|a, b| a.1.cmp(&b.1));
|
||||||
key_value
|
Ok(key_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,12 +35,12 @@ pub fn get_select_list_from_enum(input: proc_macro::TokenStream) -> proc_macro::
|
|||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActixAdminSelectListTrait for #ty {
|
impl ActixAdminSelectListTrait for #ty {
|
||||||
async fn get_key_value(db: &DatabaseConnection) -> Vec<(String, String)> {
|
async fn get_key_value(db: &DatabaseConnection) -> Result<Vec<(String, String)>, ActixAdminError> {
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
for field in #ty::iter() {
|
for field in #ty::iter() {
|
||||||
fields.push((field.to_string(), field.to_string()));
|
fields.push((field.to_string(), field.to_string()));
|
||||||
}
|
}
|
||||||
fields
|
Ok(fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -56,7 +56,7 @@ pub fn get_select_lists(fields: &Vec<ModelField>) -> Vec<proc_macro2::TokenStrea
|
|||||||
let ident_name = model_field.ident.to_string();
|
let ident_name = model_field.ident.to_string();
|
||||||
let select_list_ident = Ident::new(&(model_field.select_list), Span::call_site());
|
let select_list_ident = Ident::new(&(model_field.select_list), Span::call_site());
|
||||||
quote! {
|
quote! {
|
||||||
#ident_name => #select_list_ident::get_key_value(db).await
|
#ident_name => #select_list_ident::get_key_value(db).await?
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
82
src/lib.rs
82
src/lib.rs
@ -122,6 +122,12 @@ use tera::{Tera, Result, to_value, try_get_value };
|
|||||||
use std::{ hash::BuildHasher};
|
use std::{ hash::BuildHasher};
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
use actix_web::{
|
||||||
|
error,
|
||||||
|
http::{header::ContentType, StatusCode},
|
||||||
|
HttpResponse,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod view_model;
|
pub mod view_model;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
@ -133,7 +139,7 @@ pub mod prelude {
|
|||||||
pub use crate::model::{ ActixAdminModel, ActixAdminModelValidationTrait, ActixAdminModelTrait};
|
pub use crate::model::{ ActixAdminModel, ActixAdminModelValidationTrait, ActixAdminModelTrait};
|
||||||
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField, ActixAdminViewModelSerializable, ActixAdminViewModelFieldType };
|
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField, ActixAdminViewModelSerializable, ActixAdminViewModelFieldType };
|
||||||
pub use actix_admin_macros::{ DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel, DeriveActixAdminEnumSelectList, DeriveActixAdminModelSelectList };
|
pub use actix_admin_macros::{ DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel, DeriveActixAdminEnumSelectList, DeriveActixAdminModelSelectList };
|
||||||
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin, ActixAdminConfiguration };
|
pub use crate::{ ActixAdminError, ActixAdminAppDataTrait, ActixAdmin, ActixAdminConfiguration };
|
||||||
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
||||||
pub use crate::routes::{ create_or_edit_post, get_admin_ctx };
|
pub use crate::routes::{ create_or_edit_post, get_admin_ctx };
|
||||||
pub use crate::{ TERA };
|
pub use crate::{ TERA };
|
||||||
@ -211,7 +217,7 @@ pub trait ActixAdminAppDataTrait {
|
|||||||
// SelectListTrait
|
// SelectListTrait
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ActixAdminSelectListTrait {
|
pub trait ActixAdminSelectListTrait {
|
||||||
async fn get_key_value(db: &DatabaseConnection) -> Vec<(String, String)>;
|
async fn get_key_value(db: &DatabaseConnection) -> core::result::Result<Vec<(String, String)>, ActixAdminError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -236,3 +242,75 @@ pub struct ActixAdminMenuElement {
|
|||||||
pub link: String,
|
pub link: String,
|
||||||
pub is_custom_handler: bool
|
pub is_custom_handler: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
pub enum ActixAdminError {
|
||||||
|
#[display(fmt = "Internal error")]
|
||||||
|
InternalError,
|
||||||
|
|
||||||
|
#[display(fmt = "Form has validation errors")]
|
||||||
|
ValidationErrors,
|
||||||
|
|
||||||
|
#[display(fmt = "Could not list entities")]
|
||||||
|
ListError,
|
||||||
|
|
||||||
|
#[display(fmt = "Could not create entity")]
|
||||||
|
CreateError,
|
||||||
|
|
||||||
|
#[display(fmt = "Could not delete entity")]
|
||||||
|
DeleteError,
|
||||||
|
|
||||||
|
#[display(fmt = "Could not edit entity")]
|
||||||
|
EditError,
|
||||||
|
|
||||||
|
#[display(fmt = "Database error")]
|
||||||
|
DatabaseError,
|
||||||
|
|
||||||
|
#[display(fmt = "Entity does not exist")]
|
||||||
|
EntityDoesNotExistError
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl error::ResponseError for ActixAdminError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::build(self.status_code())
|
||||||
|
.insert_header(ContentType::html())
|
||||||
|
.body(self.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match *self {
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<sea_orm::DbErr> for ActixAdminError {
|
||||||
|
fn from(_err: sea_orm::DbErr) -> ActixAdminError {
|
||||||
|
ActixAdminError::DatabaseError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
#[derive(Debug, Display, Serialize)]
|
||||||
|
pub enum ActixAdminNotificationType {
|
||||||
|
#[display(fmt = "is-danger")]
|
||||||
|
Danger,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ActixAdminNotification {
|
||||||
|
css_class: String,
|
||||||
|
message: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<ActixAdminError> for ActixAdminNotification {
|
||||||
|
fn from(e: ActixAdminError) -> ActixAdminNotification {
|
||||||
|
ActixAdminNotification {
|
||||||
|
css_class: ActixAdminNotificationType::Danger.to_string(),
|
||||||
|
message: e.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::ActixAdminViewModelField;
|
use crate::{ ActixAdminViewModelField, ActixAdminError};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -14,7 +14,7 @@ pub trait ActixAdminModelTrait {
|
|||||||
page: usize,
|
page: usize,
|
||||||
posts_per_page: usize,
|
posts_per_page: usize,
|
||||||
search: &String
|
search: &String
|
||||||
) -> (usize, Vec<ActixAdminModel>);
|
) -> Result<(usize, Vec<ActixAdminModel>), ActixAdminError>;
|
||||||
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
||||||
fn validate_model(model: &mut ActixAdminModel);
|
fn validate_model(model: &mut ActixAdminModel);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
use actix_web::{error, web, Error, HttpRequest, HttpResponse};
|
||||||
use tera::{Context};
|
use tera::{Context};
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
|
use crate::ActixAdminError;
|
||||||
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
@ -16,7 +18,7 @@ pub async fn create_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
let model = ActixAdminModel::create_empty();
|
let model = ActixAdminModel::create_empty();
|
||||||
|
|
||||||
create_or_edit_get::<T, E>(&session, &data, db, model).await
|
create_or_edit_get::<T, E>(&session, &data, db, Ok(model)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
@ -32,13 +34,14 @@ pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
create_or_edit_get::<T, E>(&session, &data, db, model).await
|
create_or_edit_get::<T, E>(&session, &data, db, model).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(session: &Session, data: &web::Data<T>, db: &sea_orm::DatabaseConnection, model: ActixAdminModel) -> Result<HttpResponse, Error>{
|
async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(session: &Session, data: &web::Data<T>, db: &sea_orm::DatabaseConnection, model_result: Result<ActixAdminModel, ActixAdminError>) -> Result<HttpResponse, Error>{
|
||||||
let actix_admin = &data.get_actix_admin();
|
let actix_admin = &data.get_actix_admin();
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
let entity_names = &actix_admin.entity_names;
|
let entity_names = &actix_admin.entity_names;
|
||||||
ctx.insert("entity_names", entity_names);
|
ctx.insert("entity_names", entity_names);
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
|
||||||
@ -46,13 +49,33 @@ async fn create_or_edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTra
|
|||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let model;
|
||||||
|
match model_result {
|
||||||
|
Ok(res) => {
|
||||||
|
model = res;
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
errors.push(e);
|
||||||
|
model = ActixAdminModel::create_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut http_response_code = match errors.is_empty() {
|
||||||
|
true => HttpResponse::Ok(),
|
||||||
|
false => HttpResponse::InternalServerError(),
|
||||||
|
};
|
||||||
|
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||||
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
|
.collect();
|
||||||
|
|
||||||
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("list_link", &E::get_list_link(&entity_name));
|
||||||
ctx.insert("model", &model);
|
ctx.insert("model", &model);
|
||||||
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("create_or_edit.html", &ctx)
|
.render("create_or_edit.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
use super::{render_unauthorized, user_can_access_page};
|
use super::{render_unauthorized, user_can_access_page};
|
||||||
|
use crate::ActixAdminError;
|
||||||
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
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};
|
||||||
@ -13,7 +16,7 @@ pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
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.unwrap();
|
let model = ActixAdminModel::create_from_payload(payload).await;
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, None).await
|
create_or_edit_post::<T, E>(&session, &data, model, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,20 +26,21 @@ 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.unwrap();
|
let model = ActixAdminModel::create_from_payload(payload).await;
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, Some(id.into_inner())).await
|
create_or_edit_post::<T, E>(&session, &data, model, Some(id.into_inner())).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
session: &Session,
|
session: &Session,
|
||||||
data: &web::Data<T>,
|
data: &web::Data<T>,
|
||||||
mut model: ActixAdminModel,
|
model_res: Result<ActixAdminModel, MultipartError>,
|
||||||
id: Option<i32>,
|
id: Option<i32>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let actix_admin = data.get_actix_admin();
|
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();
|
||||||
|
let mut errors: Vec<ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
@ -44,37 +48,57 @@ pub async fn create_or_edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewMod
|
|||||||
return render_unauthorized(&ctx);
|
return render_unauthorized(&ctx);
|
||||||
}
|
}
|
||||||
let db = &data.get_db();
|
let db = &data.get_db();
|
||||||
|
|
||||||
|
let mut model = model_res.unwrap();
|
||||||
E::validate_entity(&mut model);
|
E::validate_entity(&mut model);
|
||||||
|
|
||||||
if model.has_errors() {
|
if model.has_errors() {
|
||||||
let mut ctx = Context::new();
|
errors.push(ActixAdminError::ValidationErrors);
|
||||||
ctx.insert("entity_names", &actix_admin.entity_names);
|
render_form::<E>(actix_admin, view_model, db, entity_name, &model, errors).await
|
||||||
ctx.insert(
|
|
||||||
"view_model",
|
|
||||||
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
|
||||||
);
|
|
||||||
ctx.insert("select_lists", &E::get_select_lists(db).await);
|
|
||||||
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
|
||||||
ctx.insert("model", &model);
|
|
||||||
|
|
||||||
let body = TERA
|
|
||||||
.render("create_or_edit.html", &ctx)
|
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
|
||||||
} else {
|
} else {
|
||||||
match id {
|
let res = match id {
|
||||||
Some(id) => E::edit_entity(db, id, model).await,
|
Some(id) => E::edit_entity(db, id, model.clone()).await,
|
||||||
None => E::create_entity(db, model).await,
|
None => E::create_entity(db, model.clone()).await,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
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) => {
|
||||||
|
errors.push(e);
|
||||||
|
render_form::<E>(actix_admin, view_model, db, entity_name, &model, errors).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
ctx.insert("entity_names", &actix_admin.entity_names);
|
||||||
|
ctx.insert(
|
||||||
|
"view_model",
|
||||||
|
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
||||||
|
);
|
||||||
|
ctx.insert("select_lists", &E::get_select_lists(db).await?);
|
||||||
|
ctx.insert("list_link", &E::get_list_link(&entity_name));
|
||||||
|
ctx.insert("model", model);
|
||||||
|
|
||||||
|
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||||
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
let body = TERA
|
||||||
|
.render("create_or_edit.html", &ctx)
|
||||||
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl From<String> for ActixAdminModel {
|
impl From<String> for ActixAdminModel {
|
||||||
|
@ -24,10 +24,12 @@ 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 result = E::delete_entity(db, id.into_inner()).await;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
match result {
|
||||||
.finish())
|
Ok(_) => Ok(HttpResponse::Ok().finish()),
|
||||||
|
Err(_) => Ok(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_many<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
pub async fn delete_many<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||||
@ -40,6 +42,7 @@ pub async fn delete_many<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
|
|||||||
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();
|
||||||
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
if !user_can_access_page(&session, actix_admin, view_model) {
|
if !user_can_access_page(&session, actix_admin, view_model) {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
@ -57,13 +60,24 @@ 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 result = E::delete_entity(db, id).await;
|
||||||
|
match result {
|
||||||
|
Err(e) => errors.push(e),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match errors.is_empty() {
|
||||||
|
true => {
|
||||||
Ok(HttpResponse::SeeOther()
|
Ok(HttpResponse::SeeOther()
|
||||||
.append_header((
|
.append_header((
|
||||||
header::LOCATION,
|
header::LOCATION,
|
||||||
format!("/admin/{}/list?render_partial=true", entity_name),
|
format!("/admin/{}/list?render_partial=true", entity_name),
|
||||||
))
|
))
|
||||||
.finish())
|
.finish())
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
Ok(HttpResponse::InternalServerError().finish())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -21,9 +21,11 @@ pub fn get_admin_ctx<T: ActixAdminAppDataTrait>(session: Session, data: &web::Da
|
|||||||
|
|
||||||
pub async fn index<T: ActixAdminAppDataTrait>(session: Session, data: web::Data<T>) -> Result<HttpResponse, Error> {
|
pub async fn index<T: ActixAdminAppDataTrait>(session: Session, data: web::Data<T>) -> Result<HttpResponse, Error> {
|
||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
|
let notifications: Vec<crate::ActixAdminNotification> = Vec::new();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
ctx.insert("entity_names", &actix_admin.entity_names);
|
ctx.insert("entity_names", &actix_admin.entity_names);
|
||||||
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use crate::prelude::*;
|
|||||||
use crate::ActixAdminViewModelTrait;
|
use crate::ActixAdminViewModelTrait;
|
||||||
use crate::ActixAdminViewModel;
|
use crate::ActixAdminViewModel;
|
||||||
use crate::ActixAdminModel;
|
use crate::ActixAdminModel;
|
||||||
|
use crate::ActixAdminNotification;
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
use super::{ add_auth_context, user_can_access_page, render_unauthorized};
|
||||||
@ -28,6 +29,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let actix_admin = data.get_actix_admin();
|
let actix_admin = data.get_actix_admin();
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
let mut errors: Vec<ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
@ -48,22 +50,40 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
let search = params.search.clone().unwrap_or(String::new());
|
let search = params.search.clone().unwrap_or(String::new());
|
||||||
|
|
||||||
let db = data.get_db();
|
let db = data.get_db();
|
||||||
let result: (usize, Vec<ActixAdminModel>) = E::list(db, page, entities_per_page, &search).await;
|
let result = E::list(db, page, entities_per_page, &search).await;
|
||||||
let entities = result.1;
|
match result {
|
||||||
let num_pages = result.0;
|
Ok(res) => {
|
||||||
|
let entities = res.1;
|
||||||
|
let num_pages = res.0;
|
||||||
|
ctx.insert("entities", &entities);
|
||||||
|
ctx.insert("num_pages", &num_pages);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
ctx.insert("entities", &Vec::<ActixAdminModel>::new());
|
||||||
|
ctx.insert("num_pages", &0);
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut http_response_code = match errors.is_empty() {
|
||||||
|
false => HttpResponse::InternalServerError(),
|
||||||
|
true => HttpResponse::Ok()
|
||||||
|
};
|
||||||
|
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||||
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
|
.collect();
|
||||||
|
|
||||||
ctx.insert("entity_name", &entity_name);
|
ctx.insert("entity_name", &entity_name);
|
||||||
ctx.insert("entities", &entities);
|
ctx.insert("notifications", ¬ifications);
|
||||||
ctx.insert("page", &page);
|
ctx.insert("page", &page);
|
||||||
ctx.insert("params", &entities_per_page);
|
ctx.insert("params", &entities_per_page);
|
||||||
ctx.insert("entities_per_page", &entities_per_page);
|
ctx.insert("entities_per_page", &entities_per_page);
|
||||||
ctx.insert("render_partial", &render_partial);
|
ctx.insert("render_partial", &render_partial);
|
||||||
ctx.insert("num_pages", &num_pages);
|
|
||||||
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));
|
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));
|
||||||
ctx.insert("search", &search);
|
ctx.insert("search", &search);
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("list.html", &ctx)
|
.render("list.html", &ctx)
|
||||||
.map_err(|err| error::ErrorInternalServerError(err))?;
|
.map_err(|err| error::ErrorInternalServerError(err))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ use actix_web::{error, web, Error, HttpResponse};
|
|||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use tera::{Context};
|
use tera::{Context};
|
||||||
|
|
||||||
|
use crate::ActixAdminNotification;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
@ -11,20 +12,43 @@ 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 model = E::get_entity(db, id.into_inner()).await;
|
let result = E::get_entity(db, id.into_inner()).await;
|
||||||
|
let mut errors: Vec<crate::ActixAdminError> = Vec::new();
|
||||||
|
|
||||||
|
let model;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(res) => {
|
||||||
|
model = res;
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
errors.push(e);
|
||||||
|
model = ActixAdminModel::create_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let entity_name = E::get_entity_name();
|
let entity_name = E::get_entity_name();
|
||||||
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
let view_model: &ActixAdminViewModel = actix_admin.view_models.get(&entity_name).unwrap();
|
||||||
|
|
||||||
|
let mut http_response_code = match errors.is_empty() {
|
||||||
|
false => HttpResponse::InternalServerError(),
|
||||||
|
true => HttpResponse::Ok()
|
||||||
|
};
|
||||||
|
let notifications: Vec<ActixAdminNotification> = errors.into_iter()
|
||||||
|
.map(|err| ActixAdminNotification::from(err))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut ctx = Context::new();
|
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("list_link", &E::get_list_link(&entity_name));
|
||||||
ctx.insert("entity_names", &actix_admin.entity_names);
|
ctx.insert("entity_names", &actix_admin.entity_names);
|
||||||
|
ctx.insert("notifications", ¬ifications);
|
||||||
|
|
||||||
add_auth_context(&session, actix_admin, &mut ctx);
|
add_auth_context(&session, actix_admin, &mut ctx);
|
||||||
|
|
||||||
let body = TERA
|
let body = TERA
|
||||||
.render("show.html", &ctx)
|
.render("show.html", &ctx)
|
||||||
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(body))
|
Ok(http_response_code.content_type("text/html").body(body))
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ use std::collections::HashMap;
|
|||||||
use crate::ActixAdminModel;
|
use crate::ActixAdminModel;
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
use crate::ActixAdminError;
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
pub trait ActixAdminViewModelTrait {
|
pub trait ActixAdminViewModelTrait {
|
||||||
@ -13,14 +14,14 @@ pub trait ActixAdminViewModelTrait {
|
|||||||
page: usize,
|
page: usize,
|
||||||
entities_per_page: usize,
|
entities_per_page: usize,
|
||||||
search: &String
|
search: &String
|
||||||
) -> (usize, Vec<ActixAdminModel>);
|
) -> Result<(usize, Vec<ActixAdminModel>), ActixAdminError>;
|
||||||
|
|
||||||
// TODO: Replace return value with proper Result Type containing Ok or Err
|
// TODO: Replace return value with proper Result Type containing Ok or Err
|
||||||
async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> ActixAdminModel;
|
async fn create_entity(db: &DatabaseConnection, model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError>;
|
||||||
async fn delete_entity(db: &DatabaseConnection, id: i32) -> bool;
|
async fn delete_entity(db: &DatabaseConnection, id: i32) -> Result<bool, ActixAdminError>;
|
||||||
async fn get_entity(db: &DatabaseConnection, id: i32) -> ActixAdminModel;
|
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError>;
|
||||||
async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> ActixAdminModel;
|
async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError>;
|
||||||
async fn get_select_lists(db: &DatabaseConnection) -> HashMap<String, Vec<(String, String)>>;
|
async fn get_select_lists(db: &DatabaseConnection) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError>;
|
||||||
fn validate_entity(model: &mut ActixAdminModel);
|
fn validate_entity(model: &mut ActixAdminModel);
|
||||||
|
|
||||||
fn get_entity_name() -> String;
|
fn get_entity_name() -> String;
|
||||||
|
@ -17,6 +17,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include "navbar.html" %}
|
{% include "navbar.html" %}
|
||||||
<div class="container is-fluid">
|
<div class="container is-fluid">
|
||||||
|
<div id="notifications">
|
||||||
|
{% if notifications %}
|
||||||
|
{% for notification in notifications -%}
|
||||||
|
<div class="notification mb-4 is-light {{ notification.css_class }}">
|
||||||
|
<button class="delete" onclick="this.parentElement.remove()"></button>
|
||||||
|
{{ notification.message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form hx-boost="true" hx-indicator="#loading" hx-encoding="multipart/form-data" method="post" enctype="multipart/form-data">
|
<form hx-boost="true" hx-indicator="#loading" hx-push-url="true" hx-encoding="multipart/form-data" method="post" enctype="multipart/form-data">
|
||||||
{% for model_field in view_model.fields -%}
|
{% for model_field in view_model.fields -%}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="{{ model_field | get_html_input_type }}" for="{{ model_field.field_name }}">
|
<label class="{{ model_field | get_html_input_type }}" for="{{ model_field.field_name }}">
|
||||||
|
@ -36,8 +36,13 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmx.on("htmx:responseError", function () {
|
||||||
|
document.getElementById("notifications").insertAdjacentHTML(
|
||||||
|
"afterend",
|
||||||
|
"<div class=\"notification mb-4 is-light is-danger\"><button class=\"delete\" onclick=\"this.parentElement.remove()\"></button>An Error occurred</div>");
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -51,7 +56,7 @@
|
|||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
pointer-events: none
|
pointer-events: none
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -6,7 +6,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button is-primary" href="create">Create</a>
|
<a class="button is-primary" href="create" hx-boost="true" hx-indicator="#loading">Create</a>
|
||||||
|
|
||||||
<div hx-include="#checked-rows" hx-target="#{{ entity_name }}table" class="dropdown is-hoverable">
|
<div hx-include="#checked-rows" hx-target="#{{ entity_name }}table" class="dropdown is-hoverable">
|
||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="dropdown-menu" id="dropdown-menu4">
|
<div class="dropdown-menu" id="dropdown-menu4">
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
<div class="dropdown-item">
|
<div class="dropdown-item">
|
||||||
<a href="#" hx-confirm="Are you sure?" hx-delete="delete">Delete</a>
|
<a href="#" hx-indicator="#loading" hx-confirm="Are you sure?" hx-delete="delete">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<div>
|
<div>
|
||||||
<form>
|
<form hx-boost="true" hx-indicator="#loading">
|
||||||
<input type="hidden" value="{{ search }}" name="search">
|
<input type="hidden" value="{{ search }}" name="search">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<div class="control has-icons-left has-icons-right">
|
<div class="control has-icons-left has-icons-right">
|
||||||
@ -82,7 +82,7 @@
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody hx-confirm="Are you sure?" hx-target="closest tr" hx-swap="outerHTML">
|
<tbody hx-confirm="Are you sure?" hx-target="closest tr" hx-indicator="#loading" hx-swap="outerHTML">
|
||||||
{% for entity in entities -%}
|
{% for entity in entities -%}
|
||||||
<tr>
|
<tr>
|
||||||
<td><input type="checkbox" name="ids" value="{{ entity.primary_key }}"></td>
|
<td><input type="checkbox" name="ids" value="{{ entity.primary_key }}"></td>
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
{% if category == "" %}
|
{% if category == "" %}
|
||||||
{% for menu_element in entities %}
|
{% for menu_element in entities %}
|
||||||
{% if menu_element.is_custom_handler %}
|
{% if menu_element.is_custom_handler %}
|
||||||
<a href="/admin/{{ menu_element.link }}" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name }}</a>
|
<a href="/admin/{{ menu_element.link }}" hx-boost="true" hx-indicator="#loading" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/admin/{{ menu_element.link }}/list" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name | title }}</a>
|
<a href="/admin/{{ menu_element.link }}/list" hx-boost="true" hx-indicator="#loading" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name | title }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -30,9 +30,9 @@
|
|||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
{% for menu_element in entities %}
|
{% for menu_element in entities %}
|
||||||
{% if menu_element.is_custom_handler %}
|
{% if menu_element.is_custom_handler %}
|
||||||
<a href="/admin/{{ menu_element.link }}" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name }}</a>
|
<a href="/admin/{{ menu_element.link }}" hx-boost="true" hx-indicator="#loading" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/admin/{{ menu_element.link }}/list" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name | title }}</a>
|
<a href="/admin/{{ menu_element.link }}/list" hx-boost="true" hx-indicator="#loading" class="navbar-item {% if entity_name and entity_name == menu_element.name %}is-active{% endif %}">{{ menu_element.name | title }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -46,11 +46,11 @@
|
|||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
{% if enable_auth %}
|
{% if enable_auth %}
|
||||||
{% if user_is_logged_in %}
|
{% if user_is_logged_in %}
|
||||||
<a href="{{ logout_link }}" class="button is-light">
|
<a href="{{ logout_link }}" hx-boost="true" hx-indicator="#loading" class="button is-light">
|
||||||
Log out
|
Log out
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ login_link }}" class="button is-light">
|
<a href="{{ login_link }}" hx-boost="true" hx-indicator="#loading" class="button is-light">
|
||||||
Log in
|
Log in
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -79,7 +79,7 @@ async fn create_post_from_plaintext<
|
|||||||
text: String,
|
text: String,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let model = ActixAdminModel::from(text);
|
let model = ActixAdminModel::from(text);
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, None).await
|
create_or_edit_post::<T, E>(&session, &data, Ok(model), None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn edit_post_from_plaintext<
|
async fn edit_post_from_plaintext<
|
||||||
@ -92,5 +92,5 @@ async fn edit_post_from_plaintext<
|
|||||||
id: web::Path<i32>,
|
id: web::Path<i32>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let model = ActixAdminModel::from(text);
|
let model = ActixAdminModel::from(text);
|
||||||
create_or_edit_post::<T, E>(&session, &data, model, Some(id.into_inner())).await
|
create_or_edit_post::<T, E>(&session, &data, Ok(model), Some(id.into_inner())).await
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user