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 primary_key: Option<()>,
|
||||
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>,
|
||||
|
||||
// Anything that implements `syn::parse::Parse` is supported.
|
||||
|
@ -13,7 +13,8 @@ use struct_fields::{
|
||||
get_primary_key_field_name,
|
||||
get_actix_admin_fields_select_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;
|
||||
@ -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 field_for_primary_key = get_field_for_primary_key(&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);
|
||||
|
||||
@ -96,8 +98,8 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl ActixAdminViewModelTrait for Entity {
|
||||
async fn list(db: &DatabaseConnection, page: usize, entities_per_page: usize) -> (usize, Vec<ActixAdminModel>) {
|
||||
let entities = Entity::list_model(db, page, entities_per_page).await;
|
||||
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, search).await;
|
||||
entities
|
||||
}
|
||||
|
||||
@ -163,9 +165,13 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
|
||||
#[async_trait]
|
||||
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::* };
|
||||
let paginator = Entity::find()
|
||||
.filter(
|
||||
Condition::any()
|
||||
#(#fields_searchable)*
|
||||
)
|
||||
.order_by_asc(Column::Id)
|
||||
.paginate(db, posts_per_page);
|
||||
let num_pages = paginator.num_pages().await.ok().unwrap();
|
||||
|
@ -11,7 +11,8 @@ pub struct ModelField {
|
||||
pub inner_type: Option<Type>,
|
||||
pub primary_key: bool,
|
||||
pub html_input_type: String,
|
||||
pub select_list: String
|
||||
pub select_list: String,
|
||||
pub searchable: bool
|
||||
}
|
||||
|
||||
impl ModelField {
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::attributes::derive_attr;
|
||||
use crate::model_fields::ModelField;
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
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> {
|
||||
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
|
||||
}
|
||||
|
||||
fn capitalize_first_letter(s: &str) -> String {
|
||||
s[0..1].to_uppercase() + &s[1..]
|
||||
}
|
||||
|
||||
pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
fields
|
||||
.iter()
|
||||
@ -31,6 +35,9 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
let is_primary_key = actix_admin_attr
|
||||
.clone()
|
||||
.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| {
|
||||
attr.select_list.map_or("".to_string(), |attr_field| {
|
||||
(LitStr::from(attr_field)).value()
|
||||
@ -51,6 +58,7 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
primary_key: is_primary_key,
|
||||
html_input_type: html_input_type,
|
||||
select_list: select_list,
|
||||
searchable: is_searchable
|
||||
};
|
||||
Some(model_field)
|
||||
} else {
|
||||
@ -146,6 +154,20 @@ pub fn get_actix_admin_fields_html_input(fields: &Vec<ModelField>) -> Vec<TokenS
|
||||
.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> {
|
||||
fields
|
||||
.iter()
|
||||
|
@ -10,6 +10,7 @@ pub trait ActixAdminModelTrait {
|
||||
db: &DatabaseConnection,
|
||||
page: usize,
|
||||
posts_per_page: usize,
|
||||
search: &String
|
||||
) -> (usize, Vec<ActixAdminModel>);
|
||||
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
||||
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String>;
|
||||
|
@ -35,9 +35,10 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
.entities_per_page
|
||||
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
|
||||
let render_partial = params.render_partial.unwrap_or(false);
|
||||
let search = params.search.clone().unwrap_or(String::new());
|
||||
|
||||
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 num_pages = result.0;
|
||||
|
||||
|
@ -10,6 +10,7 @@ pub trait ActixAdminViewModelTrait {
|
||||
db: &DatabaseConnection,
|
||||
page: usize,
|
||||
entities_per_page: usize,
|
||||
search: &String
|
||||
) -> (usize, Vec<ActixAdminModel>);
|
||||
|
||||
// TODO: Replace return value with proper Result Type containing Ok or Err
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% else %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@ -15,11 +15,12 @@
|
||||
<script src="https://unpkg.com/htmx.org@1.7.0"></script>
|
||||
|
||||
<style type="text/css">
|
||||
.full-width { width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<main class="container-fluid">
|
||||
{% include "header.html" %}
|
||||
<div>
|
||||
{% block content %}
|
||||
|
@ -8,8 +8,8 @@
|
||||
<label for="{{ model_field.field_name }}">
|
||||
{{ model_field.field_name | split(pat="_") | join(sep=" ") | title }}
|
||||
{% if model_field.select_list != "" %}
|
||||
<select name="{{ model_field.field_name }}"
|
||||
{% if model.errors | get(key=model_field.field_name, default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||
<select name="{{ model_field.field_name }}" {% if model.errors | get(key=model_field.field_name,
|
||||
default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||
{% if model_field.is_option %}
|
||||
<option value=""></option>
|
||||
{% else %}
|
||||
@ -17,24 +17,28 @@
|
||||
{% endif %}
|
||||
{% 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="" ) %}
|
||||
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 %}
|
||||
</select>
|
||||
</label>
|
||||
{% else %}
|
||||
<input type="{{ model_field.html_input_type }}"
|
||||
value="{{ model.values | get(key=model_field.field_name, default="") | split(pat="_") | join(sep=" ") | title }}"
|
||||
name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
||||
aria-label="{{ model_field.field_name }}"
|
||||
{% if model.errors | get(key=model_field.field_name, default="") != "" %}
|
||||
placeholder="Invalid" aria-invalid="true"
|
||||
{% endif %}
|
||||
>
|
||||
value="{{ model.values | get(key=model_field.field_name, default="") | split(pat=" _") | join(sep=" " )
|
||||
| title }}" name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}"
|
||||
aria-label="{{ model_field.field_name }}" {% if model.errors | get(key=model_field.field_name,
|
||||
default="" ) !="" %} placeholder="Invalid" aria-invalid="true" {% endif %}>
|
||||
{% endif %}
|
||||
</label>
|
||||
{%- endfor %}
|
||||
<button type="submit">Save</button>
|
||||
<a href="{{ list_link }}" role="button" class="secondary">Cancel</a>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ list_link }}" role="button" class="secondary full-width">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
@ -5,24 +5,22 @@
|
||||
{% if not render_partial or render_partial == false %}
|
||||
<article>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<a href="create" role="button">Create</a>
|
||||
</div>
|
||||
<!--
|
||||
<div>
|
||||
<details role="list">
|
||||
<summary aria-haspopup="listbox" role="button">
|
||||
With selected
|
||||
Actions
|
||||
</summary>
|
||||
<ul role="listbox">
|
||||
<li><a>Delete</a></li>
|
||||
<li><a href="create">Create</a></li>
|
||||
<li><a>Delete Selected</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</div>-->
|
||||
</div>
|
||||
<div>
|
||||
<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-trigger="keyup changed delay:500ms, search" hx-target="#{{ entity_name }}table"
|
||||
hx-indicator=".htmx-indicator"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,7 +30,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<!-- Select Checkbox -->
|
||||
<div class="htmx-indicator">{% include "spinner.svg" %}</div>
|
||||
</th>
|
||||
<th>{{ view_model.primary_key | title }}</th>
|
||||
{% 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)]
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
#[actix_admin(searchable)]
|
||||
pub title: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
#[actix_admin(searchable)]
|
||||
pub text: String,
|
||||
#[actix_admin(select_list="Tea")]
|
||||
pub tea_mandatory: Tea,
|
||||
|
Loading…
Reference in New Issue
Block a user