add dropdown for enums
This commit is contained in:
parent
2b653510b2
commit
e0da08ef73
@ -10,7 +10,7 @@ actix-web = "4.0.1"
|
||||
actix-rt = "2.7.0"
|
||||
actix-session = "0.5.0"
|
||||
tera = "1.15.0"
|
||||
|
||||
itertools = "0.10.3"
|
||||
oauth2 = "4.1"
|
||||
base64 = "0.13.0"
|
||||
async-trait = "0.1.53"
|
||||
|
@ -19,7 +19,6 @@ bae = "0.1.7"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
proc-macro2 = { version = "1.0.36", default-features = false }
|
||||
|
||||
rand = "0.8.5"
|
||||
url = "2.2.2"
|
||||
http = "0.2.6"
|
||||
|
@ -11,7 +11,8 @@ pub mod derive_attr {
|
||||
)]
|
||||
pub struct ActixAdmin {
|
||||
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 inner_type: Option<syn::Type>,
|
||||
|
||||
// Anything that implements `syn::parse::Parse` is supported.
|
||||
|
@ -2,23 +2,47 @@ use proc_macro;
|
||||
use quote::quote;
|
||||
|
||||
mod struct_fields;
|
||||
use struct_fields::{ get_fields_for_tokenstream, get_fields_for_edit_model, get_fields_for_from_model, get_actix_admin_fields_html_input, get_fields_for_create_model, get_actix_admin_fields, get_field_for_primary_key, get_primary_key_field_name};
|
||||
use struct_fields::{
|
||||
get_fields_for_tokenstream,
|
||||
get_fields_for_edit_model,
|
||||
get_fields_for_from_model,
|
||||
get_actix_admin_fields_html_input,
|
||||
get_fields_for_create_model,
|
||||
get_actix_admin_fields,
|
||||
get_field_for_primary_key,
|
||||
get_primary_key_field_name,
|
||||
get_actix_admin_fields_select_list
|
||||
};
|
||||
|
||||
mod selectlist_fields;
|
||||
use selectlist_fields::{
|
||||
get_select_list,
|
||||
get_select_lists
|
||||
};
|
||||
|
||||
mod model_fields;
|
||||
mod attributes;
|
||||
|
||||
#[proc_macro_derive(DeriveActixAdminSelectList, attributes(actix_admin))]
|
||||
pub fn derive_actix_admin_select_list(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
get_select_list(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(DeriveActixAdminModel, attributes(actix_admin))]
|
||||
pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let fields = get_fields_for_tokenstream(input);
|
||||
|
||||
let field_names = get_actix_admin_fields(&fields);
|
||||
let field_html_input_type = get_actix_admin_fields_html_input(&fields);
|
||||
let field_select_list = get_actix_admin_fields_select_list(&fields);
|
||||
let name_primary_field_str = get_primary_key_field_name(&fields);
|
||||
let fields_for_create_model = get_fields_for_create_model(&fields);
|
||||
let fields_for_edit_model = get_fields_for_edit_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 select_lists = get_select_lists(&fields);
|
||||
|
||||
let expanded = quote! {
|
||||
use std::convert::From;
|
||||
use std::iter::zip;
|
||||
@ -30,6 +54,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
use sea_orm::{entity::*, query::*};
|
||||
use std::collections::HashMap;
|
||||
use sea_orm::EntityTrait;
|
||||
use itertools::izip;
|
||||
use quote::quote;
|
||||
|
||||
impl From<Entity> for ActixAdminViewModel {
|
||||
@ -109,6 +134,12 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_select_lists(db: &DatabaseConnection) -> HashMap<String, Vec<(String, String)>> {
|
||||
hashmap![
|
||||
#(#select_lists),*
|
||||
]
|
||||
}
|
||||
|
||||
fn get_entity_name() -> String {
|
||||
Entity.table_name().to_string()
|
||||
}
|
||||
@ -136,8 +167,9 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
(num_pages, model_entities)
|
||||
}
|
||||
|
||||
fn get_fields() -> Vec<(String, String)> {
|
||||
fn get_fields() -> Vec<ActixAdminViewModelField> {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
let field_names = stringify!(
|
||||
#(#field_names),*
|
||||
).split(",")
|
||||
@ -148,23 +180,18 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
).split(",")
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut names_and_input_type = zip(field_names, html_input_types);
|
||||
|
||||
names_and_input_type
|
||||
.for_each( |field_name_and_type_tuple|
|
||||
vec.push((
|
||||
field_name_and_type_tuple.0
|
||||
.replace('"', "")
|
||||
.replace(' ', "")
|
||||
.to_string(),
|
||||
// TODO: match correct ActixAdminField Value
|
||||
field_name_and_type_tuple.1
|
||||
.replace('"', "")
|
||||
.replace(' ', "")
|
||||
.to_string()
|
||||
)
|
||||
)
|
||||
);
|
||||
let field_select_lists = stringify!(
|
||||
#(#field_select_list),*
|
||||
).split(",")
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (field_name, html_input_type, select_list) in izip!(&field_names, &html_input_types, &field_select_lists) {
|
||||
vec.push(ActixAdminViewModelField {
|
||||
field_name: field_name.replace('"', "").replace(' ', "").to_string(),
|
||||
html_input_type: html_input_type.replace('"', "").replace(' ', "").to_string(),
|
||||
select_list: select_list.replace('"', "").replace(' ', "").to_string()
|
||||
});
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ pub struct ModelField {
|
||||
// struct field is option<>
|
||||
pub inner_type: Option<Type>,
|
||||
pub primary_key: bool,
|
||||
pub html_input_type: String
|
||||
pub html_input_type: String,
|
||||
pub select_list: String
|
||||
}
|
||||
|
||||
impl ModelField {
|
||||
|
38
actix_admin/actix_admin_macros/src/selectlist_fields.rs
Normal file
38
actix_admin/actix_admin_macros/src/selectlist_fields.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use syn::{
|
||||
DeriveInput, Ident
|
||||
};
|
||||
use quote::quote;
|
||||
use crate::model_fields::{ ModelField };
|
||||
use proc_macro2::{Span};
|
||||
|
||||
pub fn get_select_list(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics);
|
||||
|
||||
let expanded = quote! {
|
||||
impl ActixAdminSelectListTrait for #ty {
|
||||
fn get_key_value() -> Vec<(String, String)> {
|
||||
let mut fields = Vec::new();
|
||||
for field in #ty::iter() {
|
||||
fields.push((field.to_string(), field.to_string()));
|
||||
}
|
||||
fields
|
||||
}
|
||||
}
|
||||
};
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
pub fn get_select_lists(fields: &Vec<ModelField>) -> Vec<proc_macro2::TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
.filter(|model_field| model_field.select_list != "")
|
||||
.map(|model_field| {
|
||||
let ident_name = model_field.ident.to_string();
|
||||
let select_list_ident = Ident::new(&(model_field.select_list), Span::call_site());
|
||||
quote! {
|
||||
#ident_name => #select_list_ident::get_key_value()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
@ -9,7 +9,7 @@ use crate::model_fields::{ ModelField };
|
||||
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<ModelField> {
|
||||
let ast: DeriveInput = syn::parse(input).unwrap();
|
||||
let (_vis, ty, _generics) = (&ast.vis, &ast.ident, &ast.generics);
|
||||
let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site());
|
||||
//let _names_struct_ident = Ident::new(&(ty.to_string() + "FieldStaticStr"), Span::call_site());
|
||||
|
||||
let fields = filter_fields(match ast.data {
|
||||
syn::Data::Struct(ref s) => &s.fields,
|
||||
@ -31,6 +31,9 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
let inner_type = extract_type_from_option(&field.ty);
|
||||
let field_ty = field.ty.to_owned();
|
||||
let is_primary_key = actix_admin_attr.clone().map_or(false, |attr| attr.primary_key.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()
|
||||
));
|
||||
let html_input_type = actix_admin_attr.map_or("text".to_string(), |attr| attr.html_input_type.map_or("text".to_string(),
|
||||
|attr_field| (LitStr::from(attr_field)).value()
|
||||
));
|
||||
@ -41,7 +44,8 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
ty: field_ty,
|
||||
inner_type: inner_type,
|
||||
primary_key: is_primary_key,
|
||||
html_input_type: html_input_type
|
||||
html_input_type: html_input_type,
|
||||
select_list: select_list
|
||||
};
|
||||
|
||||
Some(model_field)
|
||||
@ -124,6 +128,21 @@ pub fn get_actix_admin_fields_html_input(fields: &Vec<ModelField>) -> Vec<TokenS
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_actix_admin_fields_select_list(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
.filter(|model_field| !model_field.primary_key)
|
||||
.map(|model_field| {
|
||||
let select_list = model_field.select_list.to_string();
|
||||
|
||||
quote! {
|
||||
#select_list
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
|
||||
pub fn get_field_for_primary_key(fields: &Vec<ModelField>) -> TokenStream {
|
||||
let primary_key_model_field = fields
|
||||
.iter()
|
||||
|
@ -2,7 +2,6 @@ use lazy_static::lazy_static;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::collections::HashMap;
|
||||
use tera::{Tera};
|
||||
use serde::{Serialize};
|
||||
|
||||
pub mod view_model;
|
||||
pub mod model;
|
||||
@ -12,10 +11,10 @@ pub mod builder;
|
||||
pub mod prelude {
|
||||
pub use crate::builder::{ ActixAdminBuilder, ActixAdminBuilderTrait};
|
||||
pub use crate::model::{ ActixAdminModel, ActixAdminModelTrait};
|
||||
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait};
|
||||
pub use actix_admin_macros::{ DeriveActixAdminModel };
|
||||
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField};
|
||||
pub use actix_admin_macros::{ DeriveActixAdminModel, DeriveActixAdminSelectList };
|
||||
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin};
|
||||
pub use crate::{ hashmap };
|
||||
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
||||
}
|
||||
|
||||
use crate::prelude::*;
|
||||
@ -24,7 +23,7 @@ use crate::prelude::*;
|
||||
macro_rules! hashmap {
|
||||
($( $key: expr => $val: expr ),*) => {{
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
$( map.insert($key.to_string(), $val.to_string()); )*
|
||||
$( map.insert($key.to_string(), $val); )*
|
||||
map
|
||||
}}
|
||||
}
|
||||
@ -41,8 +40,12 @@ pub trait ActixAdminAppDataTrait {
|
||||
fn get_actix_admin(&self) -> &ActixAdmin;
|
||||
}
|
||||
|
||||
// ActixAdminModel
|
||||
// SelectListTrait
|
||||
pub trait ActixAdminSelectListTrait {
|
||||
fn get_key_value() -> Vec<(String, String)>;
|
||||
}
|
||||
|
||||
// ActixAdminModel
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ActixAdmin {
|
||||
pub entity_names: Vec<String>,
|
||||
|
@ -2,6 +2,7 @@ use async_trait::async_trait;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::{Serialize};
|
||||
use std::collections::HashMap;
|
||||
use crate::ActixAdminViewModelField;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ActixAdminModelTrait {
|
||||
@ -10,7 +11,7 @@ pub trait ActixAdminModelTrait {
|
||||
page: usize,
|
||||
posts_per_page: usize,
|
||||
) -> (usize, Vec<ActixAdminModel>);
|
||||
fn get_fields() -> Vec<(String, String)>;
|
||||
fn get_fields() -> Vec<ActixAdminViewModelField>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
@ -37,6 +38,7 @@ impl From<String> for ActixAdminModel {
|
||||
|
||||
impl ActixAdminModel {
|
||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Option<T> {
|
||||
println!("{:?}", self.values);
|
||||
let value = self.values.get(key).unwrap().to_string().parse::<T>();
|
||||
match value {
|
||||
Ok(val) => Some(val),
|
||||
|
@ -22,7 +22,7 @@ pub async fn create_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
let mut ctx = Context::new();
|
||||
ctx.insert("entity_names", &entity_names);
|
||||
ctx.insert("view_model", &view_model);
|
||||
ctx.insert("model_fields", &view_model.fields);
|
||||
ctx.insert("select_lists", &E::get_select_lists(_db).await);
|
||||
|
||||
let body = TERA
|
||||
.render("create.html", &ctx)
|
||||
|
@ -26,6 +26,7 @@ pub async fn edit_get<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
|
||||
ctx.insert("entity_names", &entity_names);
|
||||
ctx.insert("view_model", &view_model);
|
||||
ctx.insert("model", &model);
|
||||
ctx.insert("select_lists", &E::get_select_lists(db).await);
|
||||
|
||||
let body = TERA
|
||||
.render("edit.html", &ctx)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::{Serialize};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::ActixAdminModel;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@ -17,7 +17,8 @@ pub trait ActixAdminViewModelTrait {
|
||||
async fn delete_entity(db: &DatabaseConnection, id: i32) -> bool;
|
||||
async fn get_entity(db: &DatabaseConnection, id: i32) -> ActixAdminModel;
|
||||
async fn edit_entity(db: &DatabaseConnection, id: i32, model: ActixAdminModel) -> ActixAdminModel;
|
||||
|
||||
async fn get_select_lists(db: &DatabaseConnection) -> HashMap<String, Vec<(String, String)>>;
|
||||
|
||||
fn get_entity_name() -> String;
|
||||
}
|
||||
|
||||
@ -25,5 +26,12 @@ pub trait ActixAdminViewModelTrait {
|
||||
pub struct ActixAdminViewModel {
|
||||
pub entity_name: String,
|
||||
pub primary_key: String,
|
||||
pub fields: Vec<(String, String)>,
|
||||
pub fields: Vec<ActixAdminViewModelField>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct ActixAdminViewModelField {
|
||||
pub field_name: String,
|
||||
pub html_input_type: String,
|
||||
pub select_list: String
|
||||
}
|
@ -4,7 +4,16 @@
|
||||
<form method="post">
|
||||
<div class="grid">
|
||||
{% for model_field in view_model.fields -%}
|
||||
<input type="{{ model_field[1] }}" name="{{ model_field[0] }}" placeholder="{{ model_field[0] }}" aria-label="{{ model_field[0] }}"><!-- required="" -->
|
||||
{% if model_field.select_list != "" %}
|
||||
<select name="{{ model_field.field_name }}">
|
||||
<option value="" selected>Select</option>
|
||||
{% for select_list_item in select_lists[model_field.field_name] -%}
|
||||
<option value="{{ select_list_item[0] }}">{{ select_list_item[1] }}</option>
|
||||
{%- endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="{{ model_field.html_input_type }}" name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}"><!-- required="" -->
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
<button type="submit">Save</button>
|
||||
<button onclick="history.back()">Cancel</button>
|
||||
|
@ -3,8 +3,17 @@
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
<div class="grid">
|
||||
{% for key, value in model.values -%}
|
||||
<input type="text" value="{{ value }}" name="{{ key }}" placeholder="{{ key }}" aria-label="{{ key }}"><!-- required="" -->
|
||||
{% for model_field in view_model.fields -%}
|
||||
{% if model_field.select_list != "" %}
|
||||
<select name="{{ model_field.field_name }}">
|
||||
<option value="" selected>Select</option>
|
||||
{% 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) %} selected {% endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] }}</option>
|
||||
{%- endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="{{ model_field.html_input_type }}" value="{{ model.values | get(key=model_field.field_name) }}" name="{{ model_field.field_name }}" placeholder="{{ model_field.field_name }}" aria-label="{{ model_field.field_name }}"><!-- required="" -->
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
<button type="submit">Save</button>
|
||||
<button onclick="history.back()">Cancel</button>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<tr>
|
||||
<th>{{ view_model.primary_key }}</th>
|
||||
{% for model_field in view_model.fields -%}
|
||||
<th>{{ model_field[0] }}</th>
|
||||
<th>{{ model_field.field_name }}</th>
|
||||
{%- endfor %}
|
||||
<th>
|
||||
<!-- Edit Action -->
|
||||
@ -20,7 +20,7 @@
|
||||
<tr>
|
||||
<td>{{ entity.primary_key }}</td>
|
||||
{% for model_field in view_model.fields -%}
|
||||
<td>{{ entity.values | get(key=model_field[0]) }}</td>
|
||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||
{%- endfor %}
|
||||
<td>
|
||||
<a href="edit/{{ entity.primary_key }}">✎</a>
|
||||
|
BIN
database.db
BIN
database.db
Binary file not shown.
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
@ -15,8 +15,9 @@ pub struct Model {
|
||||
pub title: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub text: String,
|
||||
#[actix_admin(select_list="Tea")]
|
||||
pub tea_mandatory: Tea,
|
||||
#[actix_admin()]
|
||||
#[actix_admin(select_list="Tea")]
|
||||
pub tea_optional: Option<Tea>,
|
||||
}
|
||||
|
||||
@ -25,7 +26,7 @@ pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Deserialize, Serialize, DeriveActixAdminSelectList)]
|
||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
|
||||
pub enum Tea {
|
||||
#[sea_orm(string_value = "EverydayTea")]
|
||||
|
Loading…
Reference in New Issue
Block a user