add dropdown for custom filters

This commit is contained in:
Manuel Gugger 2023-07-04 19:49:26 +02:00
parent cf9b8f0c26
commit d95c84c2c6
12 changed files with 141 additions and 32 deletions

View File

@ -2,7 +2,7 @@
name = "actix-admin"
description = "An admin interface for actix-web"
license = "MIT OR Apache-2.0"
version = "0.4.0"
version = "0.5.0"
repository = "https://github.com/mgugger/actix-admin"
edition = "2021"
exclude = [
@ -32,7 +32,7 @@ itertools = "^0.10.5"
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" }
actix-admin-macros = { version = "0.5.0", path = "actix_admin_macros" }
derive_more = "0.99.17"
regex = "1.8.4"
urlencoding = "2.1.2"

View File

@ -3,7 +3,7 @@ name = "actix-admin-macros"
description = "macros to be used with actix-admin crate"
license = "MIT OR Apache-2.0"
repository = "https://github.com/mgugger/actix-admin"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
exclude = [
"tests/*"

View File

@ -98,13 +98,22 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
Ok(model)
}
async fn get_viewmodel_filter() -> HashMap<String, ActixAdminViewModelFilter> {
Entity::get_filter().iter().map(|f|
(f.name.to_string(), ActixAdminViewModelFilter {
name: f.name.to_string(),
value: None
})
).collect()
async fn get_viewmodel_filter(db: &DatabaseConnection) -> HashMap<String, ActixAdminViewModelFilter> {
let mut hashmap: HashMap<String, ActixAdminViewModelFilter> = HashMap::new();
for filter in Entity::get_filter() {
hashmap.insert(
filter.name.to_string(),
ActixAdminViewModelFilter {
name: filter.name.to_string(),
value: None,
values: Entity::get_filter_values(&filter, db).await,
filter_type: Some(filter.filter_type)
}
);
};
hashmap
}
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError> {

View File

@ -24,7 +24,7 @@ Check the [examples](https://github.com/mgugger/actix-admin/tree/main/examples)
[package]
name = "actix-admin-example"
description = "An admin interface for actix-web"
version = "0.4.0"
version = "0.5.0"
edition = "2021"
[[bin]]
@ -40,6 +40,6 @@ chrono = "0.4.23"
tera = "^1.17.1"
serde = "^1.0.152"
serde_derive = "^1.0.152"
actix-admin = { version = "0.4.0", path = "../../" }
actix-admin = { version = "0.5.0", path = "../../" }
regex = "1.7.1"
```

View File

@ -0,0 +1,8 @@
---
title: "Custom Filters"
date: 2023-01-17T11:44:56+01:00
draft: false
weight: 6
---
# Custom Filters

View File

@ -12,7 +12,7 @@ weight: 1
Cargo.toml:
```cargo
[dependencies]
actix-admin = "0.4.0"
actix-admin = "0.5.0"
```
## Build the Actix-Admin Configuration
@ -50,7 +50,7 @@ let app = App::new()
.app_data(web::Data::new(conn.clone()))
.app_data(web::Data::new(actix_admin_builder.get_actix_admin()))
.service(
actix_admin_builder.get_scope::<AppState>()
actix_admin_builder.get_scope()
)
.wrap(middleware::Logger::default())
```

View File

@ -1,7 +1,7 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use actix_admin::{prelude::*};
use super::Post;
use super::{Post, post};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel)]
#[sea_orm(table_name = "comment")]
@ -51,22 +51,61 @@ impl ActixAdminModelValidationTrait<ActiveModel> for Entity {
}
}
#[async_trait]
impl ActixAdminModelFilterTrait<Entity> for Entity {
fn get_filter() -> Vec<ActixAdminModelFilter<Entity>> {
vec![
ActixAdminModelFilter::<Entity> {
name: "Id".to_string(),
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::Id.eq(val)))
}
},
ActixAdminModelFilter::<Entity> {
name: "User".to_string(),
filter_type: ActixAdminModelFilterType::Text,
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::User.eq(val)))
}
},
values: None
},
ActixAdminModelFilter::<Entity> {
name: "Insert Date After".to_string(),
filter_type: ActixAdminModelFilterType::DateTime,
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::InsertDate.gte(val)))
},
values: None
},
ActixAdminModelFilter::<Entity> {
name: "Insert Date After".to_string(),
filter_type: ActixAdminModelFilterType::DateTime,
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::InsertDate.gte(val)))
},
values: None
},
ActixAdminModelFilter::<Entity> {
name: "Is Visible".to_string(),
filter_type: ActixAdminModelFilterType::Checkbox,
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::IsVisible.eq(val)))
},
values: None
},
ActixAdminModelFilter::<Entity> {
name: "Post".to_string(),
filter_type: ActixAdminModelFilterType::SelectList,
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
q.apply_if(v, | query, val: String| query.filter(Column::PostId.eq(val)))
},
values: None
}
]
}
async fn get_filter_values(filter: &ActixAdminModelFilter<Entity>, db: &DatabaseConnection) -> Option<Vec<(String, String)>> {
match filter.name.as_str() {
"Post" => Some({
Post::find().order_by_asc(post::Column::Id).all(db).await.unwrap()
.iter().map(|p| (p.id.to_string(), p.title.to_string())).collect()
}),
_ => None
}
}
}

