add dropdown for custom filters
This commit is contained in:
parent
cf9b8f0c26
commit
d95c84c2c6
@ -2,7 +2,7 @@
|
|||||||
name = "actix-admin"
|
name = "actix-admin"
|
||||||
description = "An admin interface for actix-web"
|
description = "An admin interface for actix-web"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
repository = "https://github.com/mgugger/actix-admin"
|
repository = "https://github.com/mgugger/actix-admin"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
exclude = [
|
exclude = [
|
||||||
@ -32,7 +32,7 @@ itertools = "^0.10.5"
|
|||||||
serde = "^1.0.164"
|
serde = "^1.0.164"
|
||||||
serde_derive = "^1.0.164"
|
serde_derive = "^1.0.164"
|
||||||
sea-orm = { version = "^0.11.3", features = [], default-features = false }
|
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"
|
derive_more = "0.99.17"
|
||||||
regex = "1.8.4"
|
regex = "1.8.4"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
|
@ -3,7 +3,7 @@ name = "actix-admin-macros"
|
|||||||
description = "macros to be used with actix-admin crate"
|
description = "macros to be used with actix-admin crate"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/mgugger/actix-admin"
|
repository = "https://github.com/mgugger/actix-admin"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
exclude = [
|
exclude = [
|
||||||
"tests/*"
|
"tests/*"
|
||||||
|
@ -98,13 +98,22 @@ pub fn derive_actix_admin_view_model(input: proc_macro::TokenStream) -> proc_mac
|
|||||||
Ok(model)
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_viewmodel_filter() -> HashMap<String, ActixAdminViewModelFilter> {
|
async fn get_viewmodel_filter(db: &DatabaseConnection) -> HashMap<String, ActixAdminViewModelFilter> {
|
||||||
Entity::get_filter().iter().map(|f|
|
let mut hashmap: HashMap<String, ActixAdminViewModelFilter> = HashMap::new();
|
||||||
(f.name.to_string(), ActixAdminViewModelFilter {
|
|
||||||
name: f.name.to_string(),
|
for filter in Entity::get_filter() {
|
||||||
value: None
|
hashmap.insert(
|
||||||
})
|
filter.name.to_string(),
|
||||||
).collect()
|
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> {
|
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError> {
|
||||||
|
@ -24,7 +24,7 @@ Check the [examples](https://github.com/mgugger/actix-admin/tree/main/examples)
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-admin-example"
|
name = "actix-admin-example"
|
||||||
description = "An admin interface for actix-web"
|
description = "An admin interface for actix-web"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
@ -40,6 +40,6 @@ chrono = "0.4.23"
|
|||||||
tera = "^1.17.1"
|
tera = "^1.17.1"
|
||||||
serde = "^1.0.152"
|
serde = "^1.0.152"
|
||||||
serde_derive = "^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"
|
regex = "1.7.1"
|
||||||
```
|
```
|
8
docs/content/docs/custom-filters.md
Normal file
8
docs/content/docs/custom-filters.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Custom Filters"
|
||||||
|
date: 2023-01-17T11:44:56+01:00
|
||||||
|
draft: false
|
||||||
|
weight: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
# Custom Filters
|
@ -12,7 +12,7 @@ weight: 1
|
|||||||
Cargo.toml:
|
Cargo.toml:
|
||||||
```cargo
|
```cargo
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-admin = "0.4.0"
|
actix-admin = "0.5.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build the Actix-Admin Configuration
|
## 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(conn.clone()))
|
||||||
.app_data(web::Data::new(actix_admin_builder.get_actix_admin()))
|
.app_data(web::Data::new(actix_admin_builder.get_actix_admin()))
|
||||||
.service(
|
.service(
|
||||||
actix_admin_builder.get_scope::<AppState>()
|
actix_admin_builder.get_scope()
|
||||||
)
|
)
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
```
|
```
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use actix_admin::{prelude::*};
|
use actix_admin::{prelude::*};
|
||||||
use super::Post;
|
use super::{Post, post};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdmin, DeriveActixAdminModel, DeriveActixAdminViewModel)]
|
||||||
#[sea_orm(table_name = "comment")]
|
#[sea_orm(table_name = "comment")]
|
||||||
@ -51,22 +51,61 @@ impl ActixAdminModelValidationTrait<ActiveModel> for Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl ActixAdminModelFilterTrait<Entity> for Entity {
|
impl ActixAdminModelFilterTrait<Entity> for Entity {
|
||||||
fn get_filter() -> Vec<ActixAdminModelFilter<Entity>> {
|
fn get_filter() -> Vec<ActixAdminModelFilter<Entity>> {
|
||||||
vec![
|
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> {
|
ActixAdminModelFilter::<Entity> {
|
||||||
name: "User".to_string(),
|
name: "User".to_string(),
|
||||||
|
filter_type: ActixAdminModelFilterType::Text,
|
||||||
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
|
filter: |q: sea_orm::Select<Entity>, v| -> sea_orm::Select<Entity> {
|
||||||
q.apply_if(v, | query, val: String| query.filter(Column::User.eq(val)))
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub mod view_model;
|
|||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::builder::{ActixAdminBuilder, ActixAdminBuilderTrait};
|
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::routes::{create_or_edit_post, get_admin_ctx, SortOrder};
|
||||||
pub use crate::view_model::{
|
pub use crate::view_model::{
|
||||||
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
ActixAdminViewModel, ActixAdminViewModelField, ActixAdminViewModelFieldType,
|
||||||
|
21
src/model.rs
21
src/model.rs
@ -36,20 +36,37 @@ pub trait ActixAdminModelValidationTrait<T> {
|
|||||||
|
|
||||||
pub struct ActixAdminModelFilter<E: EntityTrait> {
|
pub struct ActixAdminModelFilter<E: EntityTrait> {
|
||||||
pub name: String,
|
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> {
|
pub trait ActixAdminModelFilterTrait<E: EntityTrait> {
|
||||||
fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
|
fn get_filter() -> Vec<ActixAdminModelFilter<E>> {
|
||||||
Vec::new()
|
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 {
|
impl<T: EntityTrait> From<ActixAdminModelFilter<T>> for ActixAdminViewModelFilter {
|
||||||
fn from(filter: ActixAdminModelFilter<T>) -> Self {
|
fn from(filter: ActixAdminModelFilter<T>) -> Self {
|
||||||
ActixAdminViewModelFilter {
|
ActixAdminViewModelFilter {
|
||||||
name: filter.name,
|
name: filter.name,
|
||||||
value: None
|
value: None,
|
||||||
|
values: None,
|
||||||
|
filter_type: Some(filter.filter_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,8 @@ pub async fn list<E: ActixAdminViewModelTrait>(
|
|||||||
let af = ActixAdminViewModelFilter {
|
let af = ActixAdminViewModelFilter {
|
||||||
name: kv.next().unwrap().strip_prefix("filter_").unwrap_or_default().to_string(),
|
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()),
|
value: kv.next().map(|s| s.to_string()).filter(|f| !f.is_empty()),
|
||||||
|
values: None,
|
||||||
|
filter_type: None
|
||||||
};
|
};
|
||||||
af
|
af
|
||||||
}).collect();
|
}).collect();
|
||||||
@ -143,7 +145,7 @@ pub async fn list<E: ActixAdminViewModelTrait>(
|
|||||||
ctx.insert("notifications", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
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("viewmodel_filter", &E::get_viewmodel_filter().await);
|
ctx.insert("viewmodel_filter", &E::get_viewmodel_filter(&db).await);
|
||||||
ctx.insert(
|
ctx.insert(
|
||||||
"view_model",
|
"view_model",
|
||||||
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
&ActixAdminViewModelSerializable::from(view_model.clone()),
|
||||||
|
@ -7,11 +7,42 @@
|
|||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
{% for key, value in viewmodel_filter %}
|
{% for key, value in viewmodel_filter %}
|
||||||
<li>
|
<li>
|
||||||
<div class="field">
|
<div class="field mt-3">
|
||||||
<label class="label">{{key}}</label>
|
<label class="label">{{key}}</label>
|
||||||
|
{% if value.filter_type == "Text" %}
|
||||||
<div class="control">
|
<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>
|
</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">✓</option>
|
||||||
|
<option value="0">❌</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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -59,7 +90,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="search_form" action="/admin/{{ entity_name }}/list" hx-boost="true" hx-indicator="#loading"
|
<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_by" name="sort_by" value="{{ sort_by }}">
|
||||||
<input type="hidden" id="sort_order" name="sort_order" value="{{ sort_order }}">
|
<input type="hidden" id="sort_order" name="sort_order" value="{{ sort_order }}">
|
||||||
<input type="hidden" name="page" value="{{ page }}">
|
<input type="hidden" name="page" value="{{ page }}">
|
||||||
|
@ -3,7 +3,7 @@ use regex::Regex;
|
|||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use serde_derive::{Serialize, Deserialize};
|
use serde_derive::{Serialize, Deserialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::{ActixAdminModel, SortOrder};
|
use crate::{ActixAdminModel, SortOrder, model::ActixAdminModelFilterType};
|
||||||
use actix_session::{Session};
|
use actix_session::{Session};
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use crate::ActixAdminError;
|
use crate::ActixAdminError;
|
||||||
@ -26,7 +26,7 @@ pub trait ActixAdminViewModelTrait {
|
|||||||
async fn get_entity(db: &DatabaseConnection, id: i32) -> Result<ActixAdminModel, ActixAdminError>;
|
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 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_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 validate_entity(model: &mut ActixAdminModel);
|
||||||
|
|
||||||
fn get_entity_name() -> String;
|
fn get_entity_name() -> String;
|
||||||
@ -58,7 +58,9 @@ pub struct ActixAdminViewModelSerializable {
|
|||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct ActixAdminViewModelFilter {
|
pub struct ActixAdminViewModelFilter {
|
||||||
pub name: String,
|
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
|
// TODO: better alternative to serialize only specific fields for ActixAdminViewModel
|
||||||
|
Loading…
Reference in New Issue
Block a user