update sea-orm and fix empty list when no searchable conditions

This commit is contained in:
Manuel Gugger 2023-06-15 11:33:08 +02:00
parent 473147e80a
commit 223ad29266
8 changed files with 145 additions and 99 deletions

View File

@ -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"] }

View File

@ -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))
}

View File

@ -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"

View File

@ -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
}

View 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 {}

View File

@ -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
}

View File

@ -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();
}

View File

@ -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 }}",