separate primary_key from other field values
This commit is contained in:
parent
244d1f9e01
commit
96b069042e
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
18
actix_admin/actix_admin_macros/src/model_fields.rs
Normal file
18
actix_admin/actix_admin_macros/src/model_fields.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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<_>>()
|
||||
}
|
@ -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),
|
||||
|
@ -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((
|
||||
|
@ -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((
|
||||
|
@ -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);
|
||||
|
@ -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)>,
|
||||
}
|
@ -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]) }}">✎</a>
|
||||
<a hx-post="delete/{{ entity.values | get(key=view_model.fields[0][0]) }}">🗑</a>
|
||||
<a href="edit/{{ entity.primary_key }}">✎</a>
|
||||
<a hx-post="delete/{{ entity.primary_key }}">🗑</a>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor %}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
@ -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")]
|
||||
|
@ -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)
|
||||
|
@ -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>,
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user