update sea-orm and fix empty list when no searchable conditions
This commit is contained in:
parent
473147e80a
commit
223ad29266
20
Cargo.toml
20
Cargo.toml
@ -23,23 +23,23 @@ actix-web = "^4.3.1"
|
||||
actix-session = { version = "^0.7.2", features = [] }
|
||||
actix-multipart = "^0.6.0"
|
||||
actix-files = "^0.6.2"
|
||||
futures-util = "0.3.27"
|
||||
chrono = "0.4.24"
|
||||
tera = "^1.18.1"
|
||||
async-trait = "^0.1.67"
|
||||
futures-util = "0.3.28"
|
||||
chrono = "0.4.26"
|
||||
tera = "^1.19.0"
|
||||
async-trait = "^0.1.68"
|
||||
lazy_static = "^1.4.0"
|
||||
itertools = "^0.10.5"
|
||||
serde = "^1.0.158"
|
||||
serde_derive = "^1.0.158"
|
||||
sea-orm = { version = "^0.10.6", features = [], default-features = false }
|
||||
serde = "^1.0.164"
|
||||
serde_derive = "^1.0.164"
|
||||
sea-orm = { version = "^0.11.3", features = [], default-features = false }
|
||||
actix-admin-macros = { version = "0.4.0", path = "actix_admin_macros" }
|
||||
derive_more = "0.99.17"
|
||||
regex = "1.7.1"
|
||||
regex = "1.8.4"
|
||||
|
||||
[dev-dependencies]
|
||||
sea-orm = { version = "^0.10.6", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
sea-orm = { version = "^0.11.3", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
actix-rt = "2.8.0"
|
||||
azure_auth = { path = "./examples/azure_auth/azure_auth" }
|
||||
oauth2 = "4.3"
|
||||
oauth2 = "4.4.1"
|
||||
dotenv = "0.15"
|
||||
actix-session = { version = "0.7.2", features = ["cookie-session"] }
|
@ -15,12 +15,16 @@ mod attributes;
|
||||
mod model_fields;
|
||||
|
||||
#[proc_macro_derive(DeriveActixAdminEnumSelectList, attributes(actix_admin))]
|
||||
pub fn derive_actix_admin_enum_select_list(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn derive_actix_admin_enum_select_list(
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
get_select_list_from_enum(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DeriveActixAdminModelSelectList, attributes(actix_admin))]
|
||||
pub fn derive_actix_admin_model_select_list(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn derive_actix_admin_model_select_list(
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
get_select_list_from_model(input)
|
||||
}
|
||||
|
||||
@ -30,10 +34,10 @@ pub fn derive_actix_admin(_input: proc_macro::TokenStream) -> proc_macro::TokenS
|
||||
use std::convert::From;
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::{
|
||||
ActiveValue::Set,
|
||||
ConnectOptions,
|
||||
DatabaseConnection,
|
||||
entity::*,
|
||||
ActiveValue::Set,
|
||||
ConnectOptions,
|
||||
DatabaseConnection,
|
||||
entity::*,
|
||||
query::*,
|
||||
EntityTrait
|
||||
};
|
||||
@ -82,7 +86,7 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
||||
let custom_errors = Entity::validate(&active_model);
|
||||
model.custom_errors = custom_errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError> {
|
||||
let new_model = ActiveModel::from(model.clone());
|
||||
@ -108,8 +112,8 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
||||
Some(e) => {
|
||||
let mut entity: ActiveModel = e.into();
|
||||
#(#fields_for_edit_model);*;
|
||||
let entity: Model = entity.update(db).await?;
|
||||
Ok(model)
|
||||
let entity: Model = entity.update(db).await?;
|
||||
Ok(model)
|
||||
},
|
||||
_ => Err(ActixAdminError::EntityDoesNotExistError)
|
||||
}
|
||||
@ -143,28 +147,46 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
||||
pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let fields = get_fields_for_tokenstream(input);
|
||||
|
||||
let field_names = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.ident.to_string() });
|
||||
let field_html_input_type = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.html_input_type.to_string() });
|
||||
let field_list_regex_mask = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.list_regex_mask.to_string() });
|
||||
let field_select_list = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.select_list.to_string() });
|
||||
let is_option_list = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.is_option() });
|
||||
let field_names = get_fields_as_tokenstream(&fields, |model_field| -> String {
|
||||
model_field.ident.to_string()
|
||||
});
|
||||
let field_html_input_type = get_fields_as_tokenstream(&fields, |model_field| -> String {
|
||||
model_field.html_input_type.to_string()
|
||||
});
|
||||
let field_list_regex_mask = get_fields_as_tokenstream(&fields, |model_field| -> String {
|
||||
model_field.list_regex_mask.to_string()
|
||||
});
|
||||
let field_select_list = get_fields_as_tokenstream(&fields, |model_field| -> String {
|
||||
model_field.select_list.to_string()
|
||||
});
|
||||
let is_option_list =
|
||||
get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.is_option() });
|
||||
let fields_for_create_model = get_fields_for_create_model(&fields);
|
||||
let fields_for_from_model = get_fields_for_from_model(&fields);
|
||||
let field_for_primary_key = get_field_for_primary_key(&fields);
|
||||
let fields_for_validate_model = get_fields_for_validate_model(&fields);
|
||||
let fields_type_path = get_fields_as_tokenstream(&fields, |model_field| -> String { model_field.get_type_path_string() });
|
||||
let fields_textarea = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.textarea });
|
||||
let fields_file_upload = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.file_upload });
|
||||
let fields_type_path = get_fields_as_tokenstream(&fields, |model_field| -> String {
|
||||
model_field.get_type_path_string()
|
||||
});
|
||||
let fields_textarea =
|
||||
get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.textarea });
|
||||
let fields_file_upload =
|
||||
get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.file_upload });
|
||||
let fields_match_name_to_columns = get_match_name_to_column(&fields);
|
||||
let fields_list_sort_positions = get_fields_as_tokenstream(&fields, |model_field| -> usize { model_field.list_sort_position });
|
||||
let fields_list_hide_column = get_fields_as_tokenstream(&fields, |model_field| -> bool { model_field.list_hide_column });
|
||||
let fields_list_sort_positions = get_fields_as_tokenstream(&fields, |model_field| -> usize {
|
||||
model_field.list_sort_position
|
||||
});
|
||||
let fields_list_hide_column = get_fields_as_tokenstream(&fields, |model_field| -> bool {
|
||||
model_field.list_hide_column
|
||||
});
|
||||
let fields_searchable = get_actix_admin_fields_searchable(&fields);
|
||||
let has_searchable_fields = fields_searchable.len() > 0;
|
||||
|
||||
let expanded = quote! {
|
||||
actix_admin::prelude::lazy_static! {
|
||||
pub static ref ACTIX_ADMIN_VIEWMODEL_FIELDS: Vec<ActixAdminViewModelField> = {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
|
||||
let field_names = stringify!(
|
||||
#(#field_names),*
|
||||
).split(",")
|
||||
@ -207,9 +229,9 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
||||
let list_regex_masks = [
|
||||
#(#field_list_regex_mask),*
|
||||
];
|
||||
|
||||
|
||||
for (field_name, html_input_type, select_list, is_option_list, fields_type_path, is_textarea, is_file_upload, list_sort_position, list_hide_column, list_regex_mask) in actix_admin::prelude::izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths, fields_textareas, fields_fileupload, list_sort_positions, list_hide_columns, list_regex_masks) {
|
||||
|
||||
|
||||
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
||||
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
||||
let html_input_type = html_input_type.replace('"', "").replace(' ', "").to_string();
|
||||
@ -260,26 +282,28 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
||||
#[actix_admin::prelude::async_trait]
|
||||
impl ActixAdminModelTrait for Entity {
|
||||
async fn list_model(db: &DatabaseConnection, page: u64, posts_per_page: u64, search: &str, sort_by: &str, sort_order: &SortOrder) -> Result<(u64, Vec<ActixAdminModel>), ActixAdminError> {
|
||||
use sea_orm::{ query::* };
|
||||
|
||||
let sort_column = match sort_by {
|
||||
#(#fields_match_name_to_columns)*
|
||||
_ => panic!("Unknown column")
|
||||
};
|
||||
|
||||
let query = if sort_order.eq(&SortOrder::Asc) {
|
||||
|
||||
let mut query = if sort_order.eq(&SortOrder::Asc) {
|
||||
Entity::find().order_by_asc(sort_column)
|
||||
} else {
|
||||
Entity::find().order_by_desc(sort_column)
|
||||
};
|
||||
|
||||
let paginator = query
|
||||
if (#has_searchable_fields) {
|
||||
query = query
|
||||
.filter(
|
||||
Condition::any()
|
||||
#(#fields_searchable)*
|
||||
)
|
||||
.paginate(db, posts_per_page);
|
||||
}
|
||||
|
||||
let paginator = query.paginate(db, posts_per_page);
|
||||
let num_pages = paginator.num_pages().await?;
|
||||
|
||||
let mut model_entities = Vec::new();
|
||||
if (num_pages == 0) { return Ok((num_pages, model_entities)) };
|
||||
let entities = paginator
|
||||
@ -290,7 +314,7 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
|
||||
ActixAdminModel::from(entity)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Ok((num_pages, model_entities))
|
||||
}
|
||||
|
||||
|
@ -32,10 +32,10 @@ name = "actix-admin-example"
|
||||
path = "main.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "^4.2.1"
|
||||
actix-web = "^4.3.1"
|
||||
actix-rt = "2.7.0"
|
||||
actix-multipart = "^0.4.0"
|
||||
sea-orm = { version = "^0.11.1", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
sea-orm = { version = "^0.11.3", features = [ "sqlx-sqlite", "runtime-actix-native-tls", "macros" ], default-features = true }
|
||||
chrono = "0.4.23"
|
||||
tera = "^1.17.1"
|
||||
serde = "^1.0.152"
|
||||
|
@ -1,10 +1,12 @@
|
||||
// setup
|
||||
use sea_orm::sea_query::{ForeignKeyCreateStatement, ColumnDef, TableCreateStatement};
|
||||
use sea_orm::{Set, EntityTrait, error::*, sea_query, ConnectionTrait, DbConn, ExecResult};
|
||||
use sea_orm::{Set, EntityTrait, error::*, sea_query, ConnectionTrait, DbConn, ExecResult };
|
||||
pub mod comment;
|
||||
pub mod post;
|
||||
pub mod user;
|
||||
pub use comment::Entity as Comment;
|
||||
pub use post::Entity as Post;
|
||||
pub use user::Entity as User;
|
||||
use chrono::{Local, Duration, DurationRound};
|
||||
use sea_orm::prelude::Decimal;
|
||||
|
||||
@ -61,7 +63,22 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
)
|
||||
.to_owned();
|
||||
|
||||
let res = create_table(db, &stmt).await;
|
||||
let _res = create_table(db, &stmt).await;
|
||||
|
||||
let stmt = sea_query::Table::create()
|
||||
.table(user::Entity)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(post::Column::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(user::Column::Name).string().not_null())
|
||||
.to_owned();
|
||||
|
||||
let _res = create_table(db, &stmt).await;
|
||||
|
||||
for i in 1..1000 {
|
||||
let row = post::ActiveModel {
|
||||
@ -87,5 +104,13 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
let _res = Comment::insert(row).exec(db).await;
|
||||
}
|
||||
|
||||
res
|
||||
for i in 1..100 {
|
||||
let row = user::ActiveModel {
|
||||
name: Set(format!("user {}", i)),
|
||||
..Default::default()
|
||||
};
|
||||
let _res = User::insert(row).exec(db).await;
|
||||
}
|
||||
|
||||
_res
|
||||
}
|
||||
|
20
examples/basic/entity/user.rs
Normal file
20
examples/basic/entity/user.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use actix_admin::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
#[serde(skip_deserializing)]
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
@ -5,7 +5,7 @@ use actix_web::{web, App, HttpServer, middleware};
|
||||
use sea_orm::{ConnectOptions, DatabaseConnection};
|
||||
use std::time::Duration;
|
||||
mod entity;
|
||||
use entity::{Post, Comment};
|
||||
use entity::{Post, Comment, User};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
@ -33,13 +33,14 @@ fn create_actix_admin_builder() -> ActixAdminBuilder {
|
||||
|
||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||
|
||||
|
||||
let post_view_model = ActixAdminViewModel::from(Post);
|
||||
admin_builder.add_entity::<AppState, Post>(&post_view_model);
|
||||
|
||||
let some_category = "Groupings";
|
||||
let some_category = "Group";
|
||||
let comment_view_model = ActixAdminViewModel::from(Comment);
|
||||
admin_builder.add_entity_to_category::<AppState, Comment>(&comment_view_model, some_category);
|
||||
let user_view_model = ActixAdminViewModel::from(User);
|
||||
admin_builder.add_entity_to_category::<AppState, User>(&user_view_model, some_category);
|
||||
|
||||
admin_builder
|
||||
}
|
||||
|
@ -39,8 +39,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
function sort_by(column, order) {
|
||||
document.getElementsByName("sort_order").forEach((e) => e.value = order);
|
||||
function sort_by(column) {
|
||||
console.log(column);
|
||||
current_sort_order = document.getElementsByName("sort_order")[0].value;
|
||||
if (current_sort_order == "Asc") {
|
||||
document.getElementsByName("sort_order").forEach((e) => e.value = "Desc");
|
||||
} else {
|
||||
document.getElementsByName("sort_order").forEach((e) => e.value = "Asc");
|
||||
}
|
||||
document.getElementsByName("sort_by").forEach((e) => e.value = column);
|
||||
document.getElementById('table_form').requestSubmit();
|
||||
}
|
||||
|
@ -71,63 +71,33 @@
|
||||
<input type="hidden" name="entities_per_page" value="{{ entities_per_page }}">
|
||||
<input type="hidden" name="search" value="{{ search }}">
|
||||
<input type="hidden" name="page" value="{{ page }}">
|
||||
<table class="table is-relative is-fullwidth is-hoverable is-striped">
|
||||
<table class="table is-relative is-narrow is-fullwidth is-hoverable is-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" onclick="checkAll(this)">
|
||||
</th>
|
||||
<th>
|
||||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<div class="navbar-item has-dropdown" aria-haspopup="true"
|
||||
aria-controls="dropdown-menu4">
|
||||
<a class="navbar-link">
|
||||
<span>{{ view_model.primary_key | split(pat="_") | join(sep=" ") | title
|
||||
}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-item is-clickable"
|
||||
onclick="sort_by('{{ view_model.primary_key }}', 'Desc');">
|
||||
Sort by Desc <i class="ml-1 fa-solid fa-caret-down"></i>
|
||||
</div>
|
||||
<div class="dropdown-item is-clickable"
|
||||
onclick="sort_by('{{ view_model.primary_key }}', 'Asc');">
|
||||
Sort by Asc <i class="ml-1 fa-solid fa-caret-up"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th onclick="sort_by('{{ view_model.primary_key }}');" class="is-clickable">{{
|
||||
view_model.primary_key | title }}
|
||||
{% if sort_by == view_model.primary_key %}
|
||||
{% if sort_order == "Asc" %}
|
||||
<i class="ml-1 fa-solid fa-caret-up"></i>
|
||||
{% elif sort_order == "Desc" %}
|
||||
<i class="ml-1 fa-solid fa-caret-down"></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% for model_field in view_model.fields | filter(attribute="list_hide_column", value=false) |
|
||||
sort(attribute="list_sort_position") -%}
|
||||
<th>
|
||||
<div class="dropdown is-hoverable">
|
||||
<div class="dropdown-trigger">
|
||||
<div class="navbar-item has-dropdown" aria-haspopup="true"
|
||||
aria-controls="dropdown-menu4">
|
||||
<a class="navbar-link">
|
||||
<span>{{ model_field.field_name | split(pat="_") | join(sep=" ") | title
|
||||
}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu4" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<div class="dropdown-item is-clickable"
|
||||
onclick="sort_by('{{ model_field.field_name }}', 'Desc');">
|
||||
Sort by Desc <i class="ml-1 fa-solid fa-caret-down"></i>
|
||||
</div>
|
||||
<div class="dropdown-item is-clickable"
|
||||
onclick="sort_by('{{ model_field.field_name }}', 'Asc');">
|
||||
Sort by Asc <i class="ml-1 fa-solid fa-caret-up"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th onclick="sort_by('{{ model_field.field_name }}');" class="is-clickable">{{
|
||||
model_field.field_name | split(pat="_") | join(sep=" ") | title }}
|
||||
{% if sort_by == model_field.field_name %}
|
||||
{% if sort_order == "Asc" %}
|
||||
<i class="ml-1 fa-solid fa-caret-up"></i>
|
||||
{% elif sort_order == "Desc" %}
|
||||
<i class="ml-1 fa-solid fa-caret-down"></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
{%- endfor %}
|
||||
<th>
|
||||
@ -164,7 +134,7 @@
|
||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
<td>
|
||||
<td class="has-text-right">
|
||||
<a hx-target="body" href="/admin/{{ entity_name }}/edit/{{ entity.primary_key }}" hx-vals='{
|
||||
"page" : "{{ page }}",
|
||||
"entities_per_page" : "{{ entities_per_page }}",
|
||||
|
Loading…
Reference in New Issue
Block a user