diff --git a/js_conversion.py b/js_conversion.py
index be42098..f93e260 100644
--- a/js_conversion.py
+++ b/js_conversion.py
@@ -47,13 +47,13 @@ def all_attrs(cls):
def js_representation(cls: Type[StructuredNode]) -> str:
props, rels = get_properties_and_relations(cls)
- return f"""class {cls.__name__} extends StructuredNode {{
+ return f"""models.{cls.__name__} = class {cls.__name__} extends StructuredNode {{
{constructor(cls)}
{all_attrs(cls)}
}}
-StructuredNode.registerClassDefinition({cls.__name__}, {json.dumps(props)}, {json.dumps(rels)})
+StructuredNode.registerClassDefinition(models.{cls.__name__}, {json.dumps(props)}, {json.dumps(rels)})
"""
@@ -88,10 +88,13 @@ def get_properties_and_relations(cls) -> Tuple[Dict[str, dict], Dict[str, dict]]
if __name__ == '__main__':
reps = list()
reps.append("// This file was generated by running js_conversion.py\n" +
- "// It converts the neomodel declaration of this element into js classes.\n")
+ "// It converts the neomodel declaration of this element into js classes.\n" +
+ "(() => {\nconst models = window.models || {}")
for name, cls in vars(server.models.models).items():
if hasattr(cls, '__mro__') and StructuredNode in cls.__mro__ and StructuredNode != cls.__mro__[0]:
reps.append(js_representation(cls))
+ reps.append('window.models = models;\n})()')
+
print('\n\n\n'.join(reps))
diff --git a/server/abstract_api.py b/server/abstract_api.py
index 077be5d..7b9ce17 100644
--- a/server/abstract_api.py
+++ b/server/abstract_api.py
@@ -3,7 +3,7 @@ from server.models.models import Jsonifiable
from flask import Flask, request, Response, jsonify
from typing import Type, Final, List, Optional
from datetime import datetime
-from neomodel import StructuredNode, RelationshipManager, RelationshipDefinition
+from neomodel import StructuredNode, RelationshipManager, RelationshipDefinition, db
from neomodel.cardinality import ZeroOrOne, ZeroOrMore, One, OneOrMore
import json
@@ -95,8 +95,10 @@ def construct_object_from_request(cls: Type[StructuredNode], uid=None, data: dic
if cls_name == 'StringProperty':
convert_with = str
- elif cls_name == 'DateProperty':
+ elif cls_name == 'DateTimeProperty':
convert_with = datetime.fromisoformat
+ elif cls_name == 'DateProperty':
+ convert_with = lambda d: datetime.fromisoformat(d).date()
elif cls_name == 'IntegerProperty':
convert_with = int
elif cls_name == 'FloatProperty':
@@ -131,10 +133,11 @@ def handle_object_api_request(cls: Type[StructuredNode]):
for obj in cls.nodes.filter(**options).order_by('uid').all()[offset:offset+limit]
])
if request.method == 'POST':
- obj, attach_relationships = construct_object_from_request(cls, data=request.get_json())
- obj.save()
- attach_relationships()
- return jsonify(obj.json())
+ with db.transaction:
+ obj, attach_relationships = construct_object_from_request(cls, data=request.get_json())
+ obj.save()
+ attach_relationships()
+ return jsonify(obj.json())
def handle_object_api_request_id(cls: Type[StructuredNode], uid: str):
@@ -156,6 +159,7 @@ def handle_object_api_request_id(cls: Type[StructuredNode], uid: str):
obj.delete()
return '', 204
+
def handle_object_api_request_for_relation(cls: Type[StructuredNode], uid, relation, reluid):
obj = cls.nodes.get_or_none(uid=uid)
@@ -202,8 +206,6 @@ def register_api_object(app: Flask, cls: Type[StructuredNode], name: Optional[st
"""
name = name or cls.__name__.lower()
- print(app.view_functions)
-
def func_handle():
return handle_object_api_request(cls)
diff --git a/server/models/models.py b/server/models/models.py
index 171a05f..b3a4e46 100644
--- a/server/models/models.py
+++ b/server/models/models.py
@@ -9,7 +9,6 @@ class Jsonifiable(object):
for name, value in vars(self).items():
if name == 'id':
continue
- print(name, value)
if isinstance(value, RelationshipManager):
if not include_relations:
continue
diff --git a/static/api.js b/static/api.js
index 58cfef4..4939ffb 100644
--- a/static/api.js
+++ b/static/api.js
@@ -13,6 +13,14 @@ class StructuredNode {
// holds a list of all relations that are already loaded
this._update_json()
+
+ if (StructuredNode.instances[this.__name__][this.json.uid]) {
+ console.warn('two instances of ' + this.__name__ + '?')
+ }
+
+ if (!json.uid) {
+ this.changes = this.json
+ }
}
_update_json(json) {
@@ -35,12 +43,12 @@ class StructuredNode {
const type = StructuredNode.classes[rel.target].klass;
if (rel.cardinality.endsWith('OrMore')) {
- this.json[rel.field] = this.json[rel.field].map(x => new type(x))
+ this.json[rel.field] = this.json[rel.field].map(x => type.from_json(x))
} else {
if (this.json[rel.field] === null) {
return
}
- this.json[rel.field] = new type(this.json[rel.field])
+ this.json[rel.field] = type.from_json(this.json[rel.field])
}
})
@@ -62,6 +70,8 @@ class StructuredNode {
}
}
})
+
+ return this
}
check(name, new_value) {
@@ -86,7 +96,7 @@ class StructuredNode {
// make results available immediately
if (relation.cardinality.endsWith('OrMore')) {
- if (!this.json[name].filter(e => e.json.uid === elm.json.uid)) {
+ if (this.json[name] && !this.json[name].filter(e => e.json.uid === elm.json.uid)) {
this.json[name].push(elm)
}
} else {
@@ -142,8 +152,7 @@ class StructuredNode {
return this
})
} else {
- return Api.post('/' + this.__name__, this.json).then(json => {
- debugger;
+ return Api.post('/' + this.__name__, this.changes).then(json => {
this._update_json(json)
this.changes = {}
this.update('created')
@@ -161,6 +170,8 @@ class StructuredNode {
this.update('deleted')
return this
})
+
+ delete StructuredNode.instances[this.__name__][this.json.uid]
}
refresh() {
@@ -193,11 +204,12 @@ class StructuredNode {
static registerClassDefinition(klass, props, rels) {
console.log(klass, props, rels)
StructuredNode.classes[klass.name] = {klass, props, rels}
+ StructuredNode.instances[klass.name.toLowerCase()] = {}
}
static by_id(uid) {
return Api.get('/' + this.name.toLowerCase() + '/' + uid)
- .then(x => new this(x))
+ .then(x => this.from_json(x))
}
static filter(attributes = {}, settings = {}) {
@@ -206,16 +218,26 @@ class StructuredNode {
})
return Api.get(`/${this.name.toLowerCase()}?` + Api.queryParams(attributes))
- .then(json => json.map(x => new this(x)))
+ .then(json => json.map(x => this.from_json(x)))
}
static find(attributes) {
- return this.filter(attributes, {$limit: 1})
+ return this.filter(attributes, {limit: 1})
.then(r => r.length === 0 ? null : r[0])
}
+ static from_json(json) {
+ if (json.uid && StructuredNode.instances[this.name.toLowerCase()][json.uid]) {
+ return StructuredNode.instances[this.name.toLowerCase()][json.uid]._update_json(json)
+ }
+ const obj = new this(json)
+ StructuredNode.instances[this.name.toLowerCase()][json.uid] = obj
+ return obj
+ }
+
}
StructuredNode.classes = {}
+StructuredNode.instances = {}
class Api {
static set_auth(token) {
@@ -316,8 +338,10 @@ class PropertyConversion {
}
PropertyConversion.from = {
- DateProperty: (val) => new Date(val),
+ DateTimeProperty: (val) => new Date(val), // assume local time
+ DateProperty: (val) => new Date(val) // ensure no minutes/hours are added/subtracted from date
}
PropertyConversion.to = {
- DateProperty: (val) => ('' + val).slice(0,-11),
+ DateTimeProperty: (val) => val.toISOString().slice(0,-1),
+ DateProperty: (val) => `${val.getFullYear()}-${val.getMonth()+1}-${val.getDate()}`
}
diff --git a/static/models.js b/static/models.js
index 4b03fb5..820c885 100644
--- a/static/models.js
+++ b/static/models.js
@@ -1,9 +1,10 @@
// This file was generated by running js_conversion.py
// It converts the neomodel declaration of this element into js classes.
+(() => {
+const models = window.models || {}
-
-class Person extends StructuredNode {
+models.Person = class Person extends StructuredNode {
constructor(json) {
super(json)
}
@@ -95,11 +96,11 @@ class Person extends StructuredNode {
}
-StructuredNode.registerClassDefinition(Person, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "birthdate": {"field": "birthdate", "type": "DateProperty", "required": false}, "deceased": {"field": "deceased", "type": "DateProperty", "required": false}, "description": {"field": "description", "type": "StringProperty", "required": false}, "picture": {"field": "picture", "type": "StringProperty", "required": false}}, {"father": {"field": "father", "target": "Person", "cardinality": "ZeroOrOne"}, "mother": {"field": "mother", "target": "Person", "cardinality": "ZeroOrOne"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}})
+StructuredNode.registerClassDefinition(models.Person, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "birthdate": {"field": "birthdate", "type": "DateProperty", "required": false}, "deceased": {"field": "deceased", "type": "DateProperty", "required": false}, "description": {"field": "description", "type": "StringProperty", "required": false}, "picture": {"field": "picture", "type": "StringProperty", "required": false}}, {"father": {"field": "father", "target": "Person", "cardinality": "ZeroOrOne"}, "mother": {"field": "mother", "target": "Person", "cardinality": "ZeroOrOne"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}})
-class Event extends StructuredNode {
+models.Event = class Event extends StructuredNode {
constructor(json) {
super(json)
}
@@ -173,11 +174,11 @@ class Event extends StructuredNode {
}
-StructuredNode.registerClassDefinition(Event, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "date": {"field": "date", "type": "DateProperty", "required": false}, "end_date": {"field": "end_date", "type": "DateProperty", "required": false}}, {"participants": {"field": "participants", "target": "Person", "cardinality": "ZeroOrMore"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}, "related_events": {"field": "related_events", "target": "Event", "cardinality": "ZeroOrMore"}})
+StructuredNode.registerClassDefinition(models.Event, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "date": {"field": "date", "type": "DateProperty", "required": false}, "end_date": {"field": "end_date", "type": "DateProperty", "required": false}}, {"participants": {"field": "participants", "target": "Person", "cardinality": "ZeroOrMore"}, "places": {"field": "places", "target": "Place", "cardinality": "ZeroOrMore"}, "related_events": {"field": "related_events", "target": "Event", "cardinality": "ZeroOrMore"}})
-class Place extends StructuredNode {
+models.Place = class Place extends StructuredNode {
constructor(json) {
super(json)
}
@@ -216,11 +217,11 @@ class Place extends StructuredNode {
}
-StructuredNode.registerClassDefinition(Place, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}}, {"visitors": {"field": "visitors", "target": "Person", "cardinality": "ZeroOrMore"}})
+StructuredNode.registerClassDefinition(models.Place, {"uid": {"field": "uid", "type": "UniqueIdProperty", "required": false}, "name": {"field": "name", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}}, {"visitors": {"field": "visitors", "target": "Person", "cardinality": "ZeroOrMore"}})
-class Image extends StructuredNode {
+models.Image = class Image extends StructuredNode {
constructor(json) {
super(json)
}
@@ -272,5 +273,9 @@ class Image extends StructuredNode {
}
-StructuredNode.registerClassDefinition(Image, {"uid": {"field": "uid", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}, "date": {"field": "date", "type": "DateProperty", "required": false}}, {"pictured": {"field": "pictured", "target": "Person", "cardinality": "ZeroOrMore"}, "place": {"field": "place", "target": "Place", "cardinality": "ZeroOrMore"}})
+StructuredNode.registerClassDefinition(models.Image, {"uid": {"field": "uid", "type": "StringProperty", "required": true}, "description": {"field": "description", "type": "StringProperty", "required": false}, "date": {"field": "date", "type": "DateProperty", "required": false}}, {"pictured": {"field": "pictured", "target": "Person", "cardinality": "ZeroOrMore"}, "place": {"field": "place", "target": "Place", "cardinality": "ZeroOrMore"}})
+
+
+window.models = models;
+})()
diff --git a/static/site.js b/static/site.js
index 6a9350e..dc9caf1 100644
--- a/static/site.js
+++ b/static/site.js
@@ -40,29 +40,32 @@ class DefaultView extends View{
if (this.fields[name]) {
return this.fields[name]
}
- console.log(name)
value = value === null ? this.object.json[name] : value
if (value instanceof StructuredNode) {
this.subviews[name] = (new DefaultView(value)).render()
const wrap = document.createElement('div')
wrap.classList.add('wrapper')
- wrap.innerHTML += '' + name + ':'
+ if (name.indexOf('[') === -1) {
+ wrap.innerHTML += '' + name + ':'
+ }
wrap.appendChild(this.subviews[name].root)
this.fields[name] = wrap
} else if (value instanceof Array) {
const elm = document.createElement('ul')
- value.forEach((elm, i) => {
+ elm.innerHTML += '' + name + ':'
+ value.forEach((o, i) => {
const li = document.createElement('li')
- li.appendChild(this.getNode(name+'['+i+']',elm))
+ li.appendChild(this.getNode(name+'['+i+']',o))
elm.appendChild(li)
})
this.fields[name] = elm
} else {
- if (value === null)
+ if (value === null || name === 'uid')
return
const elm = document.createElement('p')
+
elm.innerHTML = `${name}: ${value}`
this.fields[name] = elm
}