diff --git a/Cargo.toml b/Cargo.toml index d7174ad..ab6390a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/actix_admin/actix_admin_macros/Cargo.toml b/actix_admin/actix_admin_macros/Cargo.toml index e37c597..d9adc22 100644 --- a/actix_admin/actix_admin_macros/Cargo.toml +++ b/actix_admin/actix_admin_macros/Cargo.toml @@ -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" diff --git a/actix_admin/actix_admin_macros/src/attributes.rs b/actix_admin/actix_admin_macros/src/attributes.rs index 6c07b5d..4b405a2 100644 --- a/actix_admin/actix_admin_macros/src/attributes.rs +++ b/actix_admin/actix_admin_macros/src/attributes.rs @@ -11,7 +11,8 @@ pub mod derive_attr { )] pub struct ActixAdmin { pub primary_key: Option<()>, - pub html_input_type: Option + pub html_input_type: Option, + pub select_list: Option //pub inner_type: Option, // Anything that implements `syn::parse::Parse` is supported. diff --git a/actix_admin/actix_admin_macros/src/lib.rs b/actix_admin/actix_admin_macros/src/lib.rs index 4aed48d..5d497e3 100644 --- a/actix_admin/actix_admin_macros/src/lib.rs +++ b/actix_admin/actix_admin_macros/src/lib.rs @@ -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 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> { + 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 { 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::>(); - 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::>(); + + 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 } } diff --git a/actix_admin/actix_admin_macros/src/model_fields.rs b/actix_admin/actix_admin_macros/src/model_fields.rs index 9364fb4..9358af0 100644 --- a/actix_admin/actix_admin_macros/src/model_fields.rs +++ b/actix_admin/actix_admin_macros/src/model_fields.rs @@ -9,7 +9,8 @@ pub struct ModelField { // struct field is option<> pub inner_type: Option, pub primary_key: bool, - pub html_input_type: String + pub html_input_type: String, + pub select_list: String } impl ModelField { diff --git a/actix_admin/actix_admin_macros/src/selectlist_fields.rs b/actix_admin/actix_admin_macros/src/selectlist_fields.rs new file mode 100644 index 0000000..add40f1 --- /dev/null +++ b/actix_admin/actix_admin_macros/src/selectlist_fields.rs @@ -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) -> Vec { + 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::>() +} \ No newline at end of file diff --git a/actix_admin/actix_admin_macros/src/struct_fields.rs b/actix_admin/actix_admin_macros/src/struct_fields.rs index 005aca6..46205b8 100644 --- a/actix_admin/actix_admin_macros/src/struct_fields.rs +++ b/actix_admin/actix_admin_macros/src/struct_fields.rs @@ -9,7 +9,7 @@ use crate::model_fields::{ ModelField }; pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec { 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 { 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 { 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) -> Vec>() } +pub fn get_actix_admin_fields_select_list(fields: &Vec) -> Vec { + 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::>() +} + + pub fn get_field_for_primary_key(fields: &Vec) -> TokenStream { let primary_key_model_field = fields .iter() diff --git a/actix_admin/src/lib.rs b/actix_admin/src/lib.rs index 935f790..5754c49 100644 --- a/actix_admin/src/lib.rs +++ b/actix_admin/src/lib.rs @@ -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, diff --git a/actix_admin/src/model.rs b/actix_admin/src/model.rs index 2f9576f..f8c870e 100644 --- a/actix_admin/src/model.rs +++ b/actix_admin/src/model.rs @@ -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); - fn get_fields() -> Vec<(String, String)>; + fn get_fields() -> Vec; } #[derive(Clone, Debug, Serialize)] @@ -37,6 +38,7 @@ impl From for ActixAdminModel { impl ActixAdminModel { pub fn get_value(&self, key: &str) -> Option { + println!("{:?}", self.values); let value = self.values.get(key).unwrap().to_string().parse::(); match value { Ok(val) => Some(val), diff --git a/actix_admin/src/routes/create_get.rs b/actix_admin/src/routes/create_get.rs index 0c4c2ec..cc946f8 100644 --- a/actix_admin/src/routes/create_get.rs +++ b/actix_admin/src/routes/create_get.rs @@ -22,7 +22,7 @@ pub async fn create_get( 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) diff --git a/actix_admin/src/routes/edit_get.rs b/actix_admin/src/routes/edit_get.rs index e69a6b5..3e65b18 100644 --- a/actix_admin/src/routes/edit_get.rs +++ b/actix_admin/src/routes/edit_get.rs @@ -26,6 +26,7 @@ pub async fn edit_get( 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) diff --git a/actix_admin/src/view_model.rs b/actix_admin/src/view_model.rs index c9e1439..87a1654 100644 --- a/actix_admin/src/view_model.rs +++ b/actix_admin/src/view_model.rs @@ -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>; + 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, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ActixAdminViewModelField { + pub field_name: String, + pub html_input_type: String, + pub select_list: String } \ No newline at end of file diff --git a/actix_admin/templates/create.html b/actix_admin/templates/create.html index 576af80..556d716 100644 --- a/actix_admin/templates/create.html +++ b/actix_admin/templates/create.html @@ -4,7 +4,16 @@
{% for model_field in view_model.fields -%} - + {% if model_field.select_list != "" %} + + {% else %} + + {% endif %} {%- endfor %} diff --git a/actix_admin/templates/edit.html b/actix_admin/templates/edit.html index 6215b29..4f956c3 100644 --- a/actix_admin/templates/edit.html +++ b/actix_admin/templates/edit.html @@ -3,8 +3,17 @@ {% block content %}
- {% for key, value in model.values -%} - + {% for model_field in view_model.fields -%} + {% if model_field.select_list != "" %} + + {% else %} + + {% endif %} {%- endfor %} diff --git a/actix_admin/templates/list.html b/actix_admin/templates/list.html index 0d763d1..3ba742f 100644 --- a/actix_admin/templates/list.html +++ b/actix_admin/templates/list.html @@ -7,7 +7,7 @@ {{ view_model.primary_key }} {% for model_field in view_model.fields -%} - {{ model_field[0] }} + {{ model_field.field_name }} {%- endfor %} @@ -20,7 +20,7 @@ {{ entity.primary_key }} {% for model_field in view_model.fields -%} - {{ entity.values | get(key=model_field[0]) }} + {{ entity.values | get(key=model_field.field_name) }} {%- endfor %} diff --git a/database.db b/database.db index 3d1e885..1060506 100644 Binary files a/database.db and b/database.db differ diff --git a/database.db-wal b/database.db-wal index 5e75c7c..276b164 100644 Binary files a/database.db-wal and b/database.db-wal differ diff --git a/src/entity/post.rs b/src/entity/post.rs index 8e6c18b..fe35628 100644 --- a/src/entity/post.rs +++ b/src/entity/post.rs @@ -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, } @@ -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")]