edit form save almost working
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import {castId, keyBy, objCopy, isEmpty} from "../utils";
|
||||
import forEach from "lodash-es/forEach";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
@@ -29,7 +30,6 @@ export default {
|
||||
values[p.id] = [
|
||||
// this is the format used for values
|
||||
{
|
||||
id: null,
|
||||
// it can also have model: ... here
|
||||
value: objCopy(p.default)
|
||||
}
|
||||
@@ -67,7 +67,11 @@ export default {
|
||||
let values = [];
|
||||
forEach(objCopy(this.values), (vv, prop_model_id) => {
|
||||
for (let v of vv) {
|
||||
v.model_id = castId(prop_model_id);
|
||||
if (isEqual(v.value, {"String": ""}) && this.properties[prop_model_id].optional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
v.model = castId(prop_model_id);
|
||||
values.push(v);
|
||||
}
|
||||
})
|
||||
@@ -80,7 +84,7 @@ export default {
|
||||
}
|
||||
|
||||
return {
|
||||
model_id: this.object.model, // string is fine
|
||||
model: this.object.model, // string is fine
|
||||
id: this.object.id,
|
||||
name: this.name,
|
||||
values,
|
||||
@@ -100,13 +104,13 @@ export default {
|
||||
|
||||
axios({
|
||||
method: 'post',
|
||||
url: '/object/update',
|
||||
url: `/object/update/${this.object.id}`,
|
||||
data: data
|
||||
})
|
||||
.then(function (response) {
|
||||
location.href = '/object/detail/'+this.object.id;
|
||||
.then((response) => {
|
||||
location.href = `/object/detail/${this.object.id}`;
|
||||
})
|
||||
.catch(function (error) {
|
||||
.catch((error) => {
|
||||
// TODO show error toast instead
|
||||
alert(error.response ?
|
||||
error.response.data :
|
||||
|
||||
@@ -7,14 +7,14 @@ export default {
|
||||
props: ['model', 'values'],
|
||||
data() {
|
||||
return {
|
||||
id: uniqueId(),
|
||||
widget_id: uniqueId(),
|
||||
fieldRefs: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addValue(event) {
|
||||
this.values.push({
|
||||
id: null,
|
||||
model: this.model.id,
|
||||
value: objCopy(this.model.default)
|
||||
});
|
||||
|
||||
@@ -45,12 +45,12 @@ export default {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else v-for="(instance, vi) in values" :key="vi">
|
||||
<th :rowspan="values.length + model.multiple" v-if="vi == 0"><label :for="id">{{model.name}}</label></th>
|
||||
<th :rowspan="values.length + model.multiple" v-if="vi == 0"><label :for="widget_id">{{model.name}}</label></th>
|
||||
<td>
|
||||
<string-value :ref="setFieldRef" :value="instance.value" :id="vi===0?id:null" v-if="model.data_type==='String'"></string-value>
|
||||
<integer-value :ref="setFieldRef" :value="instance.value" :id="vi===0?id:null" v-if="model.data_type==='Integer'"></integer-value>
|
||||
<decimal-value :ref="setFieldRef" :value="instance.value" :id="vi===0?id:null" v-if="model.data_type==='Decimal'"></decimal-value>
|
||||
<boolean-value :ref="setFieldRef" :value="instance.value" :id="vi===0?id:null" v-if="model.data_type==='Boolean'"></boolean-value>
|
||||
<string-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='String'"></string-value>
|
||||
<integer-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Integer'"></integer-value>
|
||||
<decimal-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Decimal'"></decimal-value>
|
||||
<boolean-value :ref="setFieldRef" :value="instance.value" :id="vi===0?widget_id:null" v-if="model.data_type==='Boolean'"></boolean-value>
|
||||
<a href="#" @click="removeValue(vi)" v-if="vi > 0 || model.optional" style="margin-left:5px">X</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import {castId, isEmpty, keyBy, objCopy} from "../utils";
|
||||
import forEach from "lodash-es/forEach";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
|
||||
export default {
|
||||
props: ['model_id', 'schema', 'objects', 'initialInstances'],
|
||||
@@ -14,7 +15,7 @@ export default {
|
||||
properties.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
if (isEmpty(properties)) {
|
||||
properties = null;
|
||||
properties = [];
|
||||
} else {
|
||||
properties = keyBy(properties, 'id');
|
||||
}
|
||||
@@ -55,11 +56,15 @@ export default {
|
||||
let values = [];
|
||||
forEach(instance.values, (vv, prop_model_id) => {
|
||||
for (let v of vv) {
|
||||
v.model_id = castId(prop_model_id);
|
||||
if (isEqual(v.value, {"String": ""}) && this.properties[prop_model_id].optional) {
|
||||
continue;
|
||||
}
|
||||
v.model = castId(prop_model_id);
|
||||
values.push(v);
|
||||
}
|
||||
})
|
||||
instance.model_id = this.model.id;
|
||||
instance.related = castId(instance.related);
|
||||
instance.model = this.model.id;
|
||||
instance.values = values;
|
||||
relations.push(instance);
|
||||
})
|
||||
@@ -78,7 +83,6 @@ export default {
|
||||
}
|
||||
});
|
||||
this.instances.push({
|
||||
id: null,
|
||||
related: '',
|
||||
values
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import {castId, keyBy, objCopy, isEmpty} from "../utils";
|
||||
import forEach from "lodash-es/forEach";
|
||||
import axios from "axios";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
|
||||
export default {
|
||||
props: ['model_id', 'schema', 'objects'],
|
||||
@@ -50,10 +51,14 @@ export default {
|
||||
}
|
||||
|
||||
let values = [];
|
||||
forEach(objCopy(this.values), (vv, k) => {
|
||||
forEach(objCopy(this.values), (vv, prop_model_id) => {
|
||||
for (let v of vv) {
|
||||
if (isEqual(v, {"String": ""}) && this.properties[prop_model_id].optional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
values.push({
|
||||
model_id: castId(k),
|
||||
model: castId(prop_model_id),
|
||||
value: v
|
||||
})
|
||||
}
|
||||
@@ -67,7 +72,7 @@ export default {
|
||||
}
|
||||
|
||||
return {
|
||||
model_id: this.model_id, // string is fine
|
||||
model: this.model.id,
|
||||
name: this.name,
|
||||
values,
|
||||
relations,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import {castId, keyBy, objCopy, isEmpty} from "../utils";
|
||||
import forEach from "lodash-es/forEach";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
|
||||
export default {
|
||||
props: ['model_id', 'schema', 'objects'],
|
||||
@@ -14,7 +15,7 @@ export default {
|
||||
properties.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
if (isEmpty(properties)) {
|
||||
properties = null;
|
||||
properties = [];
|
||||
} else {
|
||||
properties = keyBy(properties, 'id');
|
||||
}
|
||||
@@ -70,16 +71,20 @@ export default {
|
||||
forEach(instance.values, (vv, prop_model_id) => {
|
||||
|
||||
for (let v of vv) {
|
||||
if (isEqual(v, {"String": ""}) && this.properties[prop_model_id].optional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
values.push({
|
||||
model_id: castId(prop_model_id),
|
||||
model: castId(prop_model_id),
|
||||
value: v
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
relations.push({
|
||||
model_id: this.model.id,
|
||||
related_id: castId(instance.related),
|
||||
model: this.model.id,
|
||||
related: castId(instance.related),
|
||||
values
|
||||
});
|
||||
})
|
||||
@@ -96,6 +101,7 @@ export default {
|
||||
}
|
||||
});
|
||||
this.instances.push({
|
||||
model: this.model.id,
|
||||
related: '',
|
||||
values
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
props: ['model', 'values'],
|
||||
data() {
|
||||
return {
|
||||
id: uniqueId(),
|
||||
widget_id: uniqueId(),
|
||||
fieldRefs: [],
|
||||
}
|
||||
},
|
||||
@@ -42,12 +42,12 @@ export default {
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else v-for="(value, vi) in values" :key="vi">
|
||||
<th :rowspan="values.length + model.multiple" v-if="vi == 0"><label :for="id">{{model.name}}</label></th>
|
||||
<th :rowspan="values.length + model.multiple" v-if="vi == 0"><label :for="widget_id">{{model.name}}</label></th>
|
||||
<td>
|
||||
<string-value :ref="setFieldRef" :value="value" :id="vi===0?id:null" v-if="model.data_type==='String'"></string-value>
|
||||
<integer-value :ref="setFieldRef" :value="value" :id="vi===0?id:null" v-if="model.data_type==='Integer'"></integer-value>
|
||||
<decimal-value :ref="setFieldRef" :value="value" :id="vi===0?id:null" v-if="model.data_type==='Decimal'"></decimal-value>
|
||||
<boolean-value :ref="setFieldRef" :value="value" :id="vi===0?id:null" v-if="model.data_type==='Boolean'"></boolean-value>
|
||||
<string-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='String'"></string-value>
|
||||
<integer-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Integer'"></integer-value>
|
||||
<decimal-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Decimal'"></decimal-value>
|
||||
<boolean-value :ref="setFieldRef" :value="value" :id="vi===0?widget_id:null" v-if="model.data_type==='Boolean'"></boolean-value>
|
||||
<a href="#" @click="removeValue(vi)" v-if="vi > 0 || model.optional" style="margin-left:5px">X</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@
|
||||
|
||||
{% block nav -%}
|
||||
<a href="/">Home</a>
|
||||
<a href="/object/update/{{object.id}}">Edit</a>
|
||||
{%- endblock %}
|
||||
|
||||
{% block content -%}
|
||||
@@ -35,7 +36,7 @@
|
||||
<ul>
|
||||
{% for instance in relation.instances %}
|
||||
<li>
|
||||
<b>{{instance.related.name}}</b>
|
||||
<b><a href="/object/detail/{{instance.related.id}}">{{instance.related.name}}</a></b>
|
||||
|
||||
{% if instance.properties %}
|
||||
<br>
|
||||
@@ -61,4 +62,37 @@
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
{% for relation in reciprocal_relations %}
|
||||
<h3>{{relation.model.reciprocal_name}}</h3>
|
||||
|
||||
<ul>
|
||||
{% for instance in relation.instances %}
|
||||
<li>
|
||||
<b><a href="/object/detail/{{instance.related.id}}">{{instance.related.name}}</a></b>
|
||||
|
||||
{% if instance.properties %}
|
||||
<br>
|
||||
<small>
|
||||
{% for property in instance.properties %}
|
||||
{%- if 0 != loop.index0 -%}
|
||||
;
|
||||
{%- endif -%}
|
||||
{% for value in property.values %}
|
||||
{%- if loop.first -%}
|
||||
<i>{{ property.model.name }}:</i>
|
||||
{%- else -%}
|
||||
,
|
||||
{% endif -%}
|
||||
{{ value.value | print_typed_value }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
@@ -159,6 +159,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(routes::objects::create)
|
||||
.service(routes::objects::detail)
|
||||
.service(routes::objects::update_form)
|
||||
.service(routes::objects::update)
|
||||
//
|
||||
.service(static_files)
|
||||
.default_service(web::to(|| HttpResponse::NotFound().body("File or endpoint not found")))
|
||||
@@ -241,35 +242,35 @@ fn init_yopa() -> YopaStoreWrapper {
|
||||
}).unwrap();
|
||||
|
||||
let book1 = store.insert_object(InsertObj {
|
||||
model_id: id_book,
|
||||
model: id_book,
|
||||
name: "Book 1".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![]
|
||||
}).unwrap();
|
||||
|
||||
store.insert_object(InsertObj {
|
||||
model_id: id_book,
|
||||
model: id_book,
|
||||
name: "Book 2".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![]
|
||||
}).unwrap();
|
||||
|
||||
store.insert_object(InsertObj {
|
||||
model_id: id_recipe,
|
||||
model: id_recipe,
|
||||
name: "Recipe1".to_string(),
|
||||
values: vec![
|
||||
InsertValue {
|
||||
model_id: val_descr,
|
||||
model: val_descr,
|
||||
value: TypedValue::String("Bla bla bla".into())
|
||||
}
|
||||
],
|
||||
relations: vec![
|
||||
InsertRel {
|
||||
model_id: rel_book_id,
|
||||
related_id: book1,
|
||||
model: rel_book_id,
|
||||
related: book1,
|
||||
values: vec![
|
||||
InsertValue {
|
||||
model_id: page,
|
||||
model: page,
|
||||
value: TypedValue::Integer(15)
|
||||
}
|
||||
]
|
||||
|
||||
@@ -102,7 +102,7 @@ pub(crate) async fn update(
|
||||
let form = form.into_inner();
|
||||
|
||||
let id = model_id.into_inner();
|
||||
match wg.update_object(ObjectModel {
|
||||
match wg.update_object_model(ObjectModel {
|
||||
id,
|
||||
name: form.name.clone(),
|
||||
}) {
|
||||
|
||||
@@ -244,7 +244,7 @@ pub(crate) async fn update(
|
||||
}
|
||||
};
|
||||
|
||||
match wg.update_property(PropertyModel {
|
||||
match wg.update_property_model(PropertyModel {
|
||||
id,
|
||||
object: Default::default(), // dummy
|
||||
name: form.name.clone(),
|
||||
|
||||
@@ -174,7 +174,7 @@ pub(crate) async fn update(
|
||||
let form = form.into_inner();
|
||||
|
||||
let id = model_id.into_inner();
|
||||
match wg.update_relation(RelationModel {
|
||||
match wg.update_relation_model(RelationModel {
|
||||
id,
|
||||
object: Default::default(), // dummy
|
||||
name: form.name.clone(),
|
||||
|
||||
+103
-11
@@ -4,10 +4,10 @@ use crate::session_ext::SessionExt;
|
||||
use crate::routes::models::object::ObjectModelForm;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use yopa::{ID, model, Storage, data};
|
||||
use yopa::{ID, model, Storage, data, TypedValue};
|
||||
use yopa::data::Object;
|
||||
use serde::{Serialize,Deserialize};
|
||||
use yopa::insert::InsertObj;
|
||||
use yopa::insert::{InsertObj, InsertValue};
|
||||
use crate::utils::redirect;
|
||||
use actix_web::web::Json;
|
||||
use serde_json::Value;
|
||||
@@ -16,6 +16,7 @@ use yopa::model::{ObjectModel, PropertyModel, RelationModel};
|
||||
use heck::TitleCase;
|
||||
use std::collections::HashMap;
|
||||
use json_dotpath::DotPaths;
|
||||
use yopa::update::UpdateObj;
|
||||
|
||||
// we only need references here, Context serializes everything to Value.
|
||||
// cloning would be a waste of cycles
|
||||
@@ -102,7 +103,7 @@ pub(crate) async fn create(
|
||||
let form = form.into_inner();
|
||||
let name = form.name.clone();
|
||||
|
||||
let model_name = wg.get_model_name(form.model_id).to_owned().to_title_case();
|
||||
let model_name = wg.get_model_name(form.model).to_owned().to_title_case();
|
||||
|
||||
match wg.insert_object(form) {
|
||||
Ok(_id) => {
|
||||
@@ -174,11 +175,13 @@ struct RelationInstanceView<'a> {
|
||||
#[get("/object/detail/{id}")]
|
||||
pub(crate) async fn detail(
|
||||
id: web::Path<ID>,
|
||||
store: crate::YopaStoreWrapper
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let object_id = *id;
|
||||
|
||||
let mut context = tera::Context::new();
|
||||
session.render_flash(&mut context);
|
||||
|
||||
let rg = store.read().await;
|
||||
|
||||
@@ -192,14 +195,15 @@ pub(crate) async fn detail(
|
||||
context.insert("model", model);
|
||||
context.insert("kind", &rg.get_model_name(object.model));
|
||||
|
||||
let relations = rg.get_relations_for_object(object_id).collect_vec();
|
||||
let mut relations = rg.get_relations_for_object(object_id).collect_vec();
|
||||
let reci_relations = rg.get_reciprocal_relations_for_object(object_id).collect_vec();
|
||||
|
||||
// values by parent ID
|
||||
let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec();
|
||||
ids_to_get_values_for.extend(reci_relations.iter().map(|r| r.id));
|
||||
ids_to_get_values_for.push(object_id);
|
||||
let mut grouped_values = {
|
||||
rg.get_grouped_values_for_objects(ids_to_get_values_for)
|
||||
};
|
||||
|
||||
let mut grouped_values = rg.get_grouped_values_for_objects(ids_to_get_values_for);
|
||||
|
||||
// object's own properties
|
||||
{
|
||||
@@ -214,6 +218,9 @@ pub(crate) async fn detail(
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
view_object_properties.sort_by_key(|p| &p.model.name);
|
||||
|
||||
context.insert("properties", &view_object_properties);
|
||||
}
|
||||
|
||||
@@ -244,21 +251,77 @@ pub(crate) async fn detail(
|
||||
})
|
||||
}
|
||||
|
||||
view_rel_properties.sort_by_key(|p| &p.model.name);
|
||||
|
||||
instances.push(RelationInstanceView {
|
||||
related: related_obj,
|
||||
properties: view_rel_properties
|
||||
})
|
||||
}
|
||||
|
||||
instances.sort_by_key(|r| &r.related.name);
|
||||
|
||||
relation_views.push(RelationView {
|
||||
model: rg.get_relation_model(model_id).unwrap(),
|
||||
related_name: rg.get_model_name(model_id),
|
||||
instances
|
||||
})
|
||||
}
|
||||
|
||||
relation_views.sort_by_key(|r| &r.model.name);
|
||||
|
||||
context.insert("relations", &relation_views);
|
||||
}
|
||||
|
||||
// near-copypasta for reciprocal
|
||||
{
|
||||
let grouped_relations = reci_relations.iter()
|
||||
.into_group_map_by(|relation| relation.model);
|
||||
|
||||
let mut relation_views = vec![];
|
||||
for (model_id, relations) in grouped_relations {
|
||||
let mut instances = vec![];
|
||||
|
||||
for rel in relations {
|
||||
let related_obj = match rg.get_object(rel.object) {
|
||||
None => continue,
|
||||
Some(obj) => obj
|
||||
};
|
||||
|
||||
let mut rel_values_by_model = grouped_values
|
||||
.remove(&rel.id).unwrap_or_default().into_iter()
|
||||
.into_group_map_by(|value| value.model);
|
||||
|
||||
let mut view_rel_properties = vec![];
|
||||
for (prop_model_id, values) in rel_values_by_model {
|
||||
view_rel_properties.push(PropertyView {
|
||||
model: rg.get_property_model(prop_model_id).unwrap(),
|
||||
values
|
||||
})
|
||||
}
|
||||
|
||||
view_rel_properties.sort_by_key(|p| &p.model.name);
|
||||
|
||||
instances.push(RelationInstanceView {
|
||||
related: related_obj,
|
||||
properties: view_rel_properties
|
||||
})
|
||||
}
|
||||
|
||||
instances.sort_by_key(|r| &r.related.name);
|
||||
|
||||
relation_views.push(RelationView {
|
||||
model: rg.get_relation_model(model_id).unwrap(),
|
||||
related_name: rg.get_model_name(model_id),
|
||||
instances
|
||||
})
|
||||
}
|
||||
|
||||
relation_views.sort_by_key(|r| &r.model.reciprocal_name);
|
||||
|
||||
context.insert("reciprocal_relations", &relation_views);
|
||||
}
|
||||
|
||||
TERA.build_response("objects/object_detail", &context)
|
||||
}
|
||||
|
||||
@@ -280,9 +343,6 @@ struct EnrichedRelation<'a> {
|
||||
values: HashMap<String /* ID */, Vec<&'a data::Value>>,
|
||||
}
|
||||
|
||||
// FIXME relation values are not showing in the edit form!
|
||||
// TODO save handling
|
||||
|
||||
#[get("/object/update/{id}")]
|
||||
pub(crate) async fn update_form(
|
||||
id: web::Path<ID>,
|
||||
@@ -382,3 +442,35 @@ pub(crate) async fn update_form(
|
||||
|
||||
TERA.build_response("objects/object_update", &context)
|
||||
}
|
||||
|
||||
|
||||
#[post("/object/update/{id}")]
|
||||
pub(crate) async fn update(
|
||||
id: web::Path<ID>,
|
||||
form: web::Json<UpdateObj>,
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session,
|
||||
) -> actix_web::Result<HttpResponse> {
|
||||
warn!("{:?}", form);
|
||||
|
||||
let mut wg = store.write().await;
|
||||
let form = form.into_inner();
|
||||
let name = form.name.clone();
|
||||
|
||||
let object = wg.get_object(form.id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
|
||||
|
||||
let model_name = wg.get_model_name(object.model).to_owned().to_title_case();
|
||||
|
||||
match wg.update_object(form) {
|
||||
Ok(_id) => {
|
||||
debug!("Object created, redirecting to root");
|
||||
session.flash_success(format!("{} \"{}\" updated.", model_name, name));
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error updating model: {}", e);
|
||||
Ok(HttpResponse::BadRequest().body(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user