various improvements to frontend and api model handling

master
Anton Lydike 4 years ago
parent 185c182447
commit de581950fe

@ -47,13 +47,13 @@ def all_attrs(cls):
def js_representation(cls: Type[StructuredNode]) -> str: def js_representation(cls: Type[StructuredNode]) -> str:
props, rels = get_properties_and_relations(cls) 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)} {constructor(cls)}
{all_attrs(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__': if __name__ == '__main__':
reps = list() reps = list()
reps.append("// This file was generated by running js_conversion.py\n" + 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(): for name, cls in vars(server.models.models).items():
if hasattr(cls, '__mro__') and StructuredNode in cls.__mro__ and StructuredNode != cls.__mro__[0]: if hasattr(cls, '__mro__') and StructuredNode in cls.__mro__ and StructuredNode != cls.__mro__[0]:
reps.append(js_representation(cls)) reps.append(js_representation(cls))
reps.append('window.models = models;\n})()')
print('\n\n\n'.join(reps)) print('\n\n\n'.join(reps))

@ -3,7 +3,7 @@ from server.models.models import Jsonifiable
from flask import Flask, request, Response, jsonify from flask import Flask, request, Response, jsonify
from typing import Type, Final, List, Optional from typing import Type, Final, List, Optional
from datetime import datetime 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 from neomodel.cardinality import ZeroOrOne, ZeroOrMore, One, OneOrMore
import json import json
@ -95,8 +95,10 @@ def construct_object_from_request(cls: Type[StructuredNode], uid=None, data: dic
if cls_name == 'StringProperty': if cls_name == 'StringProperty':
convert_with = str convert_with = str
elif cls_name == 'DateProperty': elif cls_name == 'DateTimeProperty':
convert_with = datetime.fromisoformat convert_with = datetime.fromisoformat
elif cls_name == 'DateProperty':
convert_with = lambda d: datetime.fromisoformat(d).date()
elif cls_name == 'IntegerProperty': elif cls_name == 'IntegerProperty':
convert_with = int convert_with = int
elif cls_name == 'FloatProperty': elif cls_name == 'FloatProperty':
@ -131,6 +133,7 @@ def handle_object_api_request(cls: Type[StructuredNode]):
for obj in cls.nodes.filter(**options).order_by('uid').all()[offset:offset+limit] for obj in cls.nodes.filter(**options).order_by('uid').all()[offset:offset+limit]
]) ])
if request.method == 'POST': if request.method == 'POST':
with db.transaction:
obj, attach_relationships = construct_object_from_request(cls, data=request.get_json()) obj, attach_relationships = construct_object_from_request(cls, data=request.get_json())
obj.save() obj.save()
attach_relationships() attach_relationships()
@ -156,6 +159,7 @@ def handle_object_api_request_id(cls: Type[StructuredNode], uid: str):
obj.delete() obj.delete()
return '', 204 return '', 204
def handle_object_api_request_for_relation(cls: Type[StructuredNode], uid, relation, reluid): def handle_object_api_request_for_relation(cls: Type[StructuredNode], uid, relation, reluid):
obj = cls.nodes.get_or_none(uid=uid) 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() name = name or cls.__name__.lower()
print(app.view_functions)
def func_handle(): def func_handle():
return handle_object_api_request(cls) return handle_object_api_request(cls)

@ -9,7 +9,6 @@ class Jsonifiable(object):
for name, value in vars(self).items(): for name, value in vars(self).items():
if name == 'id': if name == 'id':
continue continue
print(name, value)
if isinstance(value, RelationshipManager): if isinstance(value, RelationshipManager):
if not include_relations: if not include_relations:
continue continue

