names are now a property and can be chosen arbitrarily

This commit is contained in:
2021-02-22 01:04:45 +01:00
parent fbd2a1ed75
commit e810724cd1
21 changed files with 697 additions and 251 deletions
+1
View File
@@ -27,6 +27,7 @@ json_dotpath = "1.0.3"
anyhow = "1.0.38"
thiserror = "1.0.24"
clap = "2"
serde_with = "1.6.4"
tokio = { version="0.2.6", features=["full"] }
@@ -86,7 +86,6 @@ export default {
return {
model: this.object.model, // string is fine
id: this.object.id,
name: this.name,
values,
relations,
};
@@ -140,13 +139,6 @@ export default {
<p><input type="button" value="Save" @click="trySave"></p>
<table>
<tr>
<th><label for="field-name">Name</label></th>
<td>
<input type="text" id="field-name" v-model="name">
</td>
</tr>
<edit-property v-for="(property, pi) in properties" :model="property" :values="values[property.id]" :key="pi"></edit-property>
</table>
@@ -39,17 +39,12 @@ export default {
haveRelations: !isEmpty(relations),
model_names,
values,
name: '',
relationRefs: [],
}
},
methods: {
/** Get values in the raw format without grouping */
collectData() {
if (isEmpty(this.name)) {
throw new Error("Name is required");
}
let values = [];
forEach(objCopy(this.values), (vv, prop_model_id) => {
for (let v of vv) {
@@ -73,7 +68,6 @@ export default {
return {
model: this.model.id,
name: this.name,
values,
relations,
};
@@ -127,13 +121,6 @@ export default {
<p><input type="button" value="Save" @click="trySave"></p>
<table>
<tr>
<th><label for="field-name">Name</label></th>
<td>
<input type="text" id="field-name" v-model="name">
</td>
</tr>
<property v-for="(property, pi) in properties" :model="property" :values="values[property.id]" :key="pi"></property>
</table>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -13,8 +13,13 @@ Define object
<h1>Define new object model</h1>
<form action="/model/object/create" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off"><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td><input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off">
</td>
</tr>
</table>
<input type="submit" value="Save">
</form>
@@ -13,8 +13,24 @@ Edit object model
<h1>Edit object model {{ model.name }}</h1>
<form action="/model/object/update/{{ model.id }}" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{ model.name }}" autocomplete="off"><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td><input type="text" id="name" name="name" value="{{ model.name }}" autocomplete="off">
</td>
</tr>
<tr>
<th><label for="name_property">Name property:</label></th>
<td>
<select name="name_property" id="name_property" autocomplete="off">
<option value=""></option>
{% for p in properties %}
<option value="{{ p.id }}" {{selected(val=old.name_property, opt=p.id)}}>{{ p.name }}</option>
{% endfor %}
</select>
</td>
</tr>
</table>
<input type="submit" value="Save">
</form>
@@ -16,28 +16,55 @@ Define property
<form action="/model/property/create" method="POST">
<input type="hidden" name="object" value="{{object.id}}">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off"><br>
<label for="optional">Optional:</label>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=old.optional)}} autocomplete="off">
<br>
<label for="multiple">Multiple:</label>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=old.multiple)}} autocomplete="off"><br>
<label for="data_type">Type:</label>
<select name="data_type" id="data_type" autocomplete="off">
<option value="String" {{selected(opt="String",val=old.data_type)}}>String</option>
<option value="Integer" {{selected(opt="Integer",val=old.data_type)}}>Integer</option>
<option value="Decimal" {{selected(opt="Decimal",val=old.data_type)}}>Decimal</option>
<option value="Boolean" {{selected(opt="Boolean",val=old.data_type)}}>Boolean</option>
</select><br>
<label for="default">Default:</label>
<input type="text" id="default" name="default" value="{{old.default}}" autocomplete="off"><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td><input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off"></td>
</tr>
<tr>
<th><label for="unique">Unique:</label></th>
<td><input type="checkbox" name="unique" id="unique" value="true" {{opt(checked=old.unique)}} autocomplete="off"></td>
</tr>
<tr>
<th><label for="optional">Optional:</label></th>
<td><input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=old.optional)}} autocomplete="off"></td>
</tr>
<tr>
<th><label for="multiple">Multiple:</label></th>
<td><input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=old.multiple)}} autocomplete="off"></td>
</tr>
<tr>
<th><label for="data_type">Type:</label></th>
<td>
<select name="data_type" id="data_type" autocomplete="off">
<option value="String" {{selected(opt="String",val=old.data_type)}}>String</option>
<option value="Integer" {{selected(opt="Integer",val=old.data_type)}}>Integer</option>
<option value="Decimal" {{selected(opt="Decimal",val=old.data_type)}}>Decimal</option>
<option value="Boolean" {{selected(opt="Boolean",val=old.data_type)}}>Boolean</option>
</select>
</td>
</tr>
<tr>
<th><label for="default">Default:</label></th>
<td><input type="text" id="default" name="default" value="{{old.default}}" autocomplete="off"></td>
</tr>
</table>
<input type="submit" value="Save">
</form>
<script>
(function () {
// multiple and unique are XORed. This is also enforced server-side
let multiple = document.getElementById('multiple');
let unique = document.getElementById('unique');
unique.addEventListener('input', function () {
multiple.checked &= !unique.checked;
})
multiple.addEventListener('input', function () {
unique.checked &= !multiple.checked;
})
})();
</script>
{%- endblock %}
@@ -13,28 +13,64 @@ Edit property
<h1>Edit property {{model.name}}</h1>
<form action="/model/property/update/{{model.id}}" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{model.name}}" autocomplete="off"><br>
<label for="optional">Optional:</label>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=model.optional)}} autocomplete="off">
<br>
<label for="multiple">Multiple:</label>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=model.multiple)}} autocomplete="off"><br>
<label for="data_type">Type:</label>
<select name="data_type" id="data_type" autocomplete="off">
<option value="String" {{selected(val=model.data_type, opt="String")}}>String</option>
<option value="Integer" {{selected(val=model.data_type, opt="Integer")}}>Integer</option>
<option value="Decimal" {{selected(val=model.data_type, opt="Decimal")}}>Decimal</option>
<option value="Boolean" {{selected(val=model.data_type, opt="Boolean")}}>Boolean</option>
</select><br>
<label for="default">Default:</label>
<input type="text" id="default" name="default" value="{{model.default | print_typed_value}}" autocomplete="off"><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td><input type="text" id="name" name="name" value="{{model.name}}" autocomplete="off"></td>
</tr>
<tr>
<th><label for="unique">Unique:</label></th>
<td>
<input type="checkbox" name="unique" id="unique" value="true" {{opt(checked=model.unique)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="optional">Optional:</label></th>
<td>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=model.optional)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="multiple">Multiple:</label></th>
<td>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=model.multiple)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="data_type">Type:</label></th>
<td>
<select name="data_type" id="data_type" autocomplete="off">
<option value="String" {{selected(val=model.data_type, opt="String")}}>String</option>
<option value="Integer" {{selected(val=model.data_type, opt="Integer")}}>Integer</option>
<option value="Decimal" {{selected(val=model.data_type, opt="Decimal")}}>Decimal</option>
<option value="Boolean" {{selected(val=model.data_type, opt="Boolean")}}>Boolean</option>
</select>
</td>
</tr>
<tr>
<th><label for="default">Default:</label></th>
<td>
<input type="text" id="default" name="default" value="{{model.default | print_typed_value}}" autocomplete="off">
</td>
</tr>
</table>
<input type="submit" value="Save">
</form>
<script>
(function () {
// multiple and unique are XORed. This is also enforced server-side
let multiple = document.getElementById('multiple');
let unique = document.getElementById('unique');
unique.addEventListener('input', function () {
multiple.checked &= !unique.checked;
})
multiple.addEventListener('input', function () {
unique.checked &= !multiple.checked;
})
})();
</script>
{%- endblock %}
@@ -15,25 +15,42 @@ Define relation
<form action="/model/relation/create" method="POST">
<input type="hidden" name="object" value="{{object.id}}">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off"><br>
<label for="reciprocal_name">Reciprocal name:</label>
<input type="text" id="reciprocal_name" name="reciprocal_name" value="{{old.reciprocal_name}}" autocomplete="off"><br>
<label for="optional">Optional:</label>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=old.optional)}} autocomplete="off">
<br>
<label for="multiple">Multiple:</label>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=old.multiple)}} autocomplete="off"><br>
<label for="related">Related object:</label>
<select name="related" id="related" autocomplete="off">
{% for m in models %}
<option value="{{ m.id }}" {{selected(val=old.related, opt=m.id)}}>{{ m.name }}</option>
{% endfor %}
</select><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td>
<input type="text" id="name" name="name" value="{{old.name}}" autocomplete="off"><br>
</td>
</tr>
<tr>
<th><label for="reciprocal_name">Reciprocal name:</label></th>
<td>
<input type="text" id="reciprocal_name" name="reciprocal_name" value="{{old.reciprocal_name}}" autocomplete="off">
</td>
</tr>
<tr>
<th><label for="optional">Optional:</label></th>
<td>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=old.optional)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="multiple">Multiple:</label></th>
<td>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=old.multiple)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="related">Related object:</label></th>
<td>
<select name="related" id="related" autocomplete="off">
{% for m in models %}
<option value="{{ m.id }}" {{selected(val=old.related, opt=m.id)}}>{{ m.name }}</option>
{% endfor %}
</select>
</td>
</tr>
</table>
<input type="submit" value="Save">
</form>
@@ -13,18 +13,29 @@ Edit relation
<h1>Edit relation model "{{model.name}}"</h1>
<form action="/model/relation/update/{{model.id}}" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{model.name}}" autocomplete="off"><br>
<label for="reciprocal_name">Reciprocal name:</label>
<input type="text" id="reciprocal_name" name="reciprocal_name" value="{{model.reciprocal_name}}" autocomplete="off"><br>
<label for="optional">Optional:</label>
<input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=model.optional)}} autocomplete="off">
<br>
<label for="multiple">Multiple:</label>
<input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=model.multiple)}} autocomplete="off"><br>
<table>
<tr>
<th><label for="name">Name:</label></th>
<td><input type="text" id="name" name="name" value="{{model.name}}" autocomplete="off">
</td>
</tr>
<tr>
<th><label for="reciprocal_name">Reciprocal name:</label></th>
<td><input type="text" id="reciprocal_name" name="reciprocal_name" value="{{model.reciprocal_name}}" autocomplete="off">
</td>
</tr>
<tr>
<th><label for="optional">Optional:</label>
</th>
<td><input type="checkbox" name="optional" id="optional" value="true" {{opt(checked=model.optional)}} autocomplete="off">
</td>
</tr>
<tr>
<th><label for="multiple">Multiple:</label></th>
<td><input type="checkbox" name="multiple" id="multiple" value="true" {{opt(checked=model.multiple)}} autocomplete="off">
</td>
</tr>
</table>
<p>The related object cannot be changed. Create a new relation if needed.</p>
+103
View File
@@ -10,6 +10,7 @@ use crate::session_ext::SessionExt;
use crate::tera_ext::TeraExt;
use crate::utils::{redirect, StorageErrorIntoResponseError};
use crate::TERA;
use itertools::Itertools;
#[derive(Serialize, Debug)]
pub(crate) struct ObjectModelDisplay<'a> {
@@ -38,6 +39,10 @@ pub(crate) async fn create_form(session: Session) -> actix_web::Result<impl Resp
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ObjectModelForm {
pub name: String,
#[serde(default)]
// #[serde(with="serde_with::rust::default_on_error")] // This is because "" can be selected
#[serde(with="my_string_empty_as_none")]
pub name_property: Option<ID>,
}
#[post("/model/object/create")]
@@ -51,6 +56,7 @@ pub(crate) async fn create(
match wg.define_object(ObjectModel {
id: Default::default(),
name: form.name.clone(),
name_property: form.name_property
}) {
Ok(_id) => {
wg.persist().err_to_500()?;
@@ -84,12 +90,22 @@ pub(crate) async fn update_form(
// Re-fill old values
if let Ok(Some(form)) = session.take::<ObjectModelForm>("old") {
let mut model = model.clone();
context.insert("old", &form);
model.name = form.name;
model.name_property = form.name_property;
context.insert("model", &model);
} else {
context.insert("model", model);
context.insert("old", &ObjectModelForm {
name: model.name.to_string(),
name_property: model.name_property
});
}
let properties = rg.get_property_models_for_parent(*model_id).collect_vec();
context.insert("properties", &properties);
TERA.build_response("models/model_update", &context)
}
@@ -107,6 +123,7 @@ pub(crate) async fn update(
match wg.update_object_model(ObjectModel {
id,
name: form.name.clone(),
name_property: form.name_property,
}) {
Ok(_id) => {
wg.persist().err_to_500()?;
@@ -144,3 +161,89 @@ pub(crate) async fn delete(
}
}
}
pub mod my_string_empty_as_none {
use serde::{Deserializer, Serializer, Serialize};
use std::str::FromStr;
use std::fmt::{Display, Write};
use std::marker::PhantomData;
use serde::de::{Visitor, Error};
use std::fmt;
use yopa::ID;
// FIXME largely copied from serde_with
/// Deserialize an `Option<T>` from a string using `FromStr`
pub fn deserialize<'de, D, S>(deserializer: D) -> Result<Option<S>, D::Error>
where
D: Deserializer<'de>,
S: FromStr,
S::Err: Display,
{
struct OptionStringEmptyNone<S>(PhantomData<S>);
impl<'de, S> Visitor<'de> for OptionStringEmptyNone<S>
where
S: FromStr,
S::Err: Display,
{
type Value = Option<S>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("any string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
match value {
"" => Ok(None),
v => S::from_str(v).map(Some).map_err(Error::custom),
}
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: Error,
{
match &*value {
"" => Ok(None),
v => S::from_str(v).map(Some).map_err(Error::custom),
}
}
// TODO remove?
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_str(&v.to_string())
}
// handles the `null` case
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
Ok(None)
}
}
deserializer.deserialize_any(OptionStringEmptyNone(PhantomData))
}
/// Serialize a string from `Option<T>` using `AsRef<str>` or using the empty string if `None`.
pub fn serialize<S>(option: &Option<ID>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
option.serialize(serializer)
// if let Some(value) = option {
// value.serialize(serializer)
// } else {
// "".serialize(serializer)
// }
}
}
+9
View File
@@ -28,11 +28,13 @@ pub(crate) async fn create_form(
} else {
context.insert(
"old",
// This is the defaults for the form
&PropertyModelCreateForm {
object: Default::default(),
name: "".to_string(),
optional: true,
multiple: false,
unique: false,
data_type: DataType::String,
default: "".to_string(),
},
@@ -70,6 +72,8 @@ pub(crate) struct PropertyModelCreateForm {
pub optional: bool,
#[serde(default)]
pub multiple: bool,
#[serde(default)]
pub unique: bool,
pub data_type: DataType,
/// Default value to be parsed to the data type
/// May be unused if empty and optional
@@ -126,6 +130,7 @@ pub(crate) async fn create(
let optional = form.optional;
let multiple = form.multiple;
let unique = form.unique;
let default = match parse_default(form.data_type, form.default.clone()) {
Ok(def) => def,
@@ -143,6 +148,7 @@ pub(crate) async fn create(
name: form.name.clone(),
optional,
multiple,
unique,
data_type: form.data_type,
default,
}) {
@@ -190,6 +196,8 @@ pub(crate) struct PropertyModelEditForm {
pub optional: bool,
#[serde(default)]
pub multiple: bool,
#[serde(default)]
pub unique: bool,
pub data_type: DataType,
/// Default value to be parsed to the data type
/// May be unused if empty and optional
@@ -253,6 +261,7 @@ pub(crate) async fn update(
name: form.name.clone(),
optional: form.optional,
multiple: form.multiple,
unique: form.unique,
data_type: form.data_type,
default,
}) {
+89 -41
View File
@@ -1,22 +1,24 @@
use crate::session_ext::SessionExt;
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use actix_session::Session;
use actix_web::{web, HttpResponse, Responder};
use crate::tera_ext::TeraExt;
use crate::utils::{redirect, StorageErrorIntoResponseError};
use crate::TERA;
use serde::Serialize;
use yopa::data::Object;
use yopa::insert::InsertObj;
use yopa::{data, model, Storage, ID};
use actix_web::{HttpResponse, Responder, web};
use heck::TitleCase;
use itertools::Itertools;
use json_dotpath::DotPaths;
use std::collections::HashMap;
use serde::Serialize;
use yopa::{data, ID, model, Storage};
use yopa::data::Object;
use yopa::insert::InsertObj;
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
use yopa::update::UpdateObj;
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::utils::{redirect, StorageErrorIntoResponseError};
// we only need references here, Context serializes everything to Value.
// cloning would be a waste of cycles
@@ -27,11 +29,18 @@ pub struct Schema<'a> {
pub prop_models: Vec<&'a model::PropertyModel>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ObjectDisplay<'a> {
pub id: ID,
pub model: ID,
pub name: Cow<'a, str>,
}
#[derive(Serialize, Debug, Clone)]
pub struct ObjectCreateData<'a> {
pub model_id: ID,
pub schema: Schema<'a>,
pub objects: Vec<&'a Object>,
pub objects: Vec<ObjectDisplay<'a>>,
}
#[get("/object/create/{model_id}")]
@@ -76,16 +85,25 @@ fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result<O
related_ids.sort();
related_ids.dedup();
let mut models_to_fetch = vec![model_id];
models_to_fetch.extend(&related_ids);
Ok(ObjectCreateData {
model_id: model.id,
schema: Schema {
obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here
obj_models: rg.get_object_models_by_ids(models_to_fetch).collect(), // TODO get only the ones that matter here
rel_models: relations,
prop_models: rg
.get_property_models_for_parents(prop_object_ids)
.collect(),
},
objects: rg.get_objects_of_types(related_ids).collect(),
objects: rg.get_objects_of_types(related_ids).map(|o| {
ObjectDisplay {
id: o.id,
model: o.model,
name: rg.get_object_name(o),
}
}).collect(),
})
}
@@ -97,22 +115,17 @@ pub(crate) async fn create(
) -> actix_web::Result<impl Responder> {
warn!("{:?}", form);
// let des : InsertObj = serde_json::from_value(form.into_inner()).unwrap();
//
// Ok(HttpResponse::Ok().finish())
//
let mut wg = store.write().await;
let form = form.into_inner();
let name = form.name.clone();
let model_name = wg.get_model_name(form.model).to_owned().to_title_case();
match wg.insert_object(form) {
Ok(_id) => {
Ok(id) => {
wg.persist().err_to_500()?;
let obj_name = wg.get_object_name_by_id(id);
debug!("Object created, redirecting to root");
session.flash_success(format!("{} \"{}\" created.", model_name, name));
session.flash_success(format!("{} \"{}\" created.", model_name, obj_name));
Ok(HttpResponse::Ok().finish())
}
Err(e) => {
@@ -125,7 +138,7 @@ pub(crate) async fn create(
#[derive(Debug, Serialize, Clone)]
struct ModelWithObjects<'a> {
model: &'a ObjectModel,
objects: Vec<&'a Object>,
objects: Vec<ObjectDisplay<'a>>,
}
#[get("/objects")]
@@ -148,8 +161,16 @@ pub(crate) async fn list_inner(
.get_object_models()
.sorted_by_key(|m| &m.name)
.map(|model| {
let mut objects = objects_by_model.remove(&model.id).unwrap_or_default();
objects.sort_by_key(|o| &o.name);
let objects = objects_by_model.remove(&model.id).unwrap_or_default();
let mut objects = objects.into_iter().map(|o| {
ObjectDisplay {
id: o.id,
model: o.model,
name: rg.get_object_name(o), // TODO optimize
}
}).collect_vec();
objects.sort_by(|a, b| a.name.cmp(&b.name));
ModelWithObjects { model, objects }
})
.collect();
@@ -176,7 +197,8 @@ struct RelationView<'a> {
#[derive(Debug, Serialize)]
struct RelationInstanceView<'a> {
related: &'a Object,
related: ObjectDisplay<'a>,
related_name: Cow<'a, str>,
properties: Vec<PropertyView<'a>>,
}
@@ -201,7 +223,11 @@ pub(crate) async fn detail(
.get_object_model(object.model)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
context.insert("object", object);
context.insert("object", &ObjectDisplay {
id: object_id,
model: object.model,
name: rg.get_object_name(object),
});
context.insert("model", model);
context.insert("kind", &rg.get_model_name(object.model));
@@ -270,13 +296,20 @@ pub(crate) async fn detail(
view_rel_properties.sort_by_key(|p| &p.model.name);
let related_name = rg.get_object_name(related_obj);
instances.push(RelationInstanceView {
related: related_obj,
related: ObjectDisplay {
id: related_obj.id,
model: related_obj.model,
name: rg.get_object_name(related_obj),
},
related_name,
properties: view_rel_properties,
})
}
instances.sort_by_key(|r| &r.related.name);
instances.sort_by(|a, b| a.related_name.cmp(&b.related_name));
relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(),
@@ -322,13 +355,21 @@ pub(crate) async fn detail(
view_rel_properties.sort_by_key(|p| &p.model.name);
let related_name = rg.get_object_name(related_obj);
instances.push(RelationInstanceView {
related: related_obj,
related: ObjectDisplay {
id: related_obj.id,
model: related_obj.model,
name: rg.get_object_name(related_obj),
},
related_name,
properties: view_rel_properties,
})
}
instances.sort_by_key(|r| &r.related.name);
// sort_by_key is not possible because rust is still stupid about lifetimes
instances.sort_by(|a, b| a.related_name.cmp(&b.related_name));
relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(),
@@ -349,7 +390,7 @@ pub(crate) async fn detail(
struct EnrichedObject<'a> {
id: ID,
model: ID,
name: String,
name: Cow<'a, str>,
values: HashMap<
String, /* ID but as string so serde will stop exploding */
Vec<&'a data::Value>,
@@ -387,7 +428,11 @@ pub(crate) async fn update_form(
// maybe its useful,idk
context.insert("model", &model);
context.insert("object", &object);
context.insert("object", &ObjectDisplay {
id: object.id,
model: object.model,
name: rg.get_object_name(object),
});
let create_data = prepare_object_create_data(&rg, model.id)?;
@@ -480,7 +525,7 @@ pub(crate) async fn update_form(
let object = EnrichedObject {
id: object.id,
model: object.model,
name: object.name.clone(),
name: rg.get_object_name_by_id(object.id),
values: value_map,
relations: relation_map,
};
@@ -494,26 +539,26 @@ pub(crate) async fn update_form(
#[post("/object/update/{id}")]
pub(crate) async fn update(
_id: web::Path<ID>,
id: web::Path<ID>,
form: web::Json<UpdateObj>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<HttpResponse> {
let mut wg = store.write().await;
let form = form.into_inner();
let name = form.name.clone();
let object = wg
.get_object(form.id)
.get_object(*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();
let id = object.id;
match wg.update_object(form) {
Ok(_id) => {
wg.persist().err_to_500()?;
debug!("Object created, redirecting to root");
session.flash_success(format!("{} \"{}\" updated.", model_name, name));
session.flash_success(format!("{} \"{}\" updated.", model_name, wg.get_object_name_by_id(id)));
Ok(HttpResponse::Ok().finish())
}
Err(e) => {
@@ -530,11 +575,14 @@ pub(crate) async fn delete(
session: Session,
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
let name = wg.get_object_name_by_id(*id).to_string();
match wg.delete_object(*id) {
Ok(obj) => {
Ok(_obj) => {
wg.persist().err_to_500()?;
debug!("Object deleted, redirecting to root");
session.flash_success(format!("Object \"{}\" deleted.", obj.name));
session.flash_success(format!("Object \"{}\" deleted.", name));
redirect("/")
}
Err(e) => {