add search with 'contains'
This commit is contained in:
parent
09b9845b3d
commit
a8ae8eff55
@ -12,7 +12,8 @@ pub mod derive_attr {
|
|||||||
pub struct ActixAdmin {
|
pub struct ActixAdmin {
|
||||||
pub primary_key: Option<()>,
|
pub primary_key: Option<()>,
|
||||||
pub html_input_type: Option<syn::LitStr>,
|
pub html_input_type: Option<syn::LitStr>,
|
||||||
pub select_list: Option<syn::LitStr>
|
pub select_list: Option<syn::LitStr>,
|
||||||
|
pub searchable: Option<()>
|
||||||
//pub inner_type: Option<syn::Type>,
|
//pub inner_type: Option<syn::Type>,
|
||||||
|
|
||||||
// Anything that implements `syn::parse::Parse` is supported.
|
// Anything that implements `syn::parse::Parse` is supported.
|
||||||
|
@ -13,7 +13,8 @@ use struct_fields::{
|
|||||||
get_primary_key_field_name,
|
get_primary_key_field_name,
|
||||||
get_actix_admin_fields_select_list,
|
get_actix_admin_fields_select_list,
|
||||||
get_actix_admin_fields_is_option_list,
|
get_actix_admin_fields_is_option_list,
|
||||||
get_fields_for_validate_model
|
get_fields_for_validate_model,
|
||||||
|
get_actix_admin_fields_searchable
|
||||||
};
|
};
|
||||||
|
|
||||||
mod selectlist_fields;
|
mod selectlist_fields;
|
||||||
@ -44,6 +45,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
|||||||
let fields_for_from_model = get_fields_for_from_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 field_for_primary_key = get_field_for_primary_key(&fields);
|
||||||
let fields_for_validate_model = get_fields_for_validate_model(&fields);
|
let fields_for_validate_model = get_fields_for_validate_model(&fields);
|
||||||
|
let fields_searchable = get_actix_admin_fields_searchable(&fields);
|
||||||
|
|
||||||
let select_lists = get_select_lists(&fields);
|
let select_lists = get_select_lists(&fields);
|
||||||
|
|
||||||
@ -96,8 +98,8 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
|||||||
|
|
||||||
#[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) -> (usize, Vec<ActixAdminModel>) {
|
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize, search: &String) -> (usize, Vec<ActixAdminModel>) {
|
||||||
let entities = Entity::list_model(db, page, entities_per_page).await;
|
let entities = Entity::list_model(db, page, entities_per_page, search).await;
|
||||||
entities
|
entities
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +165,13 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActixAdminModelTrait for Entity {
|
impl ActixAdminModelTrait for Entity {
|
||||||
async fn list_model(db: &DatabaseConnection, page: usize, posts_per_page: usize) -> (usize, Vec<ActixAdminModel>) {
|
async fn list_model(db: &DatabaseConnection, page: usize, posts_per_page: usize, search: &String) -> (usize, Vec<ActixAdminModel>) {
|
||||||
use sea_orm::{ query::* };
|
use sea_orm::{ query::* };
|
||||||
let paginator = Entity::find()
|
let paginator = Entity::find()
|
||||||
|
.filter(
|
||||||
|
Condition::any()
|
||||||
|
#(#fields_searchable)*
|
||||||
|
)
|
||||||
.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.ok().unwrap();
|
||||||
|
@ -11,7 +11,8 @@ pub struct ModelField {
|
|||||||
pub inner_type: Option<Type>,
|
pub inner_type: Option<Type>,
|
||||||
pub primary_key: bool,
|
pub primary_key: bool,
|
||||||
pub html_input_type: String,
|
pub html_input_type: String,
|
||||||
pub select_list: String
|
pub select_list: String,
|
||||||
|
pub searchable: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelField {
|
impl ModelField {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::attributes::derive_attr;
|
use crate::attributes::derive_attr;
|
||||||
use crate::model_fields::ModelField;
|
use crate::model_fields::ModelField;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{DeriveInput, Fields, LitStr};
|
use syn::{DeriveInput, Fields, LitStr, Ident};
|
||||||
|
|
||||||
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<ModelField> {
|
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<ModelField> {
|
||||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||||
@ -16,6 +16,10 @@ pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::V
|
|||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn capitalize_first_letter(s: &str) -> String {
|
||||||
|
s[0..1].to_uppercase() + &s[1..]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
@ -31,6 +35,9 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
let is_primary_key = actix_admin_attr
|
let is_primary_key = actix_admin_attr
|
||||||
.clone()
|
.clone()
|
||||||
.map_or(false, |attr| attr.primary_key.is_some());
|
.map_or(false, |attr| attr.primary_key.is_some());
|
||||||
|
let is_searchable = actix_admin_attr
|
||||||
|
.clone()
|
||||||
|
.map_or(false, |attr| attr.searchable.is_some());
|
||||||
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
let select_list = actix_admin_attr.clone().map_or("".to_string(), |attr| {
|
||||||
attr.select_list.map_or("".to_string(), |attr_field| {
|
attr.select_list.map_or("".to_string(), |attr_field| {
|
||||||
(LitStr::from(attr_field)).value()
|
(LitStr::from(attr_field)).value()
|
||||||
@ -51,6 +58,7 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
|||||||
primary_key: is_primary_key,
|
primary_key: is_primary_key,
|
||||||
html_input_type: html_input_type,
|
html_input_type: html_input_type,
|
||||||
select_list: select_list,
|
select_list: select_list,
|
||||||
|
searchable: is_searchable
|
||||||
};
|
};
|
||||||
Some(model_field)
|
Some(model_field)
|
||||||
} else {
|
} else {
|
||||||
@ -146,6 +154,20 @@ pub fn get_actix_admin_fields_html_input(fields: &Vec<ModelField>) -> Vec<TokenS
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_actix_admin_fields_searchable(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.filter(|model_field| model_field.searchable)
|
||||||
|
.map(|model_field| {
|
||||||
|
let column_name = format!("{}", capitalize_first_letter(&model_field.ident.to_string()));
|
||||||
|
let column_ident = Ident::new(&column_name, Span::call_site());
|
||||||
|
quote! {
|
||||||
|
.add(Column::#column_ident.contains(&search))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_actix_admin_fields_select_list(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
pub fn get_actix_admin_fields_select_list(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -10,6 +10,7 @@ pub trait ActixAdminModelTrait {
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
page: usize,
|
page: usize,
|
||||||
posts_per_page: usize,
|
posts_per_page: usize,
|
||||||
|
search: &String
|
||||||
) -> (usize, Vec<ActixAdminModel>);
|
) -> (usize, Vec<ActixAdminModel>);
|
||||||
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
||||||
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String>;
|
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String>;
|
||||||
|
@ -35,9 +35,10 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
|||||||
.entities_per_page
|
.entities_per_page
|
||||||
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
|
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
|
||||||
let render_partial = params.render_partial.unwrap_or(false);
|
let render_partial = params.render_partial.unwrap_or(false);
|
||||||
|
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).await;
|
let result: (usize, Vec<ActixAdminModel>) = E::list(db, page, entities_per_page, &search).await;
|
||||||
let entities = result.1;
|
let entities = result.1;
|
||||||
let num_pages = result.0;
|
let num_pages = result.0;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ pub trait ActixAdminViewModelTrait {
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
page: usize,
|
page: usize,
|
||||||
entities_per_page: usize,
|
entities_per_page: usize,
|
||||||
|
search: &String
|
||||||
) -> (usize, Vec<ActixAdminModel>);
|
) -> (usize, Vec<ActixAdminModel>);
|
||||||
|
|
||||||
// TODO: Replace return value with proper Result Type containing Ok or Err
|
// TODO: Replace return value with proper Result Type containing Ok or Err
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
@ -15,11 +15,12 @@
|
|||||||
<script src="https://unpkg.com/htmx.org@1.7.0"></script>
|
<script src="https://unpkg.com/htmx.org@1.7.0"></script>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
.full-width { width: 100%; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main class="container">
|
<main class="container-fluid">
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
<div>
|
<div>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
<label for="{{ model_field.field_name }}">
|
<label for="{{ model_field.field_name }}">
|
||||||
{{ model_field.field_name | split(pat="_") | join(sep=" ") | title }}
|
{{ model_field.field_name | split(pat="_") | join(sep=" ") | title }}
|
||||||
{% if model_field.select_list != "" %}
|
{% if model_field.select_list != "" %}
|
||||||
<select name="{{ model_field.field_name }}"
|
<select name="{{ model_field.field_name }}" {% if model.errors | get(key=model_field.field_name,
|
||||||
{% if model.errors | get(key=model_field.field_name, default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||||
{% if model_field.is_option %}
|
{% if model_field.is_option %}
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -17,24 +17,28 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for select_list_item in select_lists[model_field.field_name] -%}
|
{% for select_list_item in select_lists[model_field.field_name] -%}
|
||||||
<option {% if select_list_item[0]==model.values | get(key=model_field.field_name, default="" ) %}
|
<option {% if select_list_item[0]==model.values | get(key=model_field.field_name, default="" ) %}
|
||||||
selected {% endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] | split(pat="_") | join(sep=" ") | title }}</option>
|
selected {% endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] | split(pat="_") |
|
||||||
|
join(sep=" ") | title }}</option>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="{{ model_field.html_input_type }}"
|
<input type="{{ model_field.html_input_type }}"
|
||||||
value="{{ model.values | get(key=model_field.field_name, default="") | split(pat="_") | join(sep=" ") | title }}"
|
value="{{ model.values | get(key=model_field.field_name, default="") | split(pat=" _") | join(sep=" " )
|
||||||
name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
| title }}" name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
||||||
aria-label="{{ model_field.field_name }}"
|
aria-label="{{ model_field.field_name }}" {% if model.errors | get(key=model_field.field_name,
|
||||||
{% if model.errors | get(key=model_field.field_name, default="") != "" %}
|
default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||||
placeholder="Invalid" aria-invalid="true"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</label>
|
</label>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
<div class="grid">
|
||||||
|
<div>
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
<a href="{{ list_link }}" role="button" class="secondary">Cancel</a>
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{{ list_link }}" role="button" class="secondary full-width">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
@ -5,24 +5,22 @@
|
|||||||
{% if not render_partial or render_partial == false %}
|
{% if not render_partial or render_partial == false %}
|
||||||
<article>
|
<article>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div>
|
|
||||||
<a href="create" role="button">Create</a>
|
|
||||||
</div>
|
|
||||||
<!--
|
|
||||||
<div>
|
<div>
|
||||||
<details role="list">
|
<details role="list">
|
||||||
<summary aria-haspopup="listbox" role="button">
|
<summary aria-haspopup="listbox" role="button">
|
||||||
With selected
|
Actions
|
||||||
</summary>
|
</summary>
|
||||||
<ul role="listbox">
|
<ul role="listbox">
|
||||||
<li><a>Delete</a></li>
|
<li><a href="create">Create</a></li>
|
||||||
|
<li><a>Delete Selected</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>-->
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="search" id="search" name="search" placeholder="Search"
|
<input type="search" id="search" name="search" placeholder="Search"
|
||||||
hx-get="/admin/{{ entity_name }}/list?render_partial=true&entities_per_page={{ entities_per_page }}&page={{ page }}"
|
hx-get="/admin/{{ entity_name }}/list?render_partial=true&entities_per_page={{ entities_per_page }}&page={{ page }}"
|
||||||
hx-trigger="keyup changed delay:500ms, search" hx-target="#{{ entity_name }}table"
|
hx-trigger="keyup changed delay:500ms, search" hx-target="#{{ entity_name }}table"
|
||||||
|
hx-indicator=".htmx-indicator"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,7 +30,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<!-- Select Checkbox -->
|
<div class="htmx-indicator">{% include "spinner.svg" %}</div>
|
||||||
</th>
|
</th>
|
||||||
<th>{{ view_model.primary_key | title }}</th>
|
<th>{{ view_model.primary_key | title }}</th>
|
||||||
{% for model_field in view_model.fields -%}
|
{% for model_field in view_model.fields -%}
|
||||||
|
7
actix_admin/templates/spinner.svg
Normal file
7
actix_admin/templates/spinner.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M10.72,19.9a8,8,0,0,1-6.5-9.79A7.77,7.77,0,0,1,10.4,4.16a8,8,0,0,1,9.49,6.52A1.54,1.54,0,0,0,21.38,12h.13a1.37,1.37,0,0,0,1.38-1.54,11,11,0,1,0-12.7,12.39A1.54,1.54,0,0,0,12,21.34h0A1.47,1.47,0,0,0,10.72,19.9Z">
|
||||||
|
<animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
</path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 480 B |
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
@ -12,8 +12,10 @@ pub struct Model {
|
|||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
#[actix_admin(primary_key)]
|
#[actix_admin(primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
#[actix_admin(searchable)]
|
||||||
pub title: String,
|
pub title: String,
|
||||||
#[sea_orm(column_type = "Text")]
|
#[sea_orm(column_type = "Text")]
|
||||||
|
#[actix_admin(searchable)]
|
||||||
pub text: String,
|
pub text: String,
|
||||||
#[actix_admin(select_list="Tea")]
|
#[actix_admin(select_list="Tea")]
|
||||||
pub tea_mandatory: Tea,
|
pub tea_mandatory: Tea,
|
||||||
|
Loading…
Reference in New Issue
Block a user