@ -13,6 +13,14 @@ class StructuredNode {
// holds a list of all relations that are already loaded // holds a list of all relations that are already loaded
this._update_json() 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) { _update_json(json) {
@ -35,12 +43,12 @@ class StructuredNode {
const type = StructuredNode.classes[rel.target].klass; const type = StructuredNode.classes[rel.target].klass;
if (rel.cardinality.endsWith('OrMore')) { 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 { } else {
if (this.json[rel.field] === null) { if (this.json[rel.field] === null) {
return 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) { check(name, new_value) {
@ -86,7 +96,7 @@ class StructuredNode {
// make results available immediately // make results available immediately
if (relation.cardinality.endsWith('OrMore')) { 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) this.json[name].push(elm)
} }
} else { } else {
@ -142,8 +152,7 @@ class StructuredNode {
return this return this
}) })
} else { } else {
return Api.post('/' + this.__name__, this.json).then(json => { return Api.post('/' + this.__name__, this.changes).then(json => {
debugger;
this._update_json(json) this._update_json(json)
this.changes = {} this.changes = {}
this.update('created') this.update('created')
@ -161,6 +170,8 @@ class StructuredNode {
this.update('deleted') this.update('deleted')
return this return this
}) })
delete StructuredNode.instances[this.__name__][this.json.uid]
} }
refresh() { refresh() {
@ -193,11 +204,12 @@ class StructuredNode {
static registerClassDefinition(klass, props, rels) { static registerClassDefinition(klass, props, rels) {
console.log(klass, props, rels) console.log(klass, props, rels)
StructuredNode.classes[klass.name] = {klass, props, rels} StructuredNode.classes[klass.name] = {klass, props, rels}
StructuredNode.instances[klass.name.toLowerCase()] = {}
} }
static by_id(uid) { static by_id(uid) {
return Api.get('/' + this.name.toLowerCase() + '/' + uid) return Api.get('/' + this.name.toLowerCase() + '/' + uid)
.then(x => new this(x)) .then(x => this.from_json(x))
} }
static filter(attributes = {}, settings = {}) { static filter(attributes = {}, settings = {}) {
@ -206,16 +218,26 @@ class StructuredNode {
}) })
return Api.get(`/${this.name.toLowerCase()}?` + Api.queryParams(attributes)) 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) { static find(attributes) {
return this.filter(attributes, {$limit: 1}) return this.filter(attributes, {limit: 1})
.then(r => r.length === 0 ? null : r[0]) .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.classes = {}
StructuredNode.instances = {}
class Api { class Api {
static set_auth(token) { static set_auth(token) {
@ -316,8 +338,10 @@ class PropertyConversion {
} }
PropertyConversion.from = { 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 = { PropertyConversion.to = {
DateProperty: (val) => ('' + val).slice(0,-11), DateTimeProperty: (val) => val.toISOString().slice(0,-1),
DateProperty: (val) => `${val.getFullYear()}-${val.getMonth()+1}-${val.getDate()}`
} }

@ -1,9 +1,10 @@
// This file was generated by running js_conversion.py // This file was generated by running js_conversion.py
// It converts the neomodel declaration of this element into js classes. // It converts the neomodel declaration of this element into js classes.
(() => {
const models = window.models || {}
models.Person = class Person extends StructuredNode {
class Person extends StructuredNode {
constructor(json) { constructor(json) {
super(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) { constructor(json) {
super(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) { constructor(json) {
super(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) { constructor(json) {
super(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;
})()

@ -40,29 +40,32 @@ class DefaultView extends View{
if (this.fields[name]) { if (this.fields[name]) {
return this.fields[name] return this.fields[name]
} }
console.log(name)
value = value === null ? this.object.json[name] : value value = value === null ? this.object.json[name] : value
if (value instanceof StructuredNode) { if (value instanceof StructuredNode) {
this.subviews[name] = (new DefaultView(value)).render() this.subviews[name] = (new DefaultView(value)).render()
const wrap = document.createElement('div') const wrap = document.createElement('div')
wrap.classList.add('wrapper') wrap.classList.add('wrapper')
if (name.indexOf('[') === -1) {
wrap.innerHTML += '<b>' + name + ':</b>' wrap.innerHTML += '<b>' + name + ':</b>'
}
wrap.appendChild(this.subviews[name].root) wrap.appendChild(this.subviews[name].root)
this.fields[name] = wrap this.fields[name] = wrap
} else if (value instanceof Array) { } else if (value instanceof Array) {
const elm = document.createElement('ul') const elm = document.createElement('ul')
value.forEach((elm, i) => { elm.innerHTML += '<b>' + name + ':</b>'
value.forEach((o, i) => {
const li = document.createElement('li') const li = document.createElement('li')
li.appendChild(this.getNode(name+'['+i+']',elm)) li.appendChild(this.getNode(name+'['+i+']',o))
elm.appendChild(li) elm.appendChild(li)
}) })
this.fields[name] = elm this.fields[name] = elm
} else { } else {
if (value === null) if (value === null || name === 'uid')
return return
const elm = document.createElement('p') const elm = document.createElement('p')
elm.innerHTML = `${name}: ${value}` elm.innerHTML = `${name}: ${value}`
this.fields[name] = elm this.fields[name] = elm
} }

Loading…
Cancel
Save