From de581950fefd6e5054bbf0ad58dfe58fe45cbc92 Mon Sep 17 00:00:00 2001 From: Anton Lydike Date: Tue, 1 Dec 2020 11:27:13 +0100 Subject: [PATCH] various improvements to frontend and api model handling --- js_conversion.py | 9 ++++++--- server/abstract_api.py | 18 +++++++++-------- server/models/models.py | 1 - static/api.js | 44 +++++++++++++++++++++++++++++++---------- static/models.js | 23 ++++++++++++--------- static/site.js | 13 +++++++----- 6 files changed, 72 insertions(+), 36 deletions(-) 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 }