parse datetime and bool correctly
This commit is contained in:
parent
5aa8bc809d
commit
fb4cd9f7d7
@ -7,6 +7,7 @@ edition = "2021"
|
||||
actix-web = "4.0.1"
|
||||
actix-multipart = "0.4.0"
|
||||
futures-util = "0.3.21"
|
||||
chrono = "0.4.20"
|
||||
tera = "1.16.0"
|
||||
actix_admin_macros = { path = "actix_admin_macros" }
|
||||
async-trait = "0.1.53"
|
||||
|
@ -211,12 +211,17 @@ pub fn derive_crud_fns(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
|
||||
];
|
||||
|
||||
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) {
|
||||
|
||||
let select_list = select_list.replace('"', "").replace(' ', "").to_string();
|
||||
let field_name = field_name.replace('"', "").replace(' ', "").to_string();
|
||||
let html_input_type = html_input_type.replace('"', "").replace(' ', "").to_string();
|
||||
|
||||
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(),
|
||||
field_name: field_name,
|
||||
html_input_type: html_input_type,
|
||||
select_list: select_list.clone(),
|
||||
is_option: is_option_list,
|
||||
field_type: ActixAdminViewModelFieldType::from(fields_type_path)
|
||||
field_type: ActixAdminViewModelFieldType::get_field_type(fields_type_path, select_list)
|
||||
});
|
||||
}
|
||||
vec
|
||||
|
@ -254,22 +254,36 @@ pub fn get_fields_for_validate_model(fields: &Vec<ModelField>) -> Vec<TokenStrea
|
||||
.map(|model_field| {
|
||||
let ident_name = model_field.ident.to_string();
|
||||
let ty = model_field.ty.to_owned();
|
||||
let type_path = model_field.get_type_path_string();
|
||||
|
||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||
|
||||
match model_field.is_option() {
|
||||
true => {
|
||||
let res = match (model_field.is_option(), type_path.as_str()) {
|
||||
(_, "DateTime") => {
|
||||
quote! {
|
||||
model.get_datetime(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||
}
|
||||
},
|
||||
(_, "bool") => {
|
||||
quote! {
|
||||
model.get_bool(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||
}
|
||||
},
|
||||
// generic
|
||||
(true, _) => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||
}
|
||||
},
|
||||
false => {
|
||||
(false, _) => {
|
||||
quote! {
|
||||
model.get_value::<#ty>(#ident_name, #is_option_or_string).map_err(|err| errors.insert(#ident_name.to_string(), err)).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@ -283,30 +297,49 @@ pub fn get_fields_for_create_model(fields: &Vec<ModelField>) -> Vec<TokenStream>
|
||||
let ident_name = model_field.ident.to_string();
|
||||
let ident = model_field.ident.to_owned();
|
||||
let ty = model_field.ty.to_owned();
|
||||
let type_path = model_field.get_type_path_string();
|
||||
|
||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||
|
||||
match model_field.is_option() {
|
||||
true => {
|
||||
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
||||
// is DateTime
|
||||
(true , _, "DateTime") => {
|
||||
quote! {
|
||||
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap())
|
||||
}
|
||||
},
|
||||
(false , _, "DateTime") => {
|
||||
quote! {
|
||||
#ident: Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
(_ , _, "bool") => {
|
||||
quote! {
|
||||
#ident: Set(model.get_bool(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
// Default fields
|
||||
(true, _, _) => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
||||
}
|
||||
}
|
||||
// TODO: this fails for empty string as it returns None which then cannot be unwrapped
|
||||
// Should do maybe typecheck and if it is string return "" instead of None
|
||||
false => {
|
||||
if model_field.is_string() {
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
// is string which can be empty
|
||||
(false, true, _) => {
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||
}
|
||||
},
|
||||
// no string
|
||||
(false, false, _) => {
|
||||
quote! {
|
||||
#ident: Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@ -320,28 +353,45 @@ pub fn get_fields_for_edit_model(fields: &Vec<ModelField>) -> Vec<TokenStream> {
|
||||
let ident_name = model_field.ident.to_string();
|
||||
let ident = model_field.ident.to_owned();
|
||||
let ty = model_field.ty.to_owned();
|
||||
let type_path = model_field.get_type_path_string();
|
||||
|
||||
let is_option_or_string = model_field.is_option() || model_field.is_string();
|
||||
|
||||
match model_field.is_option() {
|
||||
true => {
|
||||
let res = match (model_field.is_option(), model_field.is_string(), type_path.as_str()) {
|
||||
(_, _, "bool") => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_bool(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
(true , _, "DateTime") => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap())
|
||||
}
|
||||
},
|
||||
(false , _, "DateTime") => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_datetime(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
(true, _, _) => {
|
||||
let inner_ty = model_field.inner_type.to_owned().unwrap();
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#inner_ty>(#ident_name, #is_option_or_string).unwrap())
|
||||
}
|
||||
}
|
||||
false => {
|
||||
if model_field.is_string() {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||
}
|
||||
},
|
||||
(false, true, _) => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap_or(String::new()))
|
||||
}
|
||||
else {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
},
|
||||
(false, false, _) => {
|
||||
quote! {
|
||||
entity.#ident = Set(model.get_value::<#ty>(#ident_name, #is_option_or_string).unwrap().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use lazy_static::lazy_static;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use std::collections::HashMap;
|
||||
use tera::{Tera, Result, Value, to_value, try_get_value };
|
||||
use tera::{Tera, Result, to_value, try_get_value };
|
||||
use std::{ hash::BuildHasher};
|
||||
|
||||
pub mod view_model;
|
||||
@ -35,6 +35,7 @@ lazy_static! {
|
||||
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.register_filter("get_icon", get_icon);
|
||||
tera
|
||||
};
|
||||
}
|
||||
@ -49,6 +50,17 @@ pub fn get_html_input_class<S: BuildHasher>(value: &tera::Value, _: &HashMap<Str
|
||||
Ok(to_value(html_input_type).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_icon<S: BuildHasher>(value: &tera::Value, _: &HashMap<String, tera::Value, S>) -> Result<tera::Value> {
|
||||
let field = try_get_value!("get_icon", "value", String, value);
|
||||
let font_awesome_icon = match field.as_str() {
|
||||
"true" => "<i class=\"fa-solid fa-check\"></i>",
|
||||
"false" => "<i class=\"fa-solid fa-xmark\"></i>",
|
||||
_ => panic!("not implemented icon")
|
||||
};
|
||||
|
||||
Ok(to_value(font_awesome_icon).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);
|
||||
|
||||
@ -60,6 +72,7 @@ pub fn get_html_input_type<S: BuildHasher>(value: &tera::Value, _: &HashMap<Stri
|
||||
let html_input_type = match field.field_type {
|
||||
ActixAdminViewModelFieldType::Text => "text",
|
||||
ActixAdminViewModelFieldType::DateTime => "datetime-local",
|
||||
ActixAdminViewModelFieldType::Date => "date",
|
||||
ActixAdminViewModelFieldType::Checkbox => "checkbox",
|
||||
_ => "text"
|
||||
};
|
||||
|
@ -5,6 +5,10 @@ use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use actix_multipart:: {Multipart, MultipartError} ;
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use chrono::{NaiveDateTime, NaiveDate};
|
||||
use sea_orm::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
#[async_trait]
|
||||
pub trait ActixAdminModelTrait {
|
||||
@ -29,6 +33,7 @@ pub struct ActixAdminModel {
|
||||
pub errors: HashMap<String, String>,
|
||||
}
|
||||
|
||||
|
||||
impl ActixAdminModel {
|
||||
pub fn create_empty() -> ActixAdminModel {
|
||||
ActixAdminModel {
|
||||
@ -65,6 +70,23 @@ impl ActixAdminModel {
|
||||
}
|
||||
|
||||
pub fn get_value<T: std::str::FromStr>(&self, key: &str, is_option_or_string: bool) -> Result<Option<T>, String> {
|
||||
self.get_value_by_closure(key, is_option_or_string, |val| val.parse::<T>())
|
||||
}
|
||||
|
||||
pub fn get_datetime(&self, key: &str, is_option_or_string: bool) -> Result<Option<DateTime>, String> {
|
||||
self.get_value_by_closure(key, is_option_or_string, |val| NaiveDateTime::parse_from_str(val, "%Y-%m-%dT%H:%M"))
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, key: &str, is_option_or_string: bool) -> Result<Option<bool>, String> {
|
||||
let val = self.get_value_by_closure(key, is_option_or_string, |val| if !val.is_empty() { Ok(true) } else { Ok(false) });
|
||||
// not selected bool field equals to false and not to missing
|
||||
match val {
|
||||
Ok(val) => Ok(val),
|
||||
Err(_) => Ok(Some(false))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_value_by_closure<T: std::str::FromStr>(&self, key: &str, is_option_or_string: bool, f: impl Fn(&String) -> Result<T, <T as std::str::FromStr>::Err>) -> Result<Option<T>, String> {
|
||||
let value = self.values.get(key);
|
||||
|
||||
let res: Result<Option<T>, String> = match value {
|
||||
@ -73,7 +95,7 @@ impl ActixAdminModel {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let parsed_val = val.parse::<T>();
|
||||
let parsed_val = f(val);
|
||||
println!("{:?}", val);
|
||||
|
||||
match parsed_val {
|
||||
|
@ -47,20 +47,6 @@ pub enum ActixAdminViewModelFieldType {
|
||||
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,
|
||||
@ -68,4 +54,25 @@ pub struct ActixAdminViewModelField {
|
||||
pub select_list: String,
|
||||
pub is_option: bool,
|
||||
pub field_type: ActixAdminViewModelFieldType
|
||||
}
|
||||
|
||||
impl ActixAdminViewModelFieldType {
|
||||
pub fn get_field_type(type_path: &str, select_list: String) -> ActixAdminViewModelFieldType {
|
||||
if !select_list.is_empty() {
|
||||
println!("field_type {} {}", type_path, select_list);
|
||||
return ActixAdminViewModelFieldType::SelectList;
|
||||
}
|
||||
|
||||
match type_path {
|
||||
"i32" => ActixAdminViewModelFieldType::Number,
|
||||
"i64" => ActixAdminViewModelFieldType::Number,
|
||||
"usize" => ActixAdminViewModelFieldType::Number,
|
||||
"String" => ActixAdminViewModelFieldType::Text,
|
||||
"bool" => ActixAdminViewModelFieldType::Checkbox,
|
||||
"DateTimeWithTimeZone" => ActixAdminViewModelFieldType::DateTime,
|
||||
"DateTime" => ActixAdminViewModelFieldType::DateTime,
|
||||
"Date" => ActixAdminViewModelFieldType::Date,
|
||||
_ => ActixAdminViewModelFieldType::Text
|
||||
}
|
||||
}
|
||||
}
|
@ -7,34 +7,15 @@
|
||||
<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 != "" %}
|
||||
<div class="control">
|
||||
<div class="select {% if model.errors | get(key=model_field.field_name, default="" ) !="" %}is-danger{% endif %}">
|
||||
<select name="{{ model_field.field_name }}">
|
||||
{% if model_field.is_option %}
|
||||
<option value=""></option>
|
||||
{% else %}
|
||||
<option value="" selected disabled>Select</option>
|
||||
{% endif %}
|
||||
{% 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, default="" ) %}
|
||||
selected {% endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] | split(pat="_") |
|
||||
join(sep=" ") | title }}</option>
|
||||
{%- endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if model_field.field_type == "SelectList" %}
|
||||
{% include "form_elements/selectlist.html" %}
|
||||
{% elif model_field.field_type == "Checkbox" %}
|
||||
{% include "form_elements/checkbox.html" %}
|
||||
{% else %}
|
||||
<div class="control">
|
||||
<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 }}"
|
||||
aria-label="{{ model_field.field_name }}"
|
||||
>
|
||||
</div>
|
||||
{% include "form_elements/input.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endfor %}
|
||||
<div class="field is-grouped">
|
||||
|
9
actix_admin/templates/form_elements/checkbox.html
Normal file
9
actix_admin/templates/form_elements/checkbox.html
Normal file
@ -0,0 +1,9 @@
|
||||
<input
|
||||
class="{{ model_field | get_html_input_class }}"
|
||||
type="{{ model_field | get_html_input_type }}"
|
||||
value="true"
|
||||
name="{{ model_field.field_name }}"
|
||||
placeholder="{{ model_field.field_name }}"
|
||||
aria-label="{{ model_field.field_name }}"
|
||||
{% if model.values | get(key=model_field.field_name) == "true" %}checked{% endif %}
|
||||
>
|
8
actix_admin/templates/form_elements/input.html
Normal file
8
actix_admin/templates/form_elements/input.html
Normal file
@ -0,0 +1,8 @@
|
||||
<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 }}"
|
||||
aria-label="{{ model_field.field_name }}"
|
||||
>
|
14
actix_admin/templates/form_elements/selectlist.html
Normal file
14
actix_admin/templates/form_elements/selectlist.html
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="select {% if model.errors | get(key=model_field.field_name, default="" ) !="" %}is-danger{% endif %}">
|
||||
<select name="{{ model_field.field_name }}">
|
||||
{% if model_field.is_option %}
|
||||
<option value=""></option>
|
||||
{% else %}
|
||||
<option value="" selected disabled>Select</option>
|
||||
{% endif %}
|
||||
{% 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, default="" ) %} selected {%
|
||||
endif %} value="{{ select_list_item[0] }}">{{ select_list_item[1] | split(pat="_") |
|
||||
join(sep=" ") | title }}</option>
|
||||
{%- endfor %}
|
||||
</select>
|
||||
</div>
|
@ -86,7 +86,11 @@
|
||||
<td><input type="checkbox" name="ids" value="{{ entity.primary_key }}"></td>
|
||||
<td>{{ entity.primary_key }}</td>
|
||||
{% for model_field in view_model.fields -%}
|
||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||
{% if model_field.field_type == "Checkbox" %}
|
||||
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
||||
{% else %}
|
||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
<td>
|
||||
<a href="edit/{{ entity.primary_key }}"><i class="fa-solid fa-pen-to-square"></i></a>
|
||||
|
BIN
database.db-wal
BIN
database.db-wal
Binary file not shown.
@ -14,7 +14,7 @@ pub struct Model {
|
||||
#[actix_admin(html_input_type = "email")]
|
||||
pub user: String,
|
||||
#[sea_orm(column_type = "DateTime")]
|
||||
pub insert_date: DateTimeWithTimeZone,
|
||||
pub insert_date: DateTime,
|
||||
pub is_visible: bool
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user