View File

@ -24,7 +24,7 @@ pub mod view_model;
pub mod prelude {
pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait};
pub use crate::model::{ActixAdminModel, ActixAdminModelTrait, ActixAdminModelValidationTrait, ActixAdminModelFilter, ActixAdminModelFilterTrait};
pub use crate::model::{ActixAdminModel, ActixAdminModelTrait, ActixAdminModelValidationTrait, ActixAdminModelFilter, ActixAdminModelFilterTrait, ActixAdminModelFilterType};
pub use crate::routes::{create_or_edit_post, get_admin_ctx, SortOrder};
pub use crate::view_model::{
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,

View File

@ -36,20 +36,37 @@ pub trait ActixAdminModelValidationTrait<T> {
pub struct ActixAdminModelFilter<E: EntityTrait> {
pub name: String,
pub filter: fn(sea_orm::Select<E>, Option<String>) -> sea_orm::Select<E>
pub filter_type: ActixAdminModelFilterType,
pub filter: fn(sea_orm::Select<E>, Option<String>) -> sea_orm::Select<E>,
pub values: Option<Vec<(String, String)>>
}
#[derive(Clone, Debug, Serialize)]
pub enum ActixAdminModelFilterType {
Text,
SelectList,
Date,
DateTime,
Checkbox
}
#[async_trait]
pub trait ActixAdminModelFilterTrait<E: EntityTrait> {
fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
Vec::new()
}
async fn get_filter_values(_filter: &ActixAdminModelFilter<E>, _db: &DatabaseConnection)-> Option<Vec<(String, String)>> {
None
}
}
impl<T: EntityTrait> From<ActixAdminModelFilter<T>> for ActixAdminViewModelFilter {
fn from(filter: ActixAdminModelFilter<T>) -> Self {
ActixAdminViewModelFilter {
name: filter.name,
value: None
value: None,
values: None,
filter_type: Some(filter.filter_type)
}
}
}

View File

@ -90,6 +90,8 @@ pub async fn list<E: ActixAdminViewModelTrait>(
let af = ActixAdminViewModelFilter {
name: kv.next().unwrap().strip_prefix("filter_").unwrap_or_default().to_string(),
value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
values: None,
filter_type: None
};
af
}).collect();
@ -143,7 +145,7 @@ pub async fn list<E: ActixAdminViewModelTrait>(
ctx.insert("notifications", &notifications);
ctx.insert("entities_per_page", &entities_per_page);
ctx.insert("render_partial", &render_partial);
ctx.insert("viewmodel_filter", &E::get_viewmodel_filter().await);
ctx.insert("viewmodel_filter", &E::get_viewmodel_filter(&db).await);
ctx.insert(
"view_model",
&ActixAdminViewModelSerializable::from(view_model.clone()),

View File

@ -7,11 +7,42 @@
<ul class="menu-list">
{% for key, value in viewmodel_filter %}
<li>
<div class="field">
<div class="field mt-3">
<label class="label">{{key}}</label>
{% if value.filter_type == "Text" %}
<div class="control">
<input class="input" type="text" placeholder="" name="filter_{{key}}">
<input class="input" value="{{ value.value }}" type="text" placeholder="" name="filter_{{key}}">
</div>
{% elif value.filter_type == "DateTime" %}
<div class="control">
<input class="input" value="{{ value.value }}" type="datetime-local" placeholder="" name="filter_{{key}}">
</div>
{% elif value.filter_type == "Checkbox" %}
<div class="select is-fullwidth">
<select name="filter_{{key}}" id="filter_{{key}}">
<option value=""></option>
<option value="1">&#10003;</option>
<option value="0">&#10060;</option>
</select>
</div>
{% elif value.filter_type == "Date" %}
<div class="control">
<input class="input" type="date" placeholder="" name="filter_{{key}}">
</div>
{% elif value.filter_type == "SelectList" %}
<div class="select is-fullwidth">
<select name="filter_{{key}}" id="filter_{{key}}">
<option value=""></option>
{% for selectval in value.values %}
<option value="{{ selectval[0] }}">{{ selectval[1] }}</option>
{% endfor %}
</select>
</div>
{% else %}
<div class="control">
<input class="input" value="{{ value.value }}" type="text" placeholder="" name="filter_{{key}}">
</div>
{% endif %}
</div>
</li>
{% endfor %}
@ -59,7 +90,8 @@
</div>
</div>
<form id="search_form" action="/admin/{{ entity_name }}/list" hx-boost="true" hx-indicator="#loading"
hx-target="#{{ entity_name }}table" hx-trigger="reload_table from:#entities_per_page" hx-include="[id='filter_form']">
hx-target="#{{ entity_name }}table" hx-trigger="reload_table from:#entities_per_page"
hx-include="[id='filter_form']">
<input type="hidden" id="sort_by" name="sort_by" value="{{ sort_by }}">
<input type="hidden" id="sort_order" name="sort_order" value="{{ sort_order }}">
<input type="hidden" name="page" value="{{ page }}">

View File

@ -3,7 +3,7 @@ use regex::Regex;
use sea_orm::DatabaseConnection;
use serde_derive::{Serialize, Deserialize};
use std::collections::HashMap;
use crate::{ActixAdminModel, SortOrder};
use crate::{ActixAdminModel, SortOrder, model::ActixAdminModelFilterType};
use actix_session::{Session};
use std::convert::From;
use crate::ActixAdminError;
@ -26,7 +26,7 @@ pub trait ActixAdminViewModelTrait {
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError>;
async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> Result<ActixAdminModel, ActixAdminError>;
async fn get_select_lists(db: &DatabaseConnection) -> Result<HashMap<String, Vec<(String, String)>>, ActixAdminError>;
async fn get_viewmodel_filter() -> HashMap<String, ActixAdminViewModelFilter>;
async fn get_viewmodel_filter(db: &DatabaseConnection) -> HashMap<String, ActixAdminViewModelFilter>;
fn validate_entity(model: &mut ActixAdminModel);
fn get_entity_name() -> String;
@ -58,7 +58,9 @@ pub struct ActixAdminViewModelSerializable {
#[derive(Clone, Debug, Serialize)]
pub struct ActixAdminViewModelFilter {
pub name: String,
pub value: Option<String>
pub value: Option<String>,
pub values: Option<Vec<(String, String)>>,
pub filter_type: Option<ActixAdminModelFilterType>
}
// TODO: better alternative to serialize only specific fields for ActixAdminViewModel