separate primary_key from other field values

This commit is contained in:
manuel 2022-06-06 15:25:37 +02:00
parent 244d1f9e01
commit 96b069042e
15 changed files with 186 additions and 144 deletions

View File

@ -9,7 +9,8 @@ pub mod derive_attr {
Default
)]
pub struct ActixAdmin {
pub inner_type: Option<syn::Type>,
pub primary_key: Option<()>
//pub inner_type: Option<syn::Type>,
// Anything that implements `syn::parse::Parse` is supported.
//mandatory_type: syn::Type,

View File

@ -2,7 +2,9 @@ use proc_macro;
use quote::quote;
mod struct_fields;
use struct_fields::get_fields_for_tokenstream;
use struct_fields::{ get_fields_for_tokenstream, get_fields_for_edit_model, get_fields_for_from_model, get_fields_for_create_model, get_field_names, get_field_for_primary_key, get_primary_key_field_name};
mod model_fields;
mod attributes;
@ -10,82 +12,12 @@ mod attributes;
pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let fields = get_fields_for_tokenstream(input);
let names_const_fields_str = fields
.iter()
.map(|(_vis, ident, _ty, _is_option)| {
let ident_name = ident.to_string();
quote! {
#ident_name
}
})
.collect::<Vec<_>>();
let fields_for_create_model = fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.filter(|(_vis, ident, _ty, _is_option)| !ident.to_string().eq("id"))
.map(|(_vis, ident, ty, is_option)| {
let ident_name = ident.to_string();
match is_option {
true => {
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name))
}
},
false => {
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
}
})
.collect::<Vec<_>>();
let fields_for_edit_model = fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.filter(|(_vis, ident, _ty, _is_option)| !ident.to_string().eq("id"))
.map(|(_vis, ident, ty, is_option)| {
let ident_name = ident.to_string();
println!("edit {} {:?}", &ident_name, ty);
match is_option {
true => {
quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name))
}
},
false => {
quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
}
})
.collect::<Vec<_>>();
let fields_for_from_model = fields
.iter()
.map(|(_vis, ident, _ty, is_option)| {
let ident_name = ident.to_string();
println!("from {} {:?}", &ident_name, _ty);
match is_option {
true => {
quote! {
#ident_name => match model.#ident {
Some(val) => val.to_string(),
None => "".to_owned()
}
}
},
false => {
quote! {
#ident_name => model.#ident.to_string()
}
}
}
})
.collect::<Vec<_>>();
let names_const_fields_str = get_field_names(&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 expanded = quote! {
use std::convert::From;
@ -102,6 +34,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
impl From<Entity> for ActixAdminViewModel {
fn from(entity: Entity) -> Self {
ActixAdminViewModel {
primary_key: #name_primary_field_str.to_string(),
entity_name: entity.table_name().to_string(),
fields: Entity::get_fields()
}
@ -111,6 +44,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
impl From<Model> for ActixAdminModel {
fn from(model: Model) -> Self {
ActixAdminModel {
#field_for_primary_key,
values: hashmap![
#(#fields_for_from_model),*
]
@ -139,8 +73,6 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
async fn create_entity(db: &DatabaseConnection, mut model: ActixAdminModel) -> ActixAdminModel {
let new_model = ActiveModel::from(model.clone());
let insert_operation = Entity::insert(new_model).exec(db).await;
println!("creating {:?}", model);
println!("operation {:?}", insert_operation);
model
}

View File

@ -0,0 +1,18 @@
use syn::{
Visibility, Type
};
pub struct ModelField {
pub visibility: Visibility,
pub ident: proc_macro2::Ident,
pub ty: Type,
// struct field is option<>
pub inner_type: Option<Type>,
pub primary_key: bool
}
impl ModelField {
pub fn is_option(&self) -> bool {
self.inner_type.is_some()
}
}

View File

@ -1,13 +1,12 @@
use proc_macro2::{Span, Ident};
use proc_macro2::{Span, Ident, TokenStream};
use syn::{
Attribute, Fields, Meta, NestedMeta, Visibility, DeriveInput, Type
Fields, DeriveInput
};
use quote::quote;
use crate::attributes::derive_attr;
use crate::model_fields::{ ModelField };
const ACTIX_ADMIN: &'static str = "actix_admin";
pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::Vec<(syn::Visibility, proc_macro2::Ident, Type, bool)> {
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());
@ -19,58 +18,29 @@ pub fn get_fields_for_tokenstream(input: proc_macro::TokenStream) -> std::vec::V
fields
}
pub fn has_skip_attr(attr: &Attribute, path: &'static str) -> bool {
if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
//println!("1");
//println!("{:?}", meta_list.path);
//println!("{}", path);
if meta_list.path.is_ident(path) {
//println!("2");
for nested_item in meta_list.nested.iter() {
if let NestedMeta::Meta(Meta::Path(path)) = nested_item {
//println!("3");
if path.is_ident(ACTIX_ADMIN) {
//println!("true");
return true;
}
}
}
}
}
false
}
pub fn get_field_type<'a>(actix_admin_attr: &'a Option<derive_attr::ActixAdmin>, field: &'a syn::Field) -> &'a syn::Type {
match actix_admin_attr {
Some(attr) => {
match &attr.inner_type {
Some(inner_type) => &inner_type,
None => &field.ty
}
},
_ => &field.ty
}
}
pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type, bool)> {
pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
fields
.iter()
.filter_map(|field| {
let actix_admin_attr = derive_attr::ActixAdmin::try_from_attributes(&field.attrs).unwrap_or_default();
if field
.attrs
.iter()
.find(|attr| has_skip_attr(attr, ACTIX_ADMIN))
.is_none()
&& field.ident.is_some()
if field.ident.is_some()
{
let field_vis = field.vis.clone();
let field_ident = field.ident.as_ref().unwrap().clone();
println!("{}", field_ident.to_string());
let is_option = extract_type_from_option(&field.ty).is_some();
let field_ty = get_field_type(&actix_admin_attr, &field).to_owned();
Some((field_vis, field_ident, field_ty, is_option))
let inner_type = extract_type_from_option(&field.ty);
let field_ty = field.ty.to_owned();
let is_primary_key = actix_admin_attr.map_or(false, |attr| attr.primary_key.is_some());
let model_field = ModelField {
visibility: field_vis,
ident: field_ident,
ty: field_ty,
inner_type: inner_type,
primary_key: is_primary_key
};
Some(model_field)
} else {
None
}
@ -78,7 +48,7 @@ pub fn filter_fields(fields: &Fields) -> Vec<(Visibility, Ident, Type, bool)> {
.collect::<Vec<_>>()
}
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
fn extract_type_from_option(ty: &syn::Type) -> Option<syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
@ -117,7 +87,125 @@ fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
GenericArgument::Type(ref ty) => Some(ty.to_owned()),
_ => None,
})
}
pub fn get_field_names(fields: &Vec<ModelField>) -> Vec<TokenStream> {
fields
.iter()
.filter(|model_field| !model_field.primary_key)
.map(|model_field| {
let ident_name = model_field.ident.to_string();
quote! {
#ident_name
}
})
.collect::<Vec<_>>()
}
pub fn get_field_for_primary_key(fields: &Vec<ModelField>) -> TokenStream {
let primary_key_model_field = fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.find(|model_field| model_field.primary_key)
.expect("model must have a single primary key");
let ident = primary_key_model_field.ident.to_owned();
quote! {
primary_key: Some(model.#ident.to_string())
}
}
pub fn get_primary_key_field_name(fields: &Vec<ModelField>) -> String {
let primary_key_model_field = fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.find(|model_field| model_field.primary_key)
.expect("model must have a single primary key");
primary_key_model_field.ident.to_string()
}
pub fn get_fields_for_from_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
fields
.iter()
.filter(|model_field| !model_field.primary_key)
.map(|model_field| {
let ident_name = model_field.ident.to_string();
let ident = model_field.ident.to_owned();
match model_field.is_option() {
true => {
quote! {
#ident_name => match model.#ident {
Some(val) => val.to_string(),
None => "".to_owned()
}
}
},
false => {
quote! {
#ident_name => model.#ident.to_string()
}
}
}
})
.collect::<Vec<_>>()
}
pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.filter(|model_field| !model_field.primary_key)
.map(|model_field| {
let ident_name = model_field.ident.to_string();
let ident = model_field.ident.to_owned();
let ty = model_field.ty.to_owned();
match model_field.is_option() {
true => {
let inner_ty = model_field.inner_type.to_owned().unwrap();
quote! {
#ident: Set(model.get_value::<#inner_ty>(#ident_name))
}
},
false => {
quote! {
#ident: Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
}
})
.collect::<Vec<_>>()
}
pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
fields
.iter()
// TODO: filter id attr based on struct attr or sea_orm primary_key attr
.filter(|model_field| !model_field.primary_key)
.map(|model_field| {
let ident_name = model_field.ident.to_string();
let ident = model_field.ident.to_owned();
let ty = model_field.ty.to_owned();
match model_field.is_option() {
true => {
let inner_ty = model_field.inner_type.to_owned().unwrap();
quote! {
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name))
}
},
false => {
quote! {
entity.#ident = Set(model.get_value::<#ty>(#ident_name).unwrap())
}
}
}
})
.collect::<Vec<_>>()
}

View File

@ -17,6 +17,7 @@ pub trait ActixAdminModelTrait {
#[derive(Clone, Debug, Serialize)]
pub struct ActixAdminModel {
pub primary_key: Option<String>,
pub values: HashMap<String, String>,
}
@ -32,13 +33,12 @@ impl From<String> for ActixAdminModel {
);
}
ActixAdminModel { values: hashmap }
ActixAdminModel { primary_key: None, values: hashmap }
}
}
impl ActixAdminModel {
pub fn get_value<T: std::str::FromStr>(&self, key: &str) -> Option<T> {
println!("get value for key {}", key);
let value = self.values.get(key).unwrap().to_string().parse::<T>();
match value {
Ok(val) => Some(val),

View File

@ -12,8 +12,8 @@ pub async fn create_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>
let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin();
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
let mut admin_model = ActixAdminModel::from(text);
admin_model = E::create_entity(db, admin_model).await;
let mut _admin_model = ActixAdminModel::from(text);
_admin_model = E::create_entity(db, _admin_model).await;
Ok(HttpResponse::Found()
.append_header((

View File

@ -13,8 +13,8 @@ pub async fn edit_post<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
let entity_name = E::get_entity_name();
let actix_admin = data.get_actix_admin();
let view_model = actix_admin.view_models.get(&entity_name).unwrap();
let mut admin_model = ActixAdminModel::from(text);
admin_model = E::edit_entity(db, id.into_inner(), admin_model).await;
let mut _admin_model = ActixAdminModel::from(text);
_admin_model = E::edit_entity(db, id.into_inner(), _admin_model).await;
Ok(HttpResponse::Found()
.append_header((

View File

@ -37,7 +37,6 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
let result: (usize, Vec<ActixAdminModel>) = E::list(db, page, entities_per_page).await;
let entities = result.1;
let num_pages = result.0;
println!("{:?}", entities);
let mut ctx = Context::new();
ctx.insert("entity_names", &entity_names);

View File

@ -25,5 +25,6 @@ pub trait ActixAdminViewModelTrait {
#[derive(Clone, Debug, Serialize)]
pub struct ActixAdminViewModel {
pub entity_name: String,
pub primary_key: String,
pub fields: Vec<(String, ActixAdminField)>,
}

View File

@ -5,6 +5,7 @@
<table role="grid">
<thead>
<tr>
<th>{{ view_model.primary_key }}</th>
{% for model_field in view_model.fields -%}
<th>{{ model_field[0] }}</th>
{%- endfor %}
@ -17,12 +18,13 @@
<tbody hx-confirm="Are you sure?" hx-target="closest tr" hx-swap="outerHTML">
{% for entity in entities -%}
<tr>
<td>{{ entity.primary_key }}</td>
{% for model_field in view_model.fields -%}
<td>{{ entity.values | get(key=model_field[0]) }}</td>
{%- endfor %}
<td>
<a href="edit/{{ entity.values | get(key=view_model.fields[0][0]) }}">&#9998;</a>
<a hx-post="delete/{{ entity.values | get(key=view_model.fields[0][0]) }}">&#128465;</a>
<a href="edit/{{ entity.primary_key }}">&#9998;</a>
<a hx-post="delete/{{ entity.primary_key }}">&#128465;</a>
</td>
</tr>
{%- endfor %}

View File

@ -125,7 +125,6 @@ async fn read_user(api_base_url: &str, access_token: &AccessToken) -> UserInfo {
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
};
println!("{} {}", &resp.status_code, s);
serde_json::from_slice(&resp.body).unwrap()
}

Binary file not shown.

View File

@ -7,6 +7,7 @@ use actix_admin::prelude::*;
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
#[actix_admin(primary_key)]
pub id: i32,
pub comment: String,
#[sea_orm(column_type = "Text")]

View File

@ -29,7 +29,7 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(post::Column::TeaOptional).string())
.to_owned();
create_table(db, &stmt).await;
let _result = create_table(db, &stmt).await;
let stmt = sea_query::Table::create()
.table(comment::Entity)

View File

@ -10,12 +10,13 @@ use std::fmt::Display;
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
#[actix_admin(primary_key)]
pub id: i32,
pub title: String,
#[sea_orm(column_type = "Text")]
pub text: String,
pub tea_mandatory: Tea,
#[actix_admin(inner_type=Tea)]
#[actix_admin()]
pub tea_optional: Option<Tea>,
}