derive html input type based on field type
This commit is contained in:
parent
80df5a2719
commit
a0fe692046
@ -2,29 +2,20 @@ 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,
|
||||
use struct_fields::{
|
||||
get_actix_admin_fields, get_actix_admin_fields_html_input,
|
||||
get_actix_admin_fields_is_option_list, get_actix_admin_fields_searchable,
|
||||
get_actix_admin_fields_select_list, get_actix_admin_fields_type_path_string,
|
||||
get_field_for_primary_key, get_fields_for_create_model, get_fields_for_edit_model,
|
||||
get_fields_for_from_model, get_fields_for_tokenstream, get_fields_for_validate_model,
|
||||
get_primary_key_field_name,
|
||||
get_actix_admin_fields_select_list,
|
||||
get_actix_admin_fields_is_option_list,
|
||||
get_fields_for_validate_model,
|
||||
get_actix_admin_fields_searchable
|
||||
};
|
||||
|
||||
mod selectlist_fields;
|
||||
use selectlist_fields::{
|
||||
get_select_list,
|
||||
get_select_lists
|
||||
};
|
||||
use selectlist_fields::{get_select_list, get_select_lists};
|
||||
|
||||
mod model_fields;
|
||||
mod attributes;
|
||||
mod model_fields;
|
||||
|
||||
#[proc_macro_derive(DeriveActixAdminSelectList, attributes(actix_admin))]
|
||||
pub fn derive_actix_admin_select_list(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
@ -46,6 +37,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
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 fields_type_path = get_actix_admin_fields_type_path_string(&fields);
|
||||
|
||||
let select_lists = get_select_lists(&fields);
|
||||
|
||||
@ -120,7 +112,6 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
// TODO: separate primary key from other keys
|
||||
let entity = Entity::find_by_id(id).one(db).await.unwrap().unwrap();
|
||||
let model = ActixAdminModel::from(entity);
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
@ -134,10 +125,8 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
let mut entity: ActiveModel = entity.unwrap().into();
|
||||
|
||||
#(#fields_for_edit_model);*;
|
||||
|
||||
let entity: Model = entity.update(db).await.unwrap();
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
@ -187,12 +176,9 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
(num_pages, model_entities)
|
||||
}
|
||||
|
||||
|
||||
fn validate_model(model: &ActixAdminModel) -> HashMap<String, String> {
|
||||
let mut errors = HashMap::<String, String>::new();
|
||||
|
||||
#(#fields_for_validate_model);*;
|
||||
|
||||
//let mut custom_errors = Entity.validate();
|
||||
//errors.append(&mut custom_errors);
|
||||
errors
|
||||
@ -205,7 +191,7 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
#(#field_names),*
|
||||
).split(",")
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
let html_input_types = stringify!(
|
||||
#(#field_html_input_type),*
|
||||
).split(",")
|
||||
@ -220,14 +206,19 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
#(#is_option_list),*
|
||||
];
|
||||
|
||||
for (field_name, html_input_type, select_list, is_option_list) in izip!(&field_names, &html_input_types, &field_select_lists, is_option_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(),
|
||||
is_option: is_option_list
|
||||
});
|
||||
}
|
||||
let fields_type_paths = [
|
||||
#(#fields_type_path),*
|
||||
];
|
||||
|
||||
for (field_name, html_input_type, select_list, is_option_list, fields_type_path) in izip!(&field_names, &html_input_types, &field_select_lists, is_option_lists, fields_type_paths) {
|
||||
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(),
|
||||
is_option: is_option_list,
|
||||
field_type: ActixAdminViewModelFieldType::from(fields_type_path)
|
||||
});
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
|
@ -28,4 +28,21 @@ impl ModelField {
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type_path_string(&self) -> String {
|
||||
let type_path_string: String;
|
||||
if self.is_option() {
|
||||
match &self.inner_type.clone().unwrap() {
|
||||
Type::Path(type_path) => type_path_string = type_path.clone().into_token_stream().to_string(),
|
||||
_ => panic!("not a type path")
|
||||
}
|
||||
} else {
|
||||
match &self.ty {
|
||||
Type::Path(type_path) => type_path_string = type_path.clone().into_token_stream().to_string(),
|
||||
_ => panic!("not a type path")
|
||||
}
|
||||
}
|
||||
|
||||
type_path_string
|
||||
}
|
||||
}
|
@ -43,9 +43,9 @@ pub fn filter_fields(fields: &Fields) -> Vec<ModelField> {
|
||||
(LitStr::from(attr_field)).value()
|
||||
})
|
||||
});
|
||||
let html_input_type = actix_admin_attr.map_or("text".to_string(), |attr| {
|
||||
let html_input_type = actix_admin_attr.map_or("".to_string(), |attr| {
|
||||
attr.html_input_type
|
||||
.map_or("text".to_string(), |attr_field| {
|
||||
.map_or("".to_string(), |attr_field| {
|
||||
(LitStr::from(attr_field)).value()
|
||||
})
|
||||
});
|
||||
@ -140,6 +140,20 @@ pub fn get_actix_admin_fields_is_option_list(fields: &Vec<ModelField>) -> Vec<To
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_actix_admin_fields_type_path_string(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
.filter(|model_field| !model_field.primary_key)
|
||||
.map(|model_field| {
|
||||
let type_path_string = model_field.get_type_path_string();
|
||||
|
||||
quote! {
|
||||
#type_path_string
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn get_actix_admin_fields_html_input(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
fields
|
||||
.iter()
|
||||
|
@ -1,7 +1,8 @@
|
||||
use lazy_static::lazy_static;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::collections::HashMap;
|
||||
use tera::{Tera};
|
||||
use tera::{Tera, Result, Value, to_value, try_get_value };
|
||||
use std::{ hash::BuildHasher};
|
||||
|
||||
pub mod view_model;
|
||||
pub mod model;
|
||||
@ -11,7 +12,7 @@ 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, ActixAdminViewModelField};
|
||||
pub use crate::view_model::{ ActixAdminViewModel, ActixAdminViewModelTrait, ActixAdminViewModelField, ActixAdminViewModelFieldType };
|
||||
pub use actix_admin_macros::{ DeriveActixAdminModel, DeriveActixAdminSelectList };
|
||||
pub use crate::{ ActixAdminAppDataTrait, ActixAdmin };
|
||||
pub use crate::{ hashmap, ActixAdminSelectListTrait };
|
||||
@ -30,10 +31,43 @@ macro_rules! hashmap {
|
||||
|
||||
// globals
|
||||
lazy_static! {
|
||||
static ref TERA: Tera =
|
||||
Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")).unwrap();
|
||||
tera.register_filter("get_html_input_type", get_html_input_type);
|
||||
tera.register_filter("get_html_input_class", get_html_input_class);
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_html_input_class<S: BuildHasher>(value: &tera::Value, _: &HashMap<String, tera::Value, S>) -> Result<tera::Value> {
|
||||
let field = try_get_value!("get_html_input_class", "value", ActixAdminViewModelField, value);
|
||||
let html_input_type = match field.field_type {
|
||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||
_ => "input"
|
||||
};
|
||||
|
||||
Ok(to_value(html_input_type).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_html_input_type<S: BuildHasher>(value: &tera::Value, _: &HashMap<String, tera::Value, S>) -> Result<tera::Value> {
|
||||
let field = try_get_value!("get_html_input_type", "value", ActixAdminViewModelField, value);
|
||||
|
||||
// TODO: convert to option
|
||||
if field.html_input_type != "" {
|
||||
return Ok(to_value(field.html_input_type).unwrap())
|
||||
}
|
||||
|
||||
let html_input_type = match field.field_type {
|
||||
ActixAdminViewModelFieldType::Text => "text",
|
||||
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||
_ => "text"
|
||||
};
|
||||
|
||||
Ok(to_value(html_input_type).unwrap())
|
||||
}
|
||||
|
||||
|
||||
// AppDataTrait
|
||||
pub trait ActixAdminAppDataTrait {
|
||||
fn get_db(&self) -> &DatabaseConnection;
|
||||
|
@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::{Serialize};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::HashMap;
|
||||
use crate::ActixAdminModel;
|
||||
use std::convert::From;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait ActixAdminViewModelTrait {
|
||||
@ -34,10 +35,37 @@ pub struct ActixAdminViewModel {
|
||||
pub fields: Vec<ActixAdminViewModelField>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ActixAdminViewModelFieldType {
|
||||
Number,
|
||||
Text,
|
||||
TextArea,
|
||||
Checkbox,
|
||||
Date,
|
||||
Time,
|
||||
DateTime,
|
||||
SelectList
|
||||
}
|
||||
|
||||
impl From<&str> for ActixAdminViewModelFieldType {
|
||||
fn from(input: &str) -> ActixAdminViewModelFieldType {
|
||||
match input {
|
||||
"i32" => ActixAdminViewModelFieldType::Number,
|
||||
"i64" => ActixAdminViewModelFieldType::Number,
|
||||
"usize" => ActixAdminViewModelFieldType::Number,
|
||||
"String" => ActixAdminViewModelFieldType::Text,
|
||||
"bool" => ActixAdminViewModelFieldType::Checkbox,
|
||||
"DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime,
|
||||
_ => ActixAdminViewModelFieldType::Text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ActixAdminViewModelField {
|
||||
pub field_name: String,
|
||||
pub html_input_type: String,
|
||||
pub select_list: String,
|
||||
pub is_option: bool
|
||||
pub is_option: bool,
|
||||
pub field_type: ActixAdminViewModelFieldType
|
||||
}
|
@ -13,27 +13,50 @@
|
||||
<title>Actix Admin</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css"
|
||||
integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<script src="https://unpkg.com/htmx.org@1.7.0"></script>
|
||||
|
||||
<script>
|
||||
function checkAll(bx) {
|
||||
var cbs = document.getElementsByTagName('input');
|
||||
for(var i=0; i < cbs.length; i++) {
|
||||
if(cbs[i].type == 'checkbox') {
|
||||
cbs[i].checked = bx.checked;
|
||||
for (var i = 0; i < cbs.length; i++) {
|
||||
if (cbs[i].type == 'checkbox') {
|
||||
cbs[i].checked = bx.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Get all "navbar-burger" elements
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
|
||||
// Get the target from the "data-target" attribute
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% include "header.html" %}
|
||||
<div class="container is-fluid">
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</div>
|
||||
{% include "header.html" %}
|
||||
<div class="container is-fluid">
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<form method="post">
|
||||
{% for model_field in view_model.fields -%}
|
||||
<div class="field">
|
||||
<label for="{{ model_field.field_name }}">
|
||||
<label class="{{ model_field | get_html_input_type }}" for="{{ model_field.field_name }}">
|
||||
{{ model_field.field_name | split(pat="_") | join(sep=" ") | title }}
|
||||
</label>
|
||||
{% if model_field.select_list != "" %}
|
||||
@ -26,8 +26,8 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="control">
|
||||
<input class="input {% if model.errors | get(key=model_field.field_name,default="" ) !="" %}is-danger{% endif %}"
|
||||
type="{{ model_field.html_input_type }}"
|
||||
<input class="{{ model_field | get_html_input_class }} {% if model.errors | get(key=model_field.field_name,default="" ) !="" %}is-danger{% endif %}"
|
||||
type="{{ model_field | get_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 }}"
|
||||
|
@ -1,7 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 480 B |
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
@ -11,10 +11,11 @@ pub struct Model {
|
||||
pub id: i32,
|
||||
pub comment: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
#[actix_admin(html_input_type = "email")]
|
||||
pub user: String,
|
||||
#[sea_orm(column_type = "DateTime")]
|
||||
#[actix_admin(html_input_type = "datetime-local")]
|
||||
pub insert_date: DateTimeWithTimeZone
|
||||
pub insert_date: DateTimeWithTimeZone,
|
||||
pub is_visible: bool
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
@ -44,6 +44,7 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
|
||||
.col(ColumnDef::new(comment::Column::Comment).string().not_null())
|
||||
.col(ColumnDef::new(comment::Column::User).string().not_null())
|
||||
.col(ColumnDef::new(comment::Column::InsertDate).date_time().not_null())
|
||||
.col(ColumnDef::new(comment::Column::IsVisible).boolean().not_null())
|
||||
.to_owned();
|
||||
|
||||
create_table(db, &stmt).await
|
||||
|
@ -1,9 +1,9 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use actix_admin::prelude::*;
|
||||
use std::str::FromStr;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize, DeriveActixAdminModel)]
|
||||
#[sea_orm(table_name = "post")]
|
||||
|
Loading…
Reference in New Issue
Block a user