From 0819643744ca2343bc03e34dc1105880adaada7a Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Fri, 6 Mar 2020 09:57:17 -0500 Subject: [PATCH 01/59] added CLI Tests --- tests/unittests/test_run.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/unittests/test_run.py diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py new file mode 100644 index 0000000..a4071c1 --- /dev/null +++ b/tests/unittests/test_run.py @@ -0,0 +1,24 @@ +""" Unit tests for run.py/Command Line Interface """ + +import pytest +import run + +################################## CMD SETUP ################################ +app = run.replShell() +################################ TEST COMMANDS ############################## +################################## TEST ADD ################################# +def test_do_add_empty(): + assert app.do_add("TestAddEmpty") == "Successfully added class \'' + name + '\'" + +def test_do_add_dup(): + assert app.do_add("TestAddEmpty") == "ERROR: Unable to add class \'' + name + '\'" + +def test_do_add_more(): + assert app.do_add("TestAddMore") == "Successfully added class \'' + name + '\'" + assert app.do_add("TestAdd1More") == "Successfully added class \'' + name + '\'" + +def test_do_add_none(): + assert app.do_add("") == "Please provide a class name" + +# write a test for adding multiple classes at a time (use list once its finished) +################################ TEST DELETE ################################ \ No newline at end of file From 2b085ada892234635ea6d61442e2b89af6973711 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Fri, 6 Mar 2020 09:59:57 -0500 Subject: [PATCH 02/59] added .pyc path to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0070bf5..853bd2d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ app_package/database.db env .vscode **/*.pyc +__pycache__/run.cpython-36.pyc From e4c78fc5c582acf5e8297a3bac89dcb1af8775b1 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Sun, 8 Mar 2020 17:52:31 -0400 Subject: [PATCH 03/59] -Added memento based undo and redo -Added functionality for add and remove in command line --- .vscode/settings.json | 2 +- app_package/memento/command_base.py | 10 ++++++ app_package/memento/command_stack.py | 30 +++++++++++++++++ app_package/memento/func_objs.py | 48 ++++++++++++++++++++++++++++ run.py | 24 ++++++++++++-- 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 app_package/memento/command_base.py create mode 100644 app_package/memento/command_stack.py create mode 100644 app_package/memento/func_objs.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 692b111..2a2510e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.pythonPath": "env\\Scripts\\python.exe", + "python.pythonPath": "env/bin/python", "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "python.linting.enabled": true diff --git a/app_package/memento/command_base.py b/app_package/memento/command_base.py new file mode 100644 index 0000000..fe8e182 --- /dev/null +++ b/app_package/memento/command_base.py @@ -0,0 +1,10 @@ + +class Command: + def execute(self): + pass + + def undo(self): + return + + def redo(self): + pass diff --git a/app_package/memento/command_stack.py b/app_package/memento/command_stack.py new file mode 100644 index 0000000..249a174 --- /dev/null +++ b/app_package/memento/command_stack.py @@ -0,0 +1,30 @@ + +# could (should) be a singleton. + + +class command_stack: + commandStack = [] + redoStack = [] + + def execute(self, command): + + if command.execute(): + return 1 + + self.commandStack.insert(0, command) + self.redoStack.clear() + return 0 + + def undo(self): + if len(self.commandStack) == 0: + return + command = self.commandStack.pop(0) + command.undo() + self.redoStack.insert(0, command) + + def redo(self): + if len(self.redoStack) == 0: + return + command = self.redoStack.pop(0) + command.redo() + self.commandStack.insert(0, command) diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py new file mode 100644 index 0000000..d169295 --- /dev/null +++ b/app_package/memento/func_objs.py @@ -0,0 +1,48 @@ +from ..core_func import core_add, core_delete + + +class Command: + """The base class which all commands are a subclass + All subclasses require these methods""" + def execute(self): + pass + + def undo(self): + return + + def redo(self): + pass + + +class add_class(Command): + """Command class for core_add""" + class_name = '' + + def __init__(self, name): + self.class_name = name + + def execute(self): + return core_add(self.class_name) + + def undo(self): + return core_delete(self.class_name) + + def redo(self): + return core_add(self.class_name) + + +class delete_class(Command): + """Command class for core_delete""" + class_name = '' + + def __init__(self, name): + self.class_name = name + + def execute(self): + return core_delete(self.class_name) + + def undo(self): + return core_add(self.class_name) + + def redo(self): + return core_delete(self.class_name) diff --git a/run.py b/run.py index 2e61760..6b370b4 100644 --- a/run.py +++ b/run.py @@ -3,6 +3,8 @@ core_load, core_add_attr, core_del_attr, core_update_attr, core_add_rel, core_del_rel, core_parse) +from app_package.memento.command_stack import command_stack +from app_package.memento.func_objs import add_class, delete_class from app_package.models import Class, Attribute, Relationship from app_package import app import webbrowser @@ -12,6 +14,7 @@ class replShell(cmd.Cmd): intro = 'Welcome to the UML editor shell.\nType help or ? to list commands.\nType web to open web app.\n' prompt = '(UML): ' file = None + cmd_stack = command_stack() ################################# Class level ############################################ @@ -22,7 +25,8 @@ def do_add(self, args): argList = core_parse(args) if argList: for name in argList: - if core_add(name): + addCmd = add_class(name) + if self.cmd_stack.execute(addCmd): print('ERROR: Unable to add class \'' + name + '\'') else: print('Successfully added class \'' + name + '\'') @@ -36,7 +40,8 @@ def do_delete(self, args): argList = core_parse(args) if argList: for name in argList: - if core_delete(name): + deleteCmd = delete_class(name) + if self.cmd_stack.execute(deleteCmd): print('ERROR: Unable to delete class \'' + name + '\'') else: print('Successfully deleted class \'' + name + '\'') @@ -147,6 +152,21 @@ def do_web(self, args): webbrowser.open_new_tab("http://127.0.0.1:5000") app.run(port=5000, debug=False) + def do_undo(self, args): + """Reverses your last action. + Usage: undo + """ + self.cmd_stack.undo() + print('undid action') + + # could add arg for amount, to undo/redo X times in a row + def do_redo(self, args): + """Reverse of undo. Will execute undone command again. + Usage: redo + """ + self.cmd_stack.redo() + print('redid action') + def do_list(self, args): """Lists every class in the database. Usage: list From 3813ae5faff76e3a261a3561b1bfa5ea2c6990d6 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Sun, 8 Mar 2020 18:06:56 -0400 Subject: [PATCH 04/59] - Moved instantiation of cmd stack to init --- app_package/__init__.py | 3 +++ app_package/memento/command_base.py | 10 ---------- app_package/memento/command_stack.py | 4 +--- run.py | 12 +++++------- 4 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 app_package/memento/command_base.py diff --git a/app_package/__init__.py b/app_package/__init__.py index 3cc8cc0..1106b93 100644 --- a/app_package/__init__.py +++ b/app_package/__init__.py @@ -2,6 +2,7 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_marshmallow import Marshmallow +from app_package.memento.command_stack import command_stack app = Flask(__name__) @@ -10,5 +11,7 @@ app.config['SECRET_KEY'] = 'super secret key' db = SQLAlchemy(app) ma = Marshmallow(app) +cmd_stack = command_stack() + from app_package import routes \ No newline at end of file diff --git a/app_package/memento/command_base.py b/app_package/memento/command_base.py deleted file mode 100644 index fe8e182..0000000 --- a/app_package/memento/command_base.py +++ /dev/null @@ -1,10 +0,0 @@ - -class Command: - def execute(self): - pass - - def undo(self): - return - - def redo(self): - pass diff --git a/app_package/memento/command_stack.py b/app_package/memento/command_stack.py index 249a174..260d960 100644 --- a/app_package/memento/command_stack.py +++ b/app_package/memento/command_stack.py @@ -1,4 +1,3 @@ - # could (should) be a singleton. @@ -7,10 +6,9 @@ class command_stack: redoStack = [] def execute(self, command): - if command.execute(): return 1 - + self.commandStack.insert(0, command) self.redoStack.clear() return 0 diff --git a/run.py b/run.py index 6b370b4..5070ea2 100644 --- a/run.py +++ b/run.py @@ -3,10 +3,9 @@ core_load, core_add_attr, core_del_attr, core_update_attr, core_add_rel, core_del_rel, core_parse) -from app_package.memento.command_stack import command_stack from app_package.memento.func_objs import add_class, delete_class from app_package.models import Class, Attribute, Relationship -from app_package import app +from app_package import app, cmd_stack import webbrowser import json @@ -14,7 +13,6 @@ class replShell(cmd.Cmd): intro = 'Welcome to the UML editor shell.\nType help or ? to list commands.\nType web to open web app.\n' prompt = '(UML): ' file = None - cmd_stack = command_stack() ################################# Class level ############################################ @@ -26,7 +24,7 @@ def do_add(self, args): if argList: for name in argList: addCmd = add_class(name) - if self.cmd_stack.execute(addCmd): + if cmd_stack.execute(addCmd): print('ERROR: Unable to add class \'' + name + '\'') else: print('Successfully added class \'' + name + '\'') @@ -41,7 +39,7 @@ def do_delete(self, args): if argList: for name in argList: deleteCmd = delete_class(name) - if self.cmd_stack.execute(deleteCmd): + if cmd_stack.execute(deleteCmd): print('ERROR: Unable to delete class \'' + name + '\'') else: print('Successfully deleted class \'' + name + '\'') @@ -156,7 +154,7 @@ def do_undo(self, args): """Reverses your last action. Usage: undo """ - self.cmd_stack.undo() + cmd_stack.undo() print('undid action') # could add arg for amount, to undo/redo X times in a row @@ -164,7 +162,7 @@ def do_redo(self, args): """Reverse of undo. Will execute undone command again. Usage: redo """ - self.cmd_stack.redo() + cmd_stack.redo() print('redid action') def do_list(self, args): From 9c071573bc2ad969593d4766c672e3c5df27d896 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Sun, 8 Mar 2020 18:34:03 -0400 Subject: [PATCH 05/59] - Attributes now readded on undo of delete --- app_package/__init__.py | 2 +- app_package/memento/func_objs.py | 16 ++++++++++++---- run.py | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app_package/__init__.py b/app_package/__init__.py index 1106b93..10088b2 100644 --- a/app_package/__init__.py +++ b/app_package/__init__.py @@ -14,4 +14,4 @@ cmd_stack = command_stack() -from app_package import routes \ No newline at end of file +from app_package import routes diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index d169295..f05f9be 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -1,4 +1,5 @@ -from ..core_func import core_add, core_delete +from ..core_func import core_add, core_delete, core_add_attr +from ..models import Attribute class Command: @@ -15,7 +16,7 @@ def redo(self): class add_class(Command): - """Command class for core_add""" + """Command class for core_add. Accepts a class name""" class_name = '' def __init__(self, name): @@ -32,17 +33,24 @@ def redo(self): class delete_class(Command): - """Command class for core_delete""" + """Command class for core_delete. Accepts a class name""" class_name = '' + attributes = [] def __init__(self, name): self.class_name = name def execute(self): + self.attributes = Attribute.query.filter(self.class_name == Attribute.class_name).all() return core_delete(self.class_name) def undo(self): - return core_add(self.class_name) + result = core_add(self.class_name) + if result == 0: + for attr in self.attributes: + core_add_attr(self.class_name, attr.attribute) + + return result def redo(self): return core_delete(self.class_name) diff --git a/run.py b/run.py index 5070ea2..28ea5ed 100644 --- a/run.py +++ b/run.py @@ -9,6 +9,7 @@ import webbrowser import json + class replShell(cmd.Cmd): intro = 'Welcome to the UML editor shell.\nType help or ? to list commands.\nType web to open web app.\n' prompt = '(UML): ' From 14be0a8c9657233a408c955d0ade54fdfeed91f0 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Mon, 9 Mar 2020 20:28:42 -0400 Subject: [PATCH 06/59] - Added add and del attr - Added edit class --- app_package/memento/func_objs.py | 83 +++++++++++++++++++++++++++----- run.py | 18 ++++--- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index f05f9be..3115da6 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -1,4 +1,4 @@ -from ..core_func import core_add, core_delete, core_add_attr +from ..core_func import (core_add, core_delete, core_add_attr, core_update, core_add_attr, core_del_attr) from ..models import Attribute @@ -17,40 +17,97 @@ def redo(self): class add_class(Command): """Command class for core_add. Accepts a class name""" - class_name = '' + className = '' def __init__(self, name): - self.class_name = name + self.className = name def execute(self): - return core_add(self.class_name) + return core_add(self.className) def undo(self): - return core_delete(self.class_name) + return core_delete(self.className) def redo(self): - return core_add(self.class_name) + return core_add(self.className) class delete_class(Command): """Command class for core_delete. Accepts a class name""" - class_name = '' + className = '' attributes = [] def __init__(self, name): - self.class_name = name + self.className = name def execute(self): - self.attributes = Attribute.query.filter(self.class_name == Attribute.class_name).all() - return core_delete(self.class_name) + self.attributes = Attribute.query.filter(self.className == Attribute.class_name).all() + return core_delete(self.className) def undo(self): - result = core_add(self.class_name) + result = core_add(self.className) if result == 0: for attr in self.attributes: - core_add_attr(self.class_name, attr.attribute) + core_add_attr(self.className, attr.attribute) return result def redo(self): - return core_delete(self.class_name) + return core_delete(self.className) + + +class edit_class(Command): + """Command class for core_update. Accepts a class name and a new name""" + oldClassName = '' + newClassName = '' + + def __init__(self, oldName, newName): + self.oldClassName = oldName + self.newClassName = newName + + def execute(self): + return core_update(self.oldClassName, self.newClassName) + + def undo(self): + return core_update(self.newClassName, self.oldClassName) + + def redo(self): + return core_update(self.oldClassName, self.newClassName) + + +class add_attr(Command): + """Command class for core_add_attr. Accepts a class name and the name of an attribute""" + className = '' + attrName = '' + + def __init__(self, className, attrName): + self.className = className + self.attrName = attrName + + def execute(self): + return core_add_attr(self.className, self.attrName) + + def undo(self): + return core_del_attr(self.className, self.attrName) + + def redo(self): + return core_add_attr(self.className, self.attrName) + + +class del_attr(Command): + """Command class for core_add_attr. Accepts a class name and the name of an attribute""" + className = '' + attrName = '' + + def __init__(self, className, attrName): + self.className = className + self.attrName = attrName + + def execute(self): + return core_del_attr(self.className, self.attrName) + + def undo(self): + return core_add_attr(self.className, self.attrName) + + def redo(self): + return core_del_attr(self.className, self.attrName) diff --git a/run.py b/run.py index 28ea5ed..f122261 100644 --- a/run.py +++ b/run.py @@ -3,7 +3,7 @@ core_load, core_add_attr, core_del_attr, core_update_attr, core_add_rel, core_del_rel, core_parse) -from app_package.memento.func_objs import add_class, delete_class +from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr from app_package.models import Class, Attribute, Relationship from app_package import app, cmd_stack import webbrowser @@ -55,7 +55,8 @@ def do_edit(self, args): if len(argList) == 2: old_name = argList.pop(0) new_name = argList.pop(0) - if core_update(old_name, new_name): + editCmd = edit_class(old_name, new_name) + if cmd_stack.execute(editCmd): print('ERROR: Unable to update class \'' + old_name + '\' to \'' + new_name + '\'') else: print('Successfully updated class \'' + old_name + '\' to \'' + new_name + '\'') @@ -72,7 +73,8 @@ def do_addAttr(self, args): if len(argList) > 1: class_name = argList.pop(0) for attr in argList: - if core_add_attr(class_name, attr): + addAttrCmd = add_attr(class_name, attr) + if cmd_stack.execute(addAttrCmd): print('ERROR: Unable to add attribute \'' + attr + '\'') else: print('Successfully added attribute \'' + attr + '\'') @@ -87,7 +89,8 @@ def do_delAttr(self, args): if len(argList) > 1: class_name = argList.pop(0) for attr in argList: - if core_del_attr(class_name, attr): + delAttrCmd = del_attr(class_name, attr) + if cmd_stack.execute(delAttrCmd): print('ERROR: Unable to delete attribute \'' + attr + '\'') else: print('Successfully deleted attribute \'' + attr + '\'') @@ -152,8 +155,8 @@ def do_web(self, args): app.run(port=5000, debug=False) def do_undo(self, args): - """Reverses your last action. - Usage: undo + """Reverses your last action. Optionally provide amount. + Usage: undo <# of undo's> """ cmd_stack.undo() print('undid action') @@ -198,7 +201,7 @@ def do_list(self, args): listStr += rel.to_name + '\n' else: listStr += (rel.to_name + ", ") - + print(listStr) def do_save(self, args): @@ -231,7 +234,6 @@ def do_load(self, args): except: print("ERROR: Unable to open file \'" + args + '\'') - def do_exit(self, args): """Exits the UML shell. Usage: exit From 1adb15c54bab5e711e2594bb17c6e5ed87d370c4 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Mon, 9 Mar 2020 20:38:18 -0400 Subject: [PATCH 07/59] - Added edit attr to undo/redo --- .vscode/settings.json | 2 +- .../__pycache__/__init__.cpython-37.pyc | Bin 600 -> 690 bytes app_package/__pycache__/models.cpython-37.pyc | Bin 3425 -> 3292 bytes app_package/__pycache__/routes.cpython-37.pyc | Bin 6887 -> 6885 bytes app_package/memento/func_objs.py | 22 +++++++++++++++++- run.py | 9 ++++--- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a2510e..692b111 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.pythonPath": "env/bin/python", + "python.pythonPath": "env\\Scripts\\python.exe", "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "python.linting.enabled": true diff --git a/app_package/__pycache__/__init__.cpython-37.pyc b/app_package/__pycache__/__init__.cpython-37.pyc index 9805a081a5c905e8fb5438e24645de1a18b76921..940156e60986d190bb9d4976d4197ee311394cbd 100644 GIT binary patch delta 261 zcmcb?vWbtjd|33)Ov#8%a+Q8_+i^8H#v-R1weQPmF#t5j)}wnr*cDtj|y6h{hcDn}M)3Y!f>3Oks^ z0c3H4SzI;@sVu1+Kvi6+Y+#x@g)xOYhbNaeikFcgg(sLnlXv2pxXIFt)r@?Tr!%@t zW@b{5;!Z3mh%ZP?&Q45EEn)@Qypo{^Y5QekKX1PmDj2DeWk!bwN`4vG@h-jg<9*7{p!WhX01BsEjAXxbaEQIas zEG;FSrH!QsigMWaOLXS^!t?ZCpWB_?ednFom%it8d^r}21n>*qSGOPa%Xp5lL-i^$ zYLgZDM!@WBU$HU^S!Pn*gw}<__sb2b2KbJC5r84Q2t#T&v={|}a}9@`Q0F`ucGqf< zrP_CViLs1Ih?T`II&Tn?gl<9)AxKCOdI`gXG=UTP2>t3*jHTZ*(%PM6X%co0l=5?xhCk<#%v zh9HNqOyQArM71XC6&uJuOc4B9NuCNgZ0ftl_@_(FBL^YecZ4PFmaMp@9KBJHja><) zX7z9TQ+RFeTR*~J3f-T1dr2?)zsUs>`O!#Ui!eh<)HKKws6L;<2LeDZGmqn=a&$%! f%j|@1b=^_SxEf^NjsMb6^RbK`H#&@{Aq?>bTYrX` delta 1024 zcmaKr%Wl(95QaVWiE-j2b({;hrIbrkY6=kraT7@v1gR3J76=IyBrpM=NT7tGaoI$O zZrC7}=Mh-314Uxbo;5;NH#|XKffm7+7jbN|1D9a!32-G{eAg*@6fw(1 z@Jf58H8d*JgK?pqmL6?l*M~$r?~f$kV>ujr(6Fk$8J*?C3PY4HFbo?k!fG0f%V}Y2 zFBSMT)i7@7utHfiG9F#QI<2t!Cu;oFBv=%&%L^fN0X1-;OdkgveX=U<0i00pa=i|p zhzA*m7#?Gm!7gx)ahTC$%rgYz2xCFn=8~6Xz^1e}xnXXASfr)e)fbz-fhDe5Vw4Gi zU&86*)$V~gd;ipvs%{<6Ji#G>rn+T)-=31--jm#DGm^us*(73JZuQU8kJ33?fM-%G zxxcK?cACpl!4=A$Wf74lS|D^>Q6KZ~w|y=>qfDFPSNG(OcLIL~J=H00tnuKHTS)gaG_=HO d)6;)BlwE!`(^o*~4iuqixD~hP+OFxEKLK_Ko_zoS diff --git a/app_package/__pycache__/routes.cpython-37.pyc b/app_package/__pycache__/routes.cpython-37.pyc index d14f0bc7de29b7af2c671d49a2f4fe480af01107..e99807cb7f8a482ecc5037b9ec5aafdbd405bf15 100644 GIT binary patch delta 1473 zcmY*YO>7%Q6rS0QcjNW?Ctf>V+i_w$agxoiT9HE2AfQxDniMCkK@fw8OvY^-<&W_K z!b%l^0~b`P>LDa9l_CzkfshXfghV+Z5FC&Yhuk=z96$)J2n25?$%c3|-@f_Y``*lZ zGd`I3Wg>Jj7&Hj}lpk-r_+9#L$fo!14!pIqo+)Q2sYX`2nZ|V1ep6xo+X^#SV3#y$ zxkq|Trr)HJ53-Qtvyu;ce1vsKJ}3F8$6G8W`Ml)g9^c6llHci-4%>4iSxP$kB%k*9 zE|!seLGs-m-@~$!?`Jud-zDV%*30_vJ1FA{UR*yLkZ}$hWDfd=*bp1uC8_eT3>@(S zN7s44+pVi#9ZER2LTlG9mc41dBV69TGFKtxT ztNVnfk!qx&qQ~KkZ_dY4xQ3?h@$Mcp%6=JpwN`icwL=-tBhP!`XWtx+z^JyYxym+e zQg~ncoc6#q{ahj^B1B$Cb1d=#n)MI@(G_Y?tu+Z);gU^jrauSs#GC5T*UVg)~h=Q9nhh&`LkHrkBmBrZ;79rL((<;aA=$1&x; zT6a0zi9DR%5xYbXNSlGjGZd3*R8tJfFT+7(Piedi|Hg*xitF;)ORu=qdiJ%NyHeZ8 zR}?93m+3O4TWev_b!_CP6VQfeu&XdO`M&$aYW&B zuoAtb56o28wyGSyi5BsmZEM87f{Hz%Z4+7*4=9*r6`n}MV9T1&3rCVatQh=cO`*(+ zowsH&=swOPnMwI$eyH8Su;M^|i&pA@Da zH}cc?B{~g%#|ycSMDCMvOs|~KVL2t3N?eEcI+s!+P0>-fEp{;x9F<=a_H_s*Qt<^e zzrtD95C~oXR=9xT&w7r9N#sup1@SbTv8?z!nqTAmzj_{~?7owyG>C54v7%Q6vt=Q-d%g+uh@=bJF&A)oDVl{K%s%Qq2i-Snz-$z#2_jcs7%IkTpT-^ z-GUM&DpE?-3lh;2AR$#sq{0O_AWIzJk^}06BCg1sI3R?O=#2vs5^pAr8|bd)KeNC0 z=Iwhk@7Jl{rUEbe{RYA3{@1U+k?-FL#OWtHqwn3Iq!PN8Vj9z#r>ZdT`wBBy*G;lb zOKEBGF}+G9_p^ZH1Cj?jJjA*sHzf~uc!WhI&q&_W;W5@L`5^KeDHiV-5-ceVLvll3 zhxfCTeg5o@2+@IL^l^p%~Vl z?>HW36Vh?K^&K6d)PO&fTd?JNRof)H?mg0KyH3$WA0A@cSgx0C`)Z@ST4@uWM6}Td zO}z(RP|xda)n2RD9N1BxPp8pT@=EUuHQQ-x2OD_?IUj@v>UkQ3X>D2Cq`S%riymiB)x=rdPz+*hpR}ae2;jl%-E(y!W8(RYoA*1xi9Ay3Bx7dmDJ;fh0(-) zLj3TTZz6gHm$raJSExZX>Y+yKgny4lO=OR%E)*9no`l~6^K=y^gC~O;n$O~}Uq)G< z0dIF2J`839hbb}@lhWYl;Nt0$ZeL$suGsd*aIv{saaL-Ls<~3O&1$nzG2h`${H!(1 z>uap+plQO&8HXo(E;I$VLeFI06@QAM5SK_3U&V*XYKlSKIP+QfDYWOZcpCofw!$X* z{kL?m!cTOGCgBr8=HPaCFtdZpgP_X8pjf>RM+`%T6kdSu!!z^@{1P-F87bi1RwDVx zJnq^RJKZS6Wl%m3cOw>^mVW<4E}&l_dMdJj*3TaK`6yp#eH7JbMx@d9@k$-5Z8q$c z+8Uoj#3u!Jl^KInc;HA4`r%i#@C(*J#2A{^qLI>^3u#uU&NOCIt7=JFeF*3_}lb<$UX_VirS4^jA zRx6Hq^n^@ojj{!SM0w!8_)h>uwt~nV2e!avBvvjDw-fttF*%Z*#cy5^B5sU}>)=a5 jMDbi?%rC>o$;tjF)nqCj#J4*hjz>^~9?hf2BdFTHP3A%y diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index 3115da6..b89c54b 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -1,4 +1,4 @@ -from ..core_func import (core_add, core_delete, core_add_attr, core_update, core_add_attr, core_del_attr) +from ..core_func import (core_add, core_delete, core_add_attr, core_update, core_add_attr, core_del_attr, core_update_attr) from ..models import Attribute @@ -111,3 +111,23 @@ def undo(self): def redo(self): return core_del_attr(self.className, self.attrName) + + +class edit_attr(Command): + """Command class for core_update. Accepts a class name and a new name""" + className = '' + oldAttrName = '' + newAttrName = '' + + def __init__(self, className, oldName, newName): + self.oldAttrName = oldName + self.newAttrName = newName + + def execute(self): + return core_update_attr(self.className, self.oldAttrName, self.newAttrName) + + def undo(self): + return core_update_attr(self.className, self.newAttrName, self.oldAttrName) + + def redo(self): + return core_update_attr(self.className, self.oldAttrName, self.newAttrName) diff --git a/run.py b/run.py index f122261..02b0ba8 100644 --- a/run.py +++ b/run.py @@ -1,9 +1,7 @@ import cmd -from app_package.core_func import (core_add, core_delete, core_save, core_update, - core_load, core_add_attr, core_del_attr, - core_update_attr, core_add_rel, core_del_rel, +from app_package.core_func import (core_save, core_load, core_add_rel, core_del_rel, core_parse) -from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr +from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr, edit_attr from app_package.models import Class, Attribute, Relationship from app_package import app, cmd_stack import webbrowser @@ -106,7 +104,8 @@ def do_editAttr(self, args): class_name = argList.pop(0) old_name = argList.pop(0) new_name = argList.pop(0) - if core_update_attr(class_name, old_name, new_name): + editAttrCmd = edit_attr(class_name, old_name, new_name) + if cmd_stack.execute(editAttrCmd): print('ERROR: Unable to update attribute \'' + old_name + '\' to \'' + new_name + '\'') else: print('Successfully updated attribute \'' + old_name + '\' to \'' + new_name + '\'') From fe9510d5eb6c4ebe83d81c87113df1616df8966c Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Mon, 9 Mar 2020 22:33:23 -0400 Subject: [PATCH 08/59] - Added del rel and add rel with undo/redo implem. --- app_package/memento/func_objs.py | 44 +++++++++++++++++++++++++++++--- run.py | 11 ++++---- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index b89c54b..c7632d3 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -1,4 +1,4 @@ -from ..core_func import (core_add, core_delete, core_add_attr, core_update, core_add_attr, core_del_attr, core_update_attr) +from ..core_func import (core_add, core_delete, core_add_attr, core_update, core_add_attr, core_del_attr, core_update_attr, core_add_rel, core_del_rel) from ..models import Attribute @@ -95,7 +95,7 @@ def redo(self): class del_attr(Command): - """Command class for core_add_attr. Accepts a class name and the name of an attribute""" + """Command class for core_del_attr. Accepts a class name and the name of an attribute to remove""" className = '' attrName = '' @@ -114,7 +114,7 @@ def redo(self): class edit_attr(Command): - """Command class for core_update. Accepts a class name and a new name""" + """Command class for edit_attr. Accepts a class name and a new name""" className = '' oldAttrName = '' newAttrName = '' @@ -131,3 +131,41 @@ def undo(self): def redo(self): return core_update_attr(self.className, self.oldAttrName, self.newAttrName) + + +class add_rel(Command): + """Command class for core_add_rel. Accepts a class name and the name of the child""" + parentName = '' + childName = '' + + def __init__(self, parentName, childName): + self.parentName = parentName + self.childName = childName + + def execute(self): + return core_add_rel(self.parentName, self.childName) + + def undo(self): + return core_del_rel(self.parentName, self.childName) + + def redo(self): + return core_add_rel(self.parentName, self.childName) + + +class del_rel(Command): + """Command class for core_del_rel. Accepts a class name and the name of the child""" + parentName = '' + childName = '' + + def __init__(self, parentName, childName): + self.parentName = parentName + self.childName = childName + + def execute(self): + return core_del_rel(self.parentName, self.childName) + + def undo(self): + return core_add_rel(self.parentName, self.childName) + + def redo(self): + return core_del_rel(self.parentName, self.childName) \ No newline at end of file diff --git a/run.py b/run.py index 02b0ba8..bc8a5b1 100644 --- a/run.py +++ b/run.py @@ -1,7 +1,6 @@ import cmd -from app_package.core_func import (core_save, core_load, core_add_rel, core_del_rel, - core_parse) -from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr, edit_attr +from app_package.core_func import (core_save, core_load, core_parse) +from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr, edit_attr, add_rel, del_rel from app_package.models import Class, Attribute, Relationship from app_package import app, cmd_stack import webbrowser @@ -122,7 +121,8 @@ def do_addRel(self, args): if len(argList) > 1: class_name = argList.pop(0) for rel in argList: - if core_add_rel(class_name, rel): + addRelCmd = add_rel(class_name, rel) + if cmd_stack.execute(addRelCmd): print('ERROR: Unable to add relationship from \'' + class_name + '\' to \'' + rel + '\'') else: print('Successfully added relationship from \'' + class_name + '\' to \'' + rel + '\'') @@ -137,7 +137,8 @@ def do_delRel(self, args): if len(argList) > 1: class_name = argList.pop(0) for rel in argList: - if core_del_rel(class_name, rel): + delRelCmd = del_rel(class_name, rel) + if cmd_stack.execute(delRelCmd): print('ERROR: Unable to delete relationship from \'' + class_name + '\' to \'' + rel + '\'') else: print('Successfully deleted relationship from \'' + class_name + '\' to \'' + rel + '\'') From a8214206334231542c1d796d8a50c79e5c6ceeff Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Tue, 10 Mar 2020 14:18:07 -0400 Subject: [PATCH 09/59] added test setup instructions, fixed gitignore --- .gitignore | 1 - tests/unittests/setup_tests.txt | 3 +++ tests/unittests/test_run.py | 39 +++++++++++++++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 tests/unittests/setup_tests.txt diff --git a/.gitignore b/.gitignore index 853bd2d..0070bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ app_package/database.db env .vscode **/*.pyc -__pycache__/run.cpython-36.pyc diff --git a/tests/unittests/setup_tests.txt b/tests/unittests/setup_tests.txt new file mode 100644 index 0000000..ae1c94b --- /dev/null +++ b/tests/unittests/setup_tests.txt @@ -0,0 +1,3 @@ +./env/bin/activate +source env/bin/activate +export PYTHONPATH+='.' \ No newline at end of file diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index a4071c1..7437c4d 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -5,20 +5,45 @@ ################################## CMD SETUP ################################ app = run.replShell() + ################################ TEST COMMANDS ############################## +# Test list and maybe save/load + +################################### CLASS ################################### ################################## TEST ADD ################################# def test_do_add_empty(): - assert app.do_add("TestAddEmpty") == "Successfully added class \'' + name + '\'" + app.do_add("TestAddEmpty") + assert app.do_list() == "TestAddEmpty" def test_do_add_dup(): - assert app.do_add("TestAddEmpty") == "ERROR: Unable to add class \'' + name + '\'" + app.do_add("TestAddEmpty") + assert app.do_list() == "TestAddEmpty" def test_do_add_more(): - assert app.do_add("TestAddMore") == "Successfully added class \'' + name + '\'" - assert app.do_add("TestAdd1More") == "Successfully added class \'' + name + '\'" + app.do_add("TestAddMore") + app.do_add("TestAdd1More") + assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More" def test_do_add_none(): - assert app.do_add("") == "Please provide a class name" + app.do_add("") + assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More" + +def test_do_add_multi(): + app.do_add("Multi1, Multi2, Multi3") + assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More, Multi1, Multi2, Multi3" + +################################ TEST DELETE ################################ + +################################# TEST EDIT ################################# + +################################# ATTRIBUTES ################################ +################################ TEST addAttr ############################### + +################################ TEST delAttr ############################### + +############################### TEST editAttr ############################### + +############################### RELATIONSHIPS ############################### +################################ TEST addRel ################################ -# write a test for adding multiple classes at a time (use list once its finished) -################################ TEST DELETE ################################ \ No newline at end of file +################################ TEST delRel ################################ \ No newline at end of file From 0bd66f61cedf5a4ac089a4b96a33a3cafd932e13 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Tue, 10 Mar 2020 14:31:44 -0400 Subject: [PATCH 10/59] Update characteristics with universal form; Disallow overlap As requested by client, all class characteristics can be changed, then saved in a batch. Also, upon drag end, put the class somewhere that is not overlapping. --- __pycache__/cmd.cpython-36.pyc | Bin 2640 -> 0 bytes __pycache__/run.cpython-36.pyc | Bin 652 -> 0 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 600 -> 0 bytes app_package/__pycache__/models.cpython-37.pyc | Bin 3425 -> 0 bytes app_package/__pycache__/routes.cpython-37.pyc | Bin 6887 -> 0 bytes app_package/routes.py | 82 +++++++------- app_package/static/css/main.css | 55 ++++++++- app_package/static/favicon.ico | Bin 0 -> 1150 bytes app_package/static/javascript/index.js | 106 +++++++++++++++--- app_package/templates/base.html | 3 +- app_package/templates/index.html | 89 ++++++++------- 11 files changed, 227 insertions(+), 108 deletions(-) delete mode 100644 __pycache__/cmd.cpython-36.pyc delete mode 100644 __pycache__/run.cpython-36.pyc delete mode 100644 app_package/__pycache__/__init__.cpython-37.pyc delete mode 100644 app_package/__pycache__/models.cpython-37.pyc delete mode 100644 app_package/__pycache__/routes.cpython-37.pyc create mode 100644 app_package/static/favicon.ico diff --git a/__pycache__/cmd.cpython-36.pyc b/__pycache__/cmd.cpython-36.pyc deleted file mode 100644 index 70ab4ce0489ef88a5b16a03b38f35bffc2296f0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2640 zcmai0%WfMt6eZ^&jpVoExM{lR6i5IINbJN-k)UXshite-TP2Mhq?tvh;~`~_Jg<;c z90|%w*%tkQ{y;ycTW-7RFLc#=IkId8L8`%!oZ%hv+;h*p)aMHe&Aq=mzwFfs`I}t$ z)nNPpZg~t1BaEgbsCpVuc(zh2vjaPG0w;3=H>(9TO1Q&oaM|2t_8)Fg-y_Un?pwlK zPI}(h4jQb+>TgNV|9dG3m{Z356Z@NvrV>>_$2*N3X;g3#E#L3Qzf=Ocd93 zd=)XKmvN9LQiX9ENy(*d;v2?Ou6WDUHX7BcM_m&1DR-m6}tLzf= z3v7*DhJKOR;NQA#2wtZ9$2?7k51#WhE;8<`!dJ)8^@U2gFL83y_x*!G$$fYz{X+Qn z(F)P~@JSZsOl~%Zjg9J?mfwld5&U6q!`+5ku0f-KT1CgCOV`Pm0?`%#v4>wg5lODh z#&KT=j^hfzF^^c1ANfh{GkzpEmtXq+x4RE_zU%A_*V_~0sg?Avvu3AdX}81`b$v1e zZyd?fE`&oI{Yk_UVEOm<)-iaz1+h5Xk|N%E_Tvv*+qbrF$@1o%?OQk7#pz)&*v*Pw za(4@2x>*i%lNI4)$xG;yanu^FO^Mt*H<)1o4GjzPhJ_I|-V^Yic3M=-!P}N47N8T0 z*dV~FS%#6W!?KD?^$|Q&o6_OR%%(Uyvm*slv!eq8BWP>5{VZpNzX2ceNO6`9T848F z?v(H-R!Na>$QJPYth4jDV^I7bwmz);31Wkryn_tq-jA#a#)?c6&Kllnr#un{lZ+wQ zxewy<1qU(YXNmyn#UKza0N~Tz{oMoLPrHIku0Dj)0|t%JrwDTrx78U6&GZb?Y1FP7 zq|8KZM0%iLN|a@gR|>wnuZj|o#)Uw=-tgnJkdQ{_sdSnE52rVbNBni70RE#FyThvo z$5DRb4~o9uh1BXxRQrJu|3S+(!qBc+EU*s;{6j9*z(p{*h7Nkr5X++;tKst{t@d5blNv4KjBFp-*CSX$Ew~>=ZX*Zoq8>eg_5+6eA=>F|~fb`UX7cSv`AX z^&F_`?%0%@=a2|Lk*DP6vxK<)B#j1#QG9alSXUI89~tMsu_r$~eDUaE`}?VDomNe( z!-nExXtW2;zU=cp*ERlH@tkQ#K*iEcS*D3flblNk;xw%_?Y^7YbKrbA0dX6alz7-- zY&obt zsP2}n-LyF6!aQ9pNO(}?{9W9&5<-%(d+=!`HjvsPTo=VwnZ#I$@Q3|OMq$vzI<{3t zF$iM_2YXc&tqKW}4HylFvsKZ=;XX`PoPpATv!!#+OxCoPv=@db(=gP{Fw6?pPjNgS zhA;b3TFnG5oED1{bl2hD-G4h&4Ngjr+1#M&2+j1a-wWut^5>hHUGPNM=_VZZacf!U7y%R1p0se>D zNn)k#cE$`1W?gX|Ce0tLv?$68x| diff --git a/__pycache__/run.cpython-36.pyc b/__pycache__/run.cpython-36.pyc deleted file mode 100644 index c2642c0db24b8f11772ecaba1da4ffac669034d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 652 zcmY*WO>fgM7`789b(5y;_&7}BuoI$(Eu|n$LI_ow*s0qPP$A`jmDcaNw6PPMMBOHC z<;p+c#0mZm7bIUf@fSE@cN~T-zkclJdHp`NpF~mc@aM_<9}Ysl(5>bfJT{~HW&%eX z=SbiLV~d^K5pLoNl927%%{}2KzGx*avnB}z7YO*!;@%gMv@bbO^%Vl{Z*USqxa@3D z5<#RsLB!E6O1jX6_R3cup$*+#WY`|`p|>&BB!xb2Sp*@cD@ToChqt#gUf$sW4?i33 z!}uNIoi}I@{WfmV0Bbib%YmcmyimYW&ULs&9CE0^5;auyaU)|Iy4?w zna)?~0+e}Qr0?I_IoKLHHx`s@I7O`B-hTqY*_O9|p?u5J*V9urIXio0Zf01X&1>yt zvTSM{G!-azgT1QEvs#CBu>$#TuYI1Ws%EKLXkS6q9Yy g2UL$t=-@u?JEY}c+bQue#nd4-;s6H@bu8+Wzk+(Vj{pDw diff --git a/app_package/__pycache__/__init__.cpython-37.pyc b/app_package/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 9805a081a5c905e8fb5438e24645de1a18b76921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmYjPL2ueH6m~)gX@IGdR`oV<qAS*l)75?Quu))+1l7yZIiX5FN-9M9-1!YO zQQOF?z`B!9ea~Y08-_U`DQjJ9ynz z>1NIKb^AqLBZQ?fA-H;9;o%e`CvPg7ao&mBTmRB}S{3e7TX z?~8*?$ib8}uY;3hG+xpNuBve18&XB1OZ8{q59V6M^V`XI-tYB$Iy-yY@16PS^CH~_ tGJQ(k%?|?12}z(AgmkiP?hIwhHiDnSZ$bJMD9dORqv9BDIUK_={{nrVraJ%t diff --git a/app_package/__pycache__/models.cpython-37.pyc b/app_package/__pycache__/models.cpython-37.pyc deleted file mode 100644 index 342593ba62c1324875bf417ab1e82df845c629cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3425 zcmbVPOOx9~5Z24`OZM8%lJE#b1q7E#)>#%Rq^KknSRO^e`aqJ4YC*`Fu`NdrXGX4I z7sVyHaLAnlhvdi~;5TsK7tEDY{sJeuM}Fhoq~a}&W_o(MHD7<-Gy7SkQc~de_n+VY zZ~=P%;9&kUpmPfz{yQ{8aTKBwEmR}T(IVZ^RUGS~5gCpl`$m|HOvhA}uN0Ca<|Boe zR2$|W>P`V9c~SsLVJ0bpq)192Da|A$kd%o95{v4?GOV&dQXy54RA-V3NOnjKB(<5O zO6s?jR%3eUFo{zyh`G(^h*3^qlm-cmlb)Rps7<`ov!jI2u>B0SqqH=kk*?4puce9t z%+fT7DD*8x!))mw82P9=|C!La1rPrP8mc%dV4@M#(TV05G&eM9?n$2LPgP=&++)ot zWU^tAmWE|wg2Vz1)T%sHo~llDZL~vch!LVf%8w1F4r2?(mD#vKs$}Pp;+!Eh*l}It z4nvRg>93zJHfHw{wy}NOy^ng7+d*vmW5xg(n*?5;c~QG$!%sGMIklUy7tyBeaeKsq z$YT@x9-Y`->U(3D#Yt{^jN0QkxIds}aMh7i(}?j>wA@Zy#( zs$I{&=Z=!m7~)3x!tXE%Vv+Yf?t27WeYXD#NWR%hBhmPFE-ufjg?hi?s@LwGo5 z3sHYohRRt5!k~4grR&m`eUaUST^jhE_~tb1{h_ckp#Sf+!%vPm^N;TyeS3Vcf3VL- zdmkO_?;R!&y2<2=C>aJHAA6&bJA#dPeR?cu%G;xfD7Y^2*>%sNbNmWLomR7tpHFpK zyQqhbU00M`7nYAh>|3sTf9!>`nY`c+_ z0&t7ofwW*)3WPaM{Wy6bEX)?yXB1vUfwBLlBAXK8@}YTAAhjS?QD^rXy82LVT|Zel|x z%g!t#iwnmL;e@PYMR>ytc|0x2pvZ)ikg|7B`rI^Z>8t{Z!h(83gMN$&&RG>iomQP8 zQ`tFeUd84$Y+lC(dB)zr<~%kRv3V04Bo})Nn+wp0(wCXpZ|Fp1UnsdPnZ+zr!=zei zBxNuX=!>Jqk}~I28KdoF`4Lo4BMZ==Kn$P(oMw-8r?^_APV;**q|XAtA}m|?3&F5K)98;O}uMS!^sg1Z0OT-|F1FtlJ73Lt;tH*az4< zk%R{@dmWxxFaRx-bXq&|coF55%TrcJ5WDQsQswtUFT~89v=bI(_mkWa{v(jpHn|%_ sM3kj811|e2jEQu$Erz0SJ-cb$M6$}OilNn1OSSYyyVMG5slWG@A0Q<=J(L_Q7H@Zf-S>X) zz2EK!Gcy$pziJLPc7nF=dTC7gDq!>UsaXPlXE)|ur#W-u4dJM-ZQ=LA0&gN1O>Sqx7) zC&N?DsqnOOIy~c?374Fu@T_w-Ty~bjbI!T&ymMaHy!po$ge6L%yl;r9Zwyfp(_d>Z zbmtKME0)RctZ7hrGgHL>$hTC zMxDfq?WBpye$zf^`i-V7y{|f6tcHgb8?B!G)b|c-S9*5ajbo4B=eH71x{bs>@RKHY z5^mz!VI;huUimlSrdG^~8v#aS(|p@*G`-Nxs#{**CVte4n|?c6$UpUaOx;Nm>F;84 zwNf@GF>6omBwpCYI4?6F$5AUQb!4!!7s;&DpKw-|p714Bo)!1-)n-=S^5Ql=+$s_VC&%eu&+tUSHm6V?_gb5V<0o8I0UFb&K>4WWuS>eNb4{Lf>3caKmiMT{3s^t9`lr+(?Q&gN2WH=&a#r-DjAU9-t8FG>0KAp>C0?9W2r7)_c(XPu zDZ*x!vUO$xzoDEaQ88`@ej?{lmsKhZmckWfH!DYyJYct*RSA+i4e8+~0O}x+7WFQn zs1@Z!+?*-?^e29tWR_Zre3SZILh)x!zKz>=?`*vHD3*;!pZ)TaM>lWWyb-t8Zr!}G zwhXj2uVVwCkVu8LmP7xc2Z zfKslZEuCG5o`%l6KEyU<6S^4NTc{{(6Qa3B*w%acp#d}+&sKW|u)G3X7J6E*@WPOn z5>pg<1)$kH1I*)IWK>@Qs*6B4JfT)fpT7d1WIMNI4-LciRMxCBh`6{z_$1hjFgiAZ znh17;^i|v6v*S*q;l=S@C*bjHjBIK+CVk^r5DnYy)MS9$PzR%~W-(gi3JES#af%8? z2VtgWW+s8uDbqVPhc&jmNiw#zDpoNdD`Vvxndm8=MWI<0HXD`8y8HnoX?a~m#F4p> z@->1Po9iwr-Ibidj2_Z1Oi~c2!4xo%u6?exPU{-|DaPZ^@#dpj-l#Htom~5y+ZYBP7|uXClG)qd_gJRZ@pgayCF(88+$2R^Om;CrFT~Ve&c=D|^omJa#zSb}1oKoOBya(*D{SGM|VJTEvFd)|mN? zq9ZvA4|JwzW^C-u^vc7g2jX}@@3Px6LjDYGwEM07y8UouOPlmOkDL?u4Q7YyG@Eph z$SQqW_~3G}PrNZbHJZ|Go-cj+r>VsYxQ*;v4t=E9ZrjUBP0u9)a5SPW;DZV)GgRY< zYOpI4L*$RflV~w%VZTB5m_}NIsz% z400uLygt5$Opp4G7$c{_ah^w?2T7!N@K{hMOeMW`B%1TesT&|oVC^U9iMURlp%770 zjI2nni{(ieN0w1!CcP*ZO7yIFpZZK7LcWjAAubi>rw9Hc%iHVKyR=xOfB# zqCdl6{}2z|yE#ydf<4s=phHGtJnjSKd7-EG3Ok_lC@n?bUm}PIa}Y{I5y8V^67=qI z5aE3J;b$L{ra6iTfMB0kIL*>;fnCOR$N@9~)DF%6hcZ0M>Vdy6{ zQ{~k=H1GoyPDyEU(gs;Iui@XJXM7rkR_I4^qY86aMnyMqm-o<;&aa1V%WvNqQ1g*l z3TPghrHzN~r>{3l&k!bdV7CTYS=U<2kQE`@WWK8NcsQ8Vy~`j zGINx5d_1$T!)Be__Ru zVKg5}(QCp5ve|K6{sCQ-0gOQ^E$dYT$Gz>}Kp_Vz;?t0@Pbkw%N8-k0e>=5Qq-5=F)(lCQVy&Rggzp9k zCRpD`kR9TR$-T7#G6G2&v_$&RomzOgWMH*i?9dDH`q2er;k09C_CPL;h=2S07Ow0# z+T6tClgzB@$1HoYH*G@DKgYpn>1Qxgwe$inooN+8#UWZnl}SB(j1hmr)z?E+mn2CS zmEYx$d|u&tkI}t*f86yBap>_1~xDjL!QVm`8YwH_l{p#pK~sQpVvPc z*CHtvD;l=|3Hu1Ydc?6Odn>P=063jw3;_Nq)qjN|D>j;bASR*x08M|#6#ji3(qx9fDoY68*S^vXRtuFIe0VcO%vi8XH4N|Hh~a~4^n8lA{y zrs#xi#Sx2CjcWzRghgVff^sKTXWRvxtWPK|o10M2w|JeRjl@(4NVvdIXy?CKTg^&4 zJ0fcA>|_hMlejt`u*ZBnC`qeok~B3w2~=m_PxwHPXDO$siH;q4i=+IXc0Ad1?zM83 zIxr7Al@r(b0GmRXI+^5?58C?3MJkvs^Pvud(|-10Qb;S z-x2hvE#PCm>8tDh0P2thHXX7;tvT6JEnYaM%MPE{m^);!r^XpQzIWEoPNOb@aZti}Dh;{Q z8ar6F40G(MaYm2tWro=+-~vbhk@n(s`d1wp0hc|Fj@S;)Jdb&qVV3vc_`2ZQwnaKl zRAX+rhUfQ_u&2gZ-RET@;s2q{GJV)r%)vW|=d;6LPt9hy#?WVOwaC|)$W&Bh*!e%9 Nh$Q18TULrN`w#Tp5*Ppg literal 0 HcmV?d00001 diff --git a/app_package/static/javascript/index.js b/app_package/static/javascript/index.js index 03eee47..77ed154 100644 --- a/app_package/static/javascript/index.js +++ b/app_package/static/javascript/index.js @@ -37,9 +37,15 @@ jsPlumb.ready(function() { jsPlumb.draggable(document.querySelectorAll(".Class"), { stop: function(params) { ensureValidCoords(params.el.getAttribute("id")); + ensureNoOverlap(params.el.getAttribute("id"), "none"); + jsPlumb.repaintEverything(); updateCoords(params.el.getAttribute("id")); } }); + + window.onresize = function (){ + jsPlumb.repaintEverything(); + }; }); // Toggles the navBar sliding in and out from left @@ -74,30 +80,26 @@ function addAttribute(name){ // OnClick function for the edit button function editClass(name) { let attrNames = document.getElementsByClassName('attrname-' + name); - let attrDels = document.getElementsByClassName('attrdel-' + name); - let attrEds = document.getElementsByClassName('attrupd-' + name); let attrTexts = document.getElementsByClassName('attrtext-' + name); + let attrChecks = document.getElementsByClassName('container-' + name); + if(document.getElementById('Relationships-' + name).style.display == 'block') { let elements = document.getElementById('custom-select-' + name).options; document.getElementById(name).classList.remove('activeEdit'); document.getElementById(name).classList.add('non-activeEdit'); - document.getElementById('attInput-' + name).value = ""; document.getElementById('Relationships-' + name).style.display = 'none'; document.getElementById('addAttributeForm-' + name).style.display = 'none'; - document.getElementById('attInput-' + name).style.boxShadow = 'none'; - document.getElementById('attInput-' + name).blur(); document.getElementById('custom-select-' + name).blur(); - document.getElementById('class-' + name).display = "block"; + document.getElementById('class-' + name).style.display = "block"; document.getElementById('classtext-' + name).type = "hidden"; - document.getElementById('classupd-' + name).type = "hidden"; + document.getElementById('attrsave-' + name).style.display = "none"; for (let i = 0; i < attrNames.length; ++i) { attrNames[i].style.display = "block"; - attrDels[i].type = "hidden"; - attrEds[i].type = "hidden"; attrTexts[i].type = "hidden"; + attrChecks[i].style.display = "none"; } // Deselecting the selected options when the user is done editing if they selected any @@ -111,18 +113,18 @@ function editClass(name) { document.getElementById(name).classList.add('activeEdit'); document.getElementById('Relationships-' + name).style.display = 'block'; document.getElementById('addAttributeForm-' + name).style.display = 'block'; - document.getElementById('class-' + name).display = "none"; + document.getElementById('class-' + name).style.display = "none"; document.getElementById('classtext-' + name).type = "text"; - document.getElementById('classupd-' + name).type = "submit"; + document.getElementById('attrsave-' + name).style.display = "inline-block"; for (let i = 0; i < attrNames.length; ++i) { attrNames[i].style.display = "none"; - attrDels[i].type = "submit"; - attrEds[i].type = "submit"; attrTexts[i].type = "text"; + attrChecks[i].style.display = "inline"; } } + jsPlumb.repaintEverything(); } // Displays "Add Class" popup, closes all other popups @@ -265,11 +267,85 @@ function renderLines(){ // Put the element back onto the screen if it gets dragged off function ensureValidCoords(name){ - el = document.getElementById(name); + let el = document.getElementById(name); if (parseInt(el.style.top) < 0){ el.style.top = 0; } if (parseInt(el.style.left) < 0){ el.style.left = 0; } -} \ No newline at end of file +} + +function ensureNoOverlap(name, lastMove){ + let el = document.getElementById(name); + let rect1 = el.getBoundingClientRect(); + let classes = document.getElementsByClassName("Class"); + for (let i = 0; i < classes.length; ++i){ + if (classes[i] !== el){ + let rect2 = classes[i].getBoundingClientRect(); + var localMove = "none"; + + if (!(rect1.right < rect2.left || rect1.left > rect2.right || rect1.bottom < rect2.top || rect1.top > rect2.bottom)){ + let distanceRight = rect2.right - rect1.right; + let distanceLeft = rect1.left - rect2.left; + let distanceTop = rect1.top - rect2.top; + let distanceBottom = rect2.bottom - rect1.bottom; + + if (distanceRight < 0){ + if (lastMove != "left"){ + el.style.left = (rect2.right + 1) + "px"; + localMove = "right"; + } + else{ + el.style.top = (rect2.bottom + 1) + "px"; + localMove = "down"; + } + } + else if (distanceBottom < 0){ + if (lastMove != "up"){ + el.style.top = (rect2.bottom + 1) + "px"; + localMove = "down"; + } + else{ + el.style.left = (rect2.right + 1) + "px"; + localMove = "right"; + } + } + else{ + if (distanceTop < 0 && (rect2.top - rect1.height - 1) > 0 ){ + if (lastMove != "down"){ + el.style.top = (rect2.top - rect1.height - 1) + "px"; + localMove = "up"; + } + else{ + el.style.left = (rect2.right + 1) + "px"; + localMove = "right"; + } + } + else if (distanceLeft < 0 && (rect2.left - rect1.width - 1) > 0){ + if (lastMove != "right"){ + el.style.left = (rect2.left - rect1.width - 1) + "px"; + localMove = "left"; + } + else{ + el.style.top = (rect2.bottom + 1) + "px"; + localMove = "down"; + } + } + else{ + if (lastMove != "left"){ + el.style.left = (rect2.right + 1) + "px"; + localMove = "right"; + } + else{ + el.style.top = (rect2.bottom + 1) + "px"; + localMove = "down"; + } + } + } + + ensureNoOverlap(name, localMove); + } + } + } +} diff --git a/app_package/templates/base.html b/app_package/templates/base.html index bf49b16..caddfba 100644 --- a/app_package/templates/base.html +++ b/app_package/templates/base.html @@ -5,6 +5,7 @@ + @@ -62,8 +63,6 @@

Load File

{% block elements %} {% endblock %}
- - diff --git a/app_package/templates/index.html b/app_package/templates/index.html index 04c4158..b1c45bd 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -16,55 +16,54 @@ -
+
-

- - {{ class.name }} - -

-
-
- - - -
-
-
-
-

Attributes

-
    - - {% for attr in attributes %} +
    +
    + +
    +
    +

    + {{ class.name }} +

    + + + +
    +
    +
    +

    Attributes

    +
      + {% for attr in attributes %} + {% if attr.class_name == class.name %} +
    • + + + + {{ attr.attribute }} - {% if attributes|length < 1 %} -
    • No Attributes
    • - {% else %} - - {% if attr.class_name == class.name %} -
    • - - - - - {{ attr.attribute }} - - -
    • - - {% endif %} - {% endif %} - {% endfor %} -
    + -
-
+
+ + {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} From 8527e1ada621252a17a9421bc162ce404726f869 Mon Sep 17 00:00:00 2001 From: Ryan Webster Date: Mon, 23 Mar 2020 15:02:47 -0400 Subject: [PATCH 20/59] removed workflow --- .github/workflows/pythonapp.yml | 38 --------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/pythonapp.yml diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml deleted file mode 100644 index e59e371..0000000 --- a/.github/workflows/pythonapp.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python application - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pip install pytest - pytest From a6ffd0858f7b0943331f6c0e72579a71226b7384 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Mon, 23 Mar 2020 21:36:57 -0400 Subject: [PATCH 21/59] Fix merge conflicts with undo/redo Also, minor bug fixes/tweaks in func_objs to get expected behavior. The issue with requiring two "web"s to start was caused by being in debug mode, as far as I can tell. Lastly, removed some .pyc stuff from tracking. Hopefully, it sticks. --- .../__pycache__/__init__.cpython-37.pyc | Bin 690 -> 0 bytes app_package/__pycache__/models.cpython-37.pyc | Bin 3292 -> 0 bytes app_package/__pycache__/routes.cpython-37.pyc | Bin 7550 -> 0 bytes app_package/memento/func_objs.py | 5 +- app_package/routes.py | 68 ++---------------- run.py | 2 +- 6 files changed, 10 insertions(+), 65 deletions(-) delete mode 100644 app_package/__pycache__/__init__.cpython-37.pyc delete mode 100644 app_package/__pycache__/models.cpython-37.pyc delete mode 100644 app_package/__pycache__/routes.cpython-37.pyc diff --git a/app_package/__pycache__/__init__.cpython-37.pyc b/app_package/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 940156e60986d190bb9d4976d4197ee311394cbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 690 zcmY*W&u`i=6n1_PLV%7=nzqx#jvNXMRhop*v@{JGDzqBVLoGDRO-!3&lgtjP;54a! z&HkUAa@}eF!cKb$o5pPU`FqdLpZ&deRr|`MHkJkKFk?3;-Wssjpx{gi)x4 zI@g4bbuGsRH-w4J0-L-fENm6H#LL3QwuYF_Do+}ALW?;M)v!E3poQ5sPm?U-uPb=VBz^)kBKNN|Uos#Ou3 zP^s<(AiY}Zhm zgvq92fn;fO#kSa)`QFr@lMDX_-nV4DNtwEA9cN`i!Z;uVmzKFO39Ui6QD6e!qI(H4 z?+`xThs8hL(#+#;Xp)cwl+ZiY7EG{p8n^$GC`*u+*@UdAV1#4_+`!hps#piDx}aGl z5PvF?RSy`Y1$Vz8S%a-GzxrN(p=7YQo{Sg0Zm+A7lha=J#ETzq)GIKiR@+a*)|_J6nyp#JZ)#4#xc~qF diff --git a/app_package/__pycache__/models.cpython-37.pyc b/app_package/__pycache__/models.cpython-37.pyc deleted file mode 100644 index 4b6610ac4c8a794c08812e0f909780735d4e7a06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3292 zcmbVOOOx9~5VjtcU$WQh1X4f>M1V3TS!XM}AE5%vqbS%tkmTapLdc%+t{lm7W@LhW z4Hpi%bKsC1`6v7Us;HVPr~Cy@d_A(gw()LKu}iI)o}O;aH(&S6=hbRChoAXZ|A)Uz zx!gb0ng47wZs3u>L$F*Y#|&mhMr?M>*y>mY^{vQ`?T)S6c9f5uj$`D$&apgme#tS1 zn}foAt5bxgz>3fmXPOc;C02%}Jkyk+sW2BBms^7hX1UN*Sq+-nOjCtsi`Aj2&oni* zeKXf;OfT;x!!!to(i40nxa8=iVS;|r_tInTu^Z9Bvqf=YyZSgu`M5wUJ1G}@0zKec!)^D&HR=TC~dr=_e^w%#I z3-bDjSX(x&x<__gdg0LPjfDUr9t(pb5yb7Xho2U=CHI=cAm&XkklsjyaUdq%9X|28 zycdiyN`ul11oy_n@a~v~%=oT372jeJJrO(n- zgxztt@>iebboL*WEwLH@9izL>;4qE3_(brAiS7qM^X~q#p+x7js z;~=6hLa*TaEa{<9_5C#HMm*CC6EG-eFNwzSP&qfT*&a;<(Y%O9)~R9J zrfr-z8b%HOx}irmXT>qhpEWB^a)+{m6=pePAxE|-CwT41nB@Vo0(nps6@;7q_QWhW z!E9q9tR=zK=8T!NUrn!<#G^!pDL*Ya(&JE)RX6d_$Py$=iqaymATW^_0`)bA7Zu!T zS?{0byoOY~LfR*0Ldz0W7%CSj&%>i3<(L#Ts191&VjFMKAaRbwc@oc&AWn+sNn9ZD z8i^N35D&$RBwm70<*zcg-|~sdzEJ0ZCZTZ2Y(cQBb%}Je6XaZy#R*|98hT_iS$>4+ zS!4kXip&NLaF9H(I;G|Ex;9qk6)Kz@W%kIV@`>W&78Ez|$d@6OIe^36WEKv0>pl)tW@88Q zs-i3Q&E7GOgXw$we13|1IGdQ)op^m5b9Z)dY{}yo$3$|E_HQ7QA&#h?KFHfE;f;BC z`mlL;ZXuw7fabKm6tGS44qEwjAgM&Eye^rWGD6ujZx=?Auq_(mGF}_}=nSQ?OJ5PjX; zCtY*6-N`c(KCJ15|NGEbwM@~(Eb%79LCaagO*yzVL`-T2Jlm`r QuHjmZTElHr8cxIc5A#G5LI3~& diff --git a/app_package/__pycache__/routes.cpython-37.pyc b/app_package/__pycache__/routes.cpython-37.pyc deleted file mode 100644 index 8160b67fa8baa99218e1ed769c689996ff6b08cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7550 zcmcIpNpl;=6`l=)g{!EI+Q-@A$HI2sW?&=3(mXegA4A3z;PX2^QX686M0b( z#VtdWzA?m@7=Nxk)7^{QG9e0EI?E@;6w5EMd^(jM5i=~m%<`kD{Fpe-@+&Mqk;+es zS(aaA`KeTXTAX3|HI|=E<>$nCmS1Q2g;ef{*H~T_7saLLntMZB7FY27I`_Mp_PZvo zbH6!J7B|p7FJ2dO_^xoj`Ltg}EO5UCmM^CAH^dUl7g_#hDqj{qVEG&3=DJpXtN(nZ z-D>%@D0DVuyA{p*E#X9MXTz&KqSBBE9c_eN)sdE zG5%Vw)&0PW$JhL(7d6_gu-@pzGudBBkK%h#BpVx8Tsa?4NvztJ)yNMz80W{zPd+YaO^TRv$%X*v-{HWd*VU5&BA3J$g@E!gJMWk)(LVsw; z4b%)_oYJ0Z15KEaT#ig(J=52<$QrFDBiX?H9tZjZmvcPCzGPpxQ@C6lQn7fp%$z#8NtTNJ*r)-_QC z^-BG}eBgV{P%$E<5x7Sb+VDc(X#mM5V4#D~y_Q2;-{aey108{qihbZ1tK9Lx-aTAH z9tTFYokkS;%}uIFe_jzpP225&h8Rs`+?xiv-FxpGD#RuteRfT1aIGuR91RCcxKa&# zAoQy(FYseco`a}==FZyMy|r7;=Phre>C-x}`eey*&X=++`z5v8dK5H4wLHJX591Os zg2^4qX}oK-JT1w0$F>4Lw!rE@jzJ`kQgMt5>KW(#Cw>i9A;(E%kSGUZmA$yomSiIv zy?C7VU#&@>)BxHyo03Q*^C-%;d<|c2k=OD`BaC8O?Jc&k{*^$;%hdM@ir;H;0bk#~ zy>jb8C~FTs|MZgwON&d3VP}4MX>op~y|dBo-3i*;jkg}awyGUil(*$SP(G#7>BTwT zKP@~wjY2DyjFM5%3wTO8Ju`a2nn5YwKx=<;0ZT{?e<3x292zNtu?ajtMVSB*pKD|S zdSncA5Zrijb6|k-6;RzAXan<^0s30Ixq$`JUj$vvfeF%ECt)=B&N2Bfg8X?H0!*iz z?>~JBi3wn@<)r8_=nC=*GmVS)h{VLLA(2N$P!n;J%wf)HY&v1LR`bJfv)knH3=n+I z9!L5Y4rN-}?({9Tf=f^gQ?p!PHpy!wVE&w>8o?m1Q^DLJcELSc#A&xcZ(B`k%nOTT z;HrXzm_ck%#Ug6)1e(~-F_kM2g*1m|mlAa8`?M}^Le?)VD9<@WmB>rHWK>sC=~Xhg z8p?r1=B_o+f!th_6OaKhAgXI$Xf0$H^ar`6WM645<4X+)=NL!3-*^d*(kIvJcze*X z+SgdPA33%S3Zua*F^g@ryZ-qDSgD4@F-QWbKmKv+vDa(}hjgzX%V!`vjy{K?Wym+t z6c^Pi4u(8CN^gU}D!G_QPUMV(t#-`BsUxJNV-9BvYAj}?)Or!N`Tng=`P;R@>@^`p) z2u8`I3hazI_0R1g<;ksXfWQ#WIg#u+-WIZ_%0cK(Kz)ZoXS#W;ge@$Og-`{)9NRjG zVr;AB+#?`oP-r#*ss2vu4CT9!_U#2qTZTZ67}Q7T(YuiW(s)Kr(i{Lzfh<8ezmr-+ z7xp*iI-3%V$)W_D00Xo|f=ZO&636;~cnMsz0w)_Zoo2g+h|^0#{N43?tBuXxXcraH zqpoZvelqMfBV-0Zy+WCtv)O3+Y?O}Ug^A1cYIOo&ejbrlw0ByRr_IlE>N#|@WKcZN zEz-&q-qQ$9%|?wA&INjZ|0w0Z^fJ*vdKfk+7408~ze^^0hgU!^`%&NKCB0KN;zH0s zeC~CCT;2C5h3H?sU-yv{@6alpj%*_n2pr7ZXl+%T`$M8!0vgPgRRmxP^~yOPq1pr$ zLzkQwd7-A381DDF7H!PKm~aw>mM3o5aI-eP z0>KmcBb@B_aIpV*2`gxRsKcN4pCH44GMsMcOSV+yOFb4`3!N}(OXLg*TUMGrQtlD5 zz~1V93#{@GRf!kQR^u@Q8$EsngAEW`N%H6eL7JxYSt-kx_(Q-RJz)FjUD^ZR5KS?7V{3SJjVvUXWO5nJ@4 z3@h|FcbEF?V}o2l=SO%H8Hz-PN&K;spf==G%A2gTkspRbQjFwO9rVmlV(9a!W+J;X zB4eApK4^lxDu=vE$8o|$W<|#dWL9t(h3(>Q=>;4KoQt_VM$zD;7LB7m@r+`Fo&1`l zUz}(C@G$ytto`uwj|m0#E&>GEqSFBFpwU878&*^xw3_~A6q`}I)3?c^-a;x=`jX49 zZaL+tjHQuplw}lgY0HnQZCPDjT$X1^N;!+li>9bOA0iinKR{mG4uVFcrZPk1Li&?f z@0D{bkw2wjpP+E_O2fXJr?lb_hd!Q#;5mk<_-wIDH^%jG9eJ@XKSfJ_dLi&yjm~>} z+20T`6U}LyCd7K5y&AKDAuJpUmysEBb^7uyI>HepNxAf0gHx^>IHT(W zGAEqehv~4u3A~)^U;9X@5X#)kare~1cR@jt@%Aqsa9sQQJAGTZYS!3l*^;wZQN~y( zVwFv03(Kg-r9_7Z4M2W|_TS+NiNBgjyk}XBwo4y6-K8hIxA8-5oqdcM(dOyOV)@|a zp#-|Zuqb5_IVi#IUl}Sv;h1G2Sg1I7hzj=irh^swXYxT(!zrw8zZxu71E+XI8kbT$ zs>EZI;!*7orTh#p`Xio1DR2V1Bw7Ecask;}v6py(ZFKM59rXgcxDtYwR<_N)JMjZI zmCci;S~H0s_=qlwh+-r5pA)volPt~J3!JqV56{}O9y??IxZ_Ii_`>gR^YM8018hWkfoET$RlyVVN#4Qkx$kK4tZ|zMvbs3PLVfzkvhS&;V>U06K7y^%L>=F#O2=0w%y7 zppuY(&xw*>#opi!GGLG}v4U#{9KrtH$_wl>0=msPXap`ve~u!~)#{C=;EPkJ<`D9K zgOLA^NdTAnE^2HE$lGn#AjkYKQrp=;y`({eU_MJ|hY}R7Gd7mshArevNiM z${((vFVXy0JfjLCX@6z`JjSS@b&a}*G=^M9Z*mU12JUd_b{A*AZ{QjRdXmcFR(RK@ zxB~8&{pCbKe1AtAp-g*pqm35bO!CCk;qH2c?yN1?K(CyaL}pI1X5@IOsU z<6l1QCMNs$$h0I`Eic9SYE`sr)oMJGxpb}Ye_cYp5tc*%1qMODm%r*CA`kfvn9-0$ zYT~0P-!oG<=c8ymmU*qDGp3}xSn29`rme#NBOym4`5eoK7rJhY&t-cBJ{{ZJ73x@R zZ)}I^Hk&WG__~BICueC24z283*mx*Ak8{jMz8~g`M83t6N@Gg|tZTdeZt%WyGSjJiXvVIJIbGg&!XQyY6%+8=Z Oisx7XpQId{wf_r126(~% diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index 9d5dbdd..dc62e17 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -55,7 +55,9 @@ def __init__(self, name): self.className = name def execute(self): - class_ = Class.query.get_or_404(self.className) + class_ = Class.query.get(self.className) + if class_ is None: + return 1 self.xPos = class_.x self.yPos = class_.y self.attributes = Attribute.query.filter(self.className == Attribute.class_name).all() @@ -153,6 +155,7 @@ class edit_attr(Command): newAttrName = '' def __init__(self, className, oldName, newName): + self.className = className self.oldAttrName = oldName self.newAttrName = newName diff --git a/app_package/routes.py b/app_package/routes.py index 0b8c64e..288676c 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -6,20 +6,12 @@ from app_package.models import Class, ClassSchema, Relationship, RelationshipSchema, Attribute from flask import render_template, json, url_for, request, redirect, flash, Response -<<<<<<< HEAD -from app_package import app, db -from app_package.core_func import (core_add, core_delete, core_save, core_update, - core_load, core_add_attr, core_del_attr, - core_update_attr, core_add_rel, core_del_rel, - core_parse) -from parse import * -======= from app_package import app, db, cmd_stack from app_package.core_func import core_save, core_load, core_parse from app_package.memento.func_objs import (add_class, delete_class, edit_class, add_attr, del_attr, edit_attr, add_rel, del_rel, move) ->>>>>>> 207270b5a320c3b55d928e2ad34879a9409e7e14 +from parse import * @app.route('/', methods=['POST', 'GET']) @@ -52,25 +44,6 @@ def index(): return render_template('index.html', classes=classes, attributes=attributes, cmd_stack=cmd_stack) -<<<<<<< HEAD -======= -@app.route('/addAttribute/', methods=['POST']) -def add_attribute(): - """Deals with requests to add an attribute to a class. - - Adds the requested attribute to the database, if successful - """ - name = request.form['class_name'] - attrName = request.form['attribute'] - attrList = core_parse(attrName) - for attr in attrList: - addAttrCmd = add_attr(name, attr) - if cmd_stack.execute(addAttrCmd): - flash('ERROR: Unable to add attribute ' + attr + " to " + name, 'error') - return redirect('/') - - ->>>>>>> 207270b5a320c3b55d928e2ad34879a9409e7e14 @app.route('/delete/', methods=['POST']) def delete(): """Deals with requests to remove a class. @@ -87,28 +60,6 @@ def delete(): return redirect('/') -<<<<<<< HEAD -======= - -@app.route('/update/', methods=['POST']) -def update(): - """Deals with requests to update a class. - - Edits the requested class in database, if successful - """ - try: - oldName = request.form['old_name'] - newName = request.form['new_name'] - editCmd = edit_class(oldName, newName) - if cmd_stack.execute(editCmd): - flash("ERROR: Unable to update class " + oldName + " to " + newName, 'error') - except: - flash("Invalid arguments, try again.", 'error') - - return redirect('/') - - ->>>>>>> 207270b5a320c3b55d928e2ad34879a9409e7e14 @app.route('/save/', methods=['POST']) def save(): """Deals with requests to save current data locally. @@ -159,16 +110,9 @@ def updateCoords(): db.session.commit() return "Name: " + updatee.name + "\nX: " + str(updatee.x) + "\nY: " + str(updatee.y) -<<<<<<< HEAD @app.route("/manipCharacteristics/", methods=['POST']) def manipCharacteristics(): """Deals with requests from GUI to manipulate characteristics of a class. -======= - -@app.route("/manipAttribute/", methods=['POST']) -def manipAttribute(): - """Deals with requests from GUI to manipulate attributes within a class. ->>>>>>> 207270b5a320c3b55d928e2ad34879a9409e7e14 Delegates to helper functions """ @@ -202,17 +146,14 @@ def manipAttribute(): return redirect('/') -<<<<<<< HEAD def update(oldName, newName): """Helper to update a class's name.""" - if core_update(oldName, newName): + updateCmd = edit_class(oldName, newName) + if cmd_stack.execute(updateCmd): flash("ERROR: Unable to update class " + oldName + " to " + newName, 'error') -======= ->>>>>>> 207270b5a320c3b55d928e2ad34879a9409e7e14 def delAttribute(name, attr): """Helper to remove attributes from class.""" - delAttrCmd = del_attr(name, attr) if cmd_stack.execute(delAttrCmd): flash("ERROR: Unable to remove attribute " + attr + " from " + name, 'error') @@ -229,7 +170,8 @@ def addAttributes(name, attrString): """Helper to add attributes to class.""" attrList = core_parse(attrString) for attr in attrList: - if core_add_attr(name, attr): + addAttrCmd = add_attr(name, attr) + if cmd_stack.execute(addAttrCmd): flash('ERROR: Unable to add attribute ' + attr + " to " + name, 'error') diff --git a/run.py b/run.py index ec7e2a2..bc8a5b1 100644 --- a/run.py +++ b/run.py @@ -152,7 +152,7 @@ def do_web(self, args): Usage: web """ webbrowser.open_new_tab("http://127.0.0.1:5000") - app.run(port=5000, debug=True) + app.run(port=5000, debug=False) def do_undo(self, args): """Reverses your last action. Optionally provide amount. From ac9f41e8b60373a04d8e68c2691154c8300c816c Mon Sep 17 00:00:00 2001 From: NatureElf Date: Tue, 24 Mar 2020 13:41:09 -0400 Subject: [PATCH 22/59] Added parse to requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index aabaa81..4f8fdb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ marshmallow==3.4.0 marshmallow-sqlalchemy==0.21.0 six==1.14.0 SQLAlchemy==1.3.13 -Werkzeug==0.16.1 \ No newline at end of file +Werkzeug==0.16.1 +parse==1.15.0 \ No newline at end of file From d475c81556afb8fd4a00b6c97ede7781d5db6e79 Mon Sep 17 00:00:00 2001 From: OcWebb Date: Tue, 24 Mar 2020 15:01:01 -0400 Subject: [PATCH 23/59] Create pythonapp.yml --- .github/workflows/pythonapp.yml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/pythonapp.yml diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml new file mode 100644 index 0000000..65d280c --- /dev/null +++ b/.github/workflows/pythonapp.yml @@ -0,0 +1,34 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pip install pytest + pytest From 7a26d817f332adaf971418e383d8ab6f5ad058e0 Mon Sep 17 00:00:00 2001 From: Deven Date: Tue, 24 Mar 2020 15:22:53 -0400 Subject: [PATCH 24/59] add test for the model update that's about to happen --- tests/unittests/test_routes.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/unittests/test_routes.py b/tests/unittests/test_routes.py index d972245..1df29f4 100644 --- a/tests/unittests/test_routes.py +++ b/tests/unittests/test_routes.py @@ -248,44 +248,44 @@ def test_attribute_invalid_args (test_client, init_database): def test_add_one_relationship(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], action="add"), follow_redirects=True) + test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data def test_add_many_relationships(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2', 'TestClass1'], action="add"), follow_redirects=True) - test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass1', 'TestClass2'], action="add"), follow_redirects=True) + test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2', 'TestClass1'], rel_type='agg', action="add"), follow_redirects=True) + test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass1', 'TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2"}' in response.data - assert b'{"from_name": "TestClass2", "to_name": "TestClass1"}' in response.data - assert b'{"from_name": "TestClass1", "to_name": "TestClass1"}' in response.data - assert b'{"from_name": "TestClass2", "to_name": "TestClass2"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass2", "to_name": "TestClass1", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass1", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass2", "to_name": "TestClass2", "rel_type": "agg"}' in response.data def test_add_two_relationships(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2','TestClass1'], action="add"), follow_redirects=True) + test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2','TestClass1'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2"}' in response.data - assert b'{"from_name": "TestClass1", "to_name": "TestClass1"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass1", "rel_type": "agg"}' in response.data def test_add_one_relationship_but_then_delete_that_relationship(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], action="add"), follow_redirects=True) + test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2"}' in response.data + assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], action="delete"), follow_redirects=True) assert response.status_code == 200 - assert not b'{"from_name": "TestClass1", "to_name": "TestClass2"}' in response.data + assert not b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data def test_add_one_relationship_but_its_for_a_class_that_doesnt_exist(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass69'], action="add"), follow_redirects=True) + response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass69'], rel_type='agg', action="add"), follow_redirects=True) assert b"ERROR: Unable to add relationship from" in response.data response = test_client.post('/getRelationships/', follow_redirects=True) @@ -299,7 +299,7 @@ def test_delete_a_relationship_that_didnt_exist_in_the_first_place(test_client, def test_manip_relationship_invalid_args (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) - response = test_client.post('/manipRelationship/', data=dict(class_name=None, relationship=[], action="add"), follow_redirects=True) + response = test_client.post('/manipRelationship/', data=dict(class_name=None, relationship=[], rel_type='agg', action="add"), follow_redirects=True) assert b"Invalid arguments, try again." in response.data ################################ TEST COORDINATES ################################ From 198c7def5c6b8d42284d84ca3f141f8f1a4f97d0 Mon Sep 17 00:00:00 2001 From: Deven Date: Tue, 24 Mar 2020 15:52:56 -0400 Subject: [PATCH 25/59] update model to support relationship types. --- app_package/core_func.py | 8 ++++---- app_package/memento/func_objs.py | 22 +++++++++++++--------- app_package/models.py | 4 +++- app_package/routes.py | 13 +++++++------ tests/unittests/test_routes.py | 20 ++++++++++---------- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/app_package/core_func.py b/app_package/core_func.py index 28e5778..9a9b155 100644 --- a/app_package/core_func.py +++ b/app_package/core_func.py @@ -195,7 +195,7 @@ def core_update_attr(pName, attr, newAttr): db.session.rollback() return 1 -def core_add_rel(from_name, to_name): +def core_add_rel(from_name, to_name, rel_type): """Adds a relationship to class with given name in the database Returns 0 on success, 1 on failure @@ -204,7 +204,7 @@ def core_add_rel(from_name, to_name): try: if (Class.query.get(from_name) is None or Class.query.get(to_name) is None): return 1 - new_rel = Relationship(from_name=from_name, to_name=to_name) + new_rel = Relationship(from_name=from_name, to_name=to_name, rel_type=rel_type) db.session.add(new_rel) db.session.commit() return 0 @@ -212,14 +212,14 @@ def core_add_rel(from_name, to_name): db.session.rollback() return 1 -def core_del_rel(from_name, to_name): +def core_del_rel(from_name, to_name, rel_type): """Deletes a relationship from class with given target in the database Returns 0 on success, 1 on failure """ try: - rel_to_delete = Relationship.query.get({"from_name": from_name, "to_name": to_name}) + rel_to_delete = Relationship.query.get({"from_name": from_name, "to_name": to_name, "rel_type": rel_type}) if (rel_to_delete is None): return 1 diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index dc62e17..248066c 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -173,38 +173,42 @@ class add_rel(Command): """Command class for core_add_rel. Accepts a class name and the name of the child""" parentName = '' childName = '' + relType = '' - def __init__(self, parentName, childName): + def __init__(self, parentName, childName, relType): self.parentName = parentName self.childName = childName - + self.relType = relType + def execute(self): - return core_add_rel(self.parentName, self.childName) + return core_add_rel(self.parentName, self.childName, self.relType) def undo(self): - return core_del_rel(self.parentName, self.childName) + return core_del_rel(self.parentName, self.childName, self.relType) def redo(self): - return core_add_rel(self.parentName, self.childName) + return core_add_rel(self.parentName, self.childName, self.relType) class del_rel(Command): """Command class for core_del_rel. Accepts a class name and the name of the child""" parentName = '' childName = '' + relType = '' - def __init__(self, parentName, childName): + def __init__(self, parentName, childName, relType): self.parentName = parentName self.childName = childName + self.relType = relType def execute(self): - return core_del_rel(self.parentName, self.childName) + return core_del_rel(self.parentName, self.childName, self.relType) def undo(self): - return core_add_rel(self.parentName, self.childName) + return core_add_rel(self.parentName, self.childName, self.relType) def redo(self): - return core_del_rel(self.parentName, self.childName) + return core_del_rel(self.parentName, self.childName, self.relType) class move(Command): diff --git a/app_package/models.py b/app_package/models.py index ede1d29..bba2cf0 100644 --- a/app_package/models.py +++ b/app_package/models.py @@ -40,7 +40,9 @@ class Relationship(db.Model): __relationship__ = 'relationship' from_name = db.Column(db.String(200), db.ForeignKey('class.name'), primary_key=True) to_name = db.Column(db.String(200), db.ForeignKey('class.name'), primary_key=True) + rel_type = db.Column(db.String(40)) # agg,comp,gen,none parent_class = relationship("Class", back_populates="class_relationships", foreign_keys=[from_name, to_name], primaryjoin='Class.name==Relationship.from_name') + class ClassSchema(ma.ModelSchema): """Meta model used by flask-marshmallow in jsonification.""" @@ -51,7 +53,7 @@ class Meta: class RelationshipSchema(ma.ModelSchema): member_of = ma.Nested(ClassSchema) class Meta: - fields = ("from_name", "to_name") + fields = ("from_name", "to_name", "rel_type") model = Relationship class AttributeSchema(ma.ModelSchema): diff --git a/app_package/routes.py b/app_package/routes.py index 288676c..d0a09f0 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -185,28 +185,29 @@ def manipRelationship(): fro = request.form['class_name'] to = request.form.getlist('relationship') action = request.form['action'] + rel_type = request.form['rel_type'] if (action == 'delete'): - delRelationship(fro, to) + delRelationship(fro, to, rel_type) elif (action == 'add'): - addRelationship(fro, to) + addRelationship(fro, to, rel_type) except: flash("Invalid arguments, try again.", 'error') return redirect('/') -def addRelationship(fro, to): +def addRelationship(fro, to, rel_type): """Helper function to add relationships to class.""" for child in to: - addRelCmd = add_rel(fro, child) + addRelCmd = add_rel(fro, child, rel_type) if cmd_stack.execute(addRelCmd): flash("ERROR: Unable to add relationship from " + fro + " to " + child, 'error') -def delRelationship(fro, to): +def delRelationship(fro, to, rel_type): """Helper function to remove relationships from class.""" for child in to: - delRelCmd = del_rel(fro, child) + delRelCmd = del_rel(fro, child, rel_type) if cmd_stack.execute(delRelCmd): flash("ERROR: Unable to delete relationship from " + fro + " to " + child, 'error') diff --git a/tests/unittests/test_routes.py b/tests/unittests/test_routes.py index 1df29f4..1a54ca7 100644 --- a/tests/unittests/test_routes.py +++ b/tests/unittests/test_routes.py @@ -251,7 +251,7 @@ def test_add_one_relationship(test_client, init_database): test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass2"}' in response.data def test_add_many_relationships(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) @@ -259,29 +259,29 @@ def test_add_many_relationships(test_client, init_database): test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass1', 'TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data - assert b'{"from_name": "TestClass2", "to_name": "TestClass1", "rel_type": "agg"}' in response.data - assert b'{"from_name": "TestClass1", "to_name": "TestClass1", "rel_type": "agg"}' in response.data - assert b'{"from_name": "TestClass2", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass2"}' in response.data + assert b'{"from_name": "TestClass2", "rel_type": "agg", "to_name": "TestClass1"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass1"}' in response.data + assert b'{"from_name": "TestClass2", "rel_type": "agg", "to_name": "TestClass2"}' in response.data def test_add_two_relationships(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2','TestClass1'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data - assert b'{"from_name": "TestClass1", "to_name": "TestClass1", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass2"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass1"}' in response.data def test_add_one_relationship_but_then_delete_that_relationship(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], rel_type='agg', action="add"), follow_redirects=True) response = test_client.post('/getRelationships/', follow_redirects=True) assert response.status_code == 200 - assert b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass2"}' in response.data response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass1', relationship=['TestClass2'], action="delete"), follow_redirects=True) assert response.status_code == 200 - assert not b'{"from_name": "TestClass1", "to_name": "TestClass2", "rel_type": "agg"}' in response.data + assert not b'{"from_name": "TestClass1", "rel_type": "agg", "to_name": "TestClass2"}' in response.data def test_add_one_relationship_but_its_for_a_class_that_doesnt_exist(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) @@ -294,7 +294,7 @@ def test_add_one_relationship_but_its_for_a_class_that_doesnt_exist(test_client, def test_delete_a_relationship_that_didnt_exist_in_the_first_place(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1 TestClass2'), follow_redirects=True) - response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass69'], action="delete"), follow_redirects=True) + response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass69'], rel_type="agg", action="delete"), follow_redirects=True) assert b"ERROR: Unable to delete relationship from" in response.data def test_manip_relationship_invalid_args (test_client, init_database): From d34f7cc52e58e8e3a99d6a331d18d1055fb874b7 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Tue, 24 Mar 2020 16:16:03 -0400 Subject: [PATCH 26/59] added testing functionality for run.py --- tests/conftest.py | 4 ++++ tests/unittests/test_run.py | 38 +++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e309ea2..4949a1d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,10 @@ NOTE: use 'pytest' to run unit tests while in virtual env In event of module errors, you may need to add '.' to PYTHONPATH """ +import sys, os +myPath = os.path.dirname(os.path.abspath(file)) +sys.path.insert(0, myPath + '/../') + from app_package import app, db import pytest from app_package.models import Class, Relationship, Attribute diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 7437c4d..af2e958 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -11,26 +11,44 @@ ################################### CLASS ################################### ################################## TEST ADD ################################# -def test_do_add_empty(): +def test_do_add(capsys): + ##### TEST EMPTY ##### app.do_add("TestAddEmpty") - assert app.do_list() == "TestAddEmpty" + captured = capsys.readouterr() + assert captured.out == "Successfully added class 'TestAddEmpty'\n" + app.do_list("") + captured = capsys.readouterr() + assert captured.out == "TestAddEmpty\n" -def test_do_add_dup(): +def test_do_add_duplicate(capsys): app.do_add("TestAddEmpty") - assert app.do_list() == "TestAddEmpty" + captured = capsys.readouterr() + assert captured.out == "Successfully added class 'TestAddEmpty'\n" + app.do_add("TestAddEmpty") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to add class 'TestAddEmpty'\n" + app.do_list("") + captured = capsys.readouterr() + assert captured.out == "TestAddEmpty\n" -def test_do_add_more(): +def test_do_add_more(capsys): app.do_add("TestAddMore") + captured = capsys.readouterr() + assert captured.out == "Successfully added class 'TestAddMore'\n" app.do_add("TestAdd1More") - assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More" + captured = capsys.readouterr() + assert captured.out == "Successfully added class 'TestAdd1More'\n" + app.do_list("") + captured = capsys.readouterr() + assert captured.out == "TestAddMore, TestAdd1More\n" -def test_do_add_none(): +def test_do_add_none(capsys): app.do_add("") - assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More" + -def test_do_add_multi(): +def test_do_add_multi(capsys): app.do_add("Multi1, Multi2, Multi3") - assert app.do_list() == "TestAddEmpty, TestAddMore, TestAdd1More, Multi1, Multi2, Multi3" + assert app.do_list("") == "TestAddEmpty, TestAddMore, TestAdd1More, Multi1, Multi2, Multi3" ################################ TEST DELETE ################################ From 2f3800584a05318954c31562a5e86b157ea394ca Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Tue, 24 Mar 2020 16:28:39 -0400 Subject: [PATCH 27/59] added more tests for add + fixed conftest setup --- tests/conftest.py | 2 +- tests/unittests/test_run.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4949a1d..ebe002c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ In event of module errors, you may need to add '.' to PYTHONPATH """ import sys, os -myPath = os.path.dirname(os.path.abspath(file)) +myPath = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, myPath + '/../') from app_package import app, db diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index af2e958..ff8f3c1 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -18,7 +18,7 @@ def test_do_add(capsys): assert captured.out == "Successfully added class 'TestAddEmpty'\n" app.do_list("") captured = capsys.readouterr() - assert captured.out == "TestAddEmpty\n" + assert captured.out == "TestAddEmpty\n\n" def test_do_add_duplicate(capsys): app.do_add("TestAddEmpty") @@ -29,7 +29,7 @@ def test_do_add_duplicate(capsys): assert captured.out == "ERROR: Unable to add class 'TestAddEmpty'\n" app.do_list("") captured = capsys.readouterr() - assert captured.out == "TestAddEmpty\n" + assert captured.out == "TestAddEmpty\n\n" def test_do_add_more(capsys): app.do_add("TestAddMore") @@ -40,15 +40,21 @@ def test_do_add_more(capsys): assert captured.out == "Successfully added class 'TestAdd1More'\n" app.do_list("") captured = capsys.readouterr() - assert captured.out == "TestAddMore, TestAdd1More\n" + assert captured.out == "TestAddMore\nTestAdd1More\n\n" def test_do_add_none(capsys): app.do_add("") + captured = capsys.readouterr() + assert captured.out == "Usage: add , , ... , \n" def test_do_add_multi(capsys): app.do_add("Multi1, Multi2, Multi3") - assert app.do_list("") == "TestAddEmpty, TestAddMore, TestAdd1More, Multi1, Multi2, Multi3" + captured = capsys.readouterr() + assert captured.out == "Successfully added class 'Multi1'\nSuccessfully added class 'Multi2'\nSuccessfully added class 'Multi3'\n" + app.do_list("") + captured = capsys.readouterr() + assert captured.out == "Multi1\nMulti2\nMulti3\n\n" ################################ TEST DELETE ################################ From 3ac056d1c3d1d8c31c1630c37552fcb450e47c7b Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Tue, 24 Mar 2020 21:21:33 -0400 Subject: [PATCH 28/59] added more tests --- tests/unittests/test_run.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index ff8f3c1..849dbda 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -11,8 +11,7 @@ ################################### CLASS ################################### ################################## TEST ADD ################################# -def test_do_add(capsys): - ##### TEST EMPTY ##### +def test_do_add(capsys): app.do_add("TestAddEmpty") captured = capsys.readouterr() assert captured.out == "Successfully added class 'TestAddEmpty'\n" @@ -47,16 +46,32 @@ def test_do_add_none(capsys): captured = capsys.readouterr() assert captured.out == "Usage: add , , ... , \n" + #TODO + # test the list with no classes added (shouldn't output a giant error msg like it does now) def test_do_add_multi(capsys): app.do_add("Multi1, Multi2, Multi3") captured = capsys.readouterr() assert captured.out == "Successfully added class 'Multi1'\nSuccessfully added class 'Multi2'\nSuccessfully added class 'Multi3'\n" + app.do_list("") captured = capsys.readouterr() assert captured.out == "Multi1\nMulti2\nMulti3\n\n" ################################ TEST DELETE ################################ +def test_do_delete(capsys): + #Need to capture the add text first to isolate only the delete output + app.do_add("TestDelete") + captured = capsys.readouterr() + + app.do_delete("TestDelete") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'TestDelete'\n" + + #TODO + #test do_list on the empty editor (rn it crashes the program) + +def test_do_delete(capsys): ################################# TEST EDIT ################################# From 9f3a4c46671814b31c0d1069485f1ef858921fda Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Tue, 24 Mar 2020 22:27:32 -0400 Subject: [PATCH 29/59] finished delete tests, added an easy way to assert lists --- tests/unittests/test_run.py | 93 +++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 15 deletions(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 849dbda..271603c 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -3,9 +3,15 @@ import pytest import run -################################## CMD SETUP ################################ +#################################### SETUP ################################## app = run.replShell() +# lazy mans way of capturing list +def captureList(capsys): + app.do_list("") + captured = capsys.readouterr() + return captured + ################################ TEST COMMANDS ############################## # Test list and maybe save/load @@ -15,30 +21,32 @@ def test_do_add(capsys): app.do_add("TestAddEmpty") captured = capsys.readouterr() assert captured.out == "Successfully added class 'TestAddEmpty'\n" - app.do_list("") - captured = capsys.readouterr() + + captured = captureList(capsys) assert captured.out == "TestAddEmpty\n\n" def test_do_add_duplicate(capsys): app.do_add("TestAddEmpty") captured = capsys.readouterr() assert captured.out == "Successfully added class 'TestAddEmpty'\n" + app.do_add("TestAddEmpty") captured = capsys.readouterr() assert captured.out == "ERROR: Unable to add class 'TestAddEmpty'\n" - app.do_list("") - captured = capsys.readouterr() + + captured = captureList(capsys) assert captured.out == "TestAddEmpty\n\n" def test_do_add_more(capsys): app.do_add("TestAddMore") captured = capsys.readouterr() assert captured.out == "Successfully added class 'TestAddMore'\n" + app.do_add("TestAdd1More") captured = capsys.readouterr() assert captured.out == "Successfully added class 'TestAdd1More'\n" - app.do_list("") - captured = capsys.readouterr() + + captured = captureList(capsys) assert captured.out == "TestAddMore\nTestAdd1More\n\n" def test_do_add_none(capsys): @@ -46,16 +54,14 @@ def test_do_add_none(capsys): captured = capsys.readouterr() assert captured.out == "Usage: add , , ... , \n" - #TODO - # test the list with no classes added (shouldn't output a giant error msg like it does now) + def test_do_add_multi(capsys): app.do_add("Multi1, Multi2, Multi3") captured = capsys.readouterr() assert captured.out == "Successfully added class 'Multi1'\nSuccessfully added class 'Multi2'\nSuccessfully added class 'Multi3'\n" - app.do_list("") - captured = capsys.readouterr() + captured = captureList(capsys) assert captured.out == "Multi1\nMulti2\nMulti3\n\n" ################################ TEST DELETE ################################ @@ -68,11 +74,68 @@ def test_do_delete(capsys): captured = capsys.readouterr() assert captured.out == "Successfully deleted class 'TestDelete'\n" - #TODO - #test do_list on the empty editor (rn it crashes the program) + captured = captureList(capsys) + assert captured.out == "No Classes\n\n" -def test_do_delete(capsys): +def test_do_delete_none(capsys): + app.do_delete("") + captured = capsys.readouterr() + assert captured.out == "Usage: delete , , ... , \n" + +def test_do_delete_nonexistant(capsys): + app.do_delete("test") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to delete class 'test'\n" + +def test_do_delete_some(capsys): + #Need to capture adding messages before delete message + app.do_add("test1, test2, test3") + captured = capsys.readouterr() + + app.do_delete("test2") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'test2'\n" + + captured = captureList(capsys) + assert captured.out == "test1\ntest3\n\n" + +# TESTS DELETING ONE AT A TIME !!! NOT USING MULTI DELETE !!! +def test_do_delete_all_1(capsys): + app.do_add("test1, test2, test3") + captured = capsys.readouterr() + + app.do_delete("test1") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'test1'\n" + + captured = captureList(capsys) + assert captured.out == "test2\ntest3\n\n" + + app.do_delete("test2") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'test2'\n" + + captured = captureList(capsys) + assert captured.out == "test3\n\n" + + app.do_delete("test3") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'test3'\n" + + captured = captureList(capsys) + assert captured.out == "No Classes\n\n" + +def test_do_delete_all_many(capsys): + # Once again, get rid of the add output + app.do_add("one, two, three, four") + captured = capsys.readouterr() + + app.do_delete("one, two, three, four") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted class 'one'\nSuccessfully deleted class 'two'\nSuccessfully deleted class 'three'\nSuccessfully deleted class 'four'\n" + captured = captureList(capsys) + assert captured.out == "No Classes\n\n" ################################# TEST EDIT ################################# ################################# ATTRIBUTES ################################ @@ -85,4 +148,4 @@ def test_do_delete(capsys): ############################### RELATIONSHIPS ############################### ################################ TEST addRel ################################ -################################ TEST delRel ################################ \ No newline at end of file +################################ TEST delRel ################################ From aefbeed2aaef39acd5f5e3e83a23ed3aed87b939 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Thu, 26 Mar 2020 13:06:37 -0400 Subject: [PATCH 30/59] added edit tests, cleaned up extraneous code --- tests/unittests/test_run.py | 46 ++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 271603c..806a145 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -1,6 +1,5 @@ """ Unit tests for run.py/Command Line Interface """ -import pytest import run #################################### SETUP ################################## @@ -16,7 +15,7 @@ def captureList(capsys): # Test list and maybe save/load ################################### CLASS ################################### -################################## TEST ADD ################################# +################################## TEST add ################################# def test_do_add(capsys): app.do_add("TestAddEmpty") captured = capsys.readouterr() @@ -54,8 +53,6 @@ def test_do_add_none(capsys): captured = capsys.readouterr() assert captured.out == "Usage: add , , ... , \n" - - def test_do_add_multi(capsys): app.do_add("Multi1, Multi2, Multi3") captured = capsys.readouterr() @@ -64,7 +61,7 @@ def test_do_add_multi(capsys): captured = captureList(capsys) assert captured.out == "Multi1\nMulti2\nMulti3\n\n" -################################ TEST DELETE ################################ +################################ TEST delete ################################ def test_do_delete(capsys): #Need to capture the add text first to isolate only the delete output app.do_add("TestDelete") @@ -125,6 +122,7 @@ def test_do_delete_all_1(capsys): captured = captureList(capsys) assert captured.out == "No Classes\n\n" +# Tests deleting many using MULTI DELETE def test_do_delete_all_many(capsys): # Once again, get rid of the add output app.do_add("one, two, three, four") @@ -136,7 +134,41 @@ def test_do_delete_all_many(capsys): captured = captureList(capsys) assert captured.out == "No Classes\n\n" -################################# TEST EDIT ################################# + +################################# TEST edit ################################# +def test_do_edit(capsys): + #Capture add class + app.do_add("test") + captured = capsys.readouterr() + + app.do_edit("test, newtest") + captured = capsys.readouterr() + assert captured.out == "Successfully updated class 'test' to 'newtest'\n" + + captured = captureList(capsys) + assert captured.out == "newtest\n\n" + +def test_do_edit_none(capsys): + app.do_edit("test") + captured = capsys.readouterr() + assert captured.out == "Usage: edit , \n" + +def test_do_edit_deleted_class(capsys): + #Capture add class + app.do_add("test, ooga, booga") + captured = capsys.readouterr() + + #Capture delete + app.do_delete("test") + captured = capsys.readouterr() + + app.do_edit("test, thisshouldntexist") + app.do_edit("ooga, thisexists") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to update class 'test' to 'thisshouldntexist'\nSuccessfully updated class 'ooga' to 'thisexists'\n" + + captured = captureList(capsys) + assert captured.out == "thisexists\nbooga\n\n" ################################# ATTRIBUTES ################################ ################################ TEST addAttr ############################### @@ -149,3 +181,5 @@ def test_do_delete_all_many(capsys): ################################ TEST addRel ################################ ################################ TEST delRel ################################ + +################################# UNDO/REDO ################################# \ No newline at end of file From f63da692e00f33fe1bd3cc72346950a6981af01a Mon Sep 17 00:00:00 2001 From: Deven Date: Thu, 26 Mar 2020 19:26:20 -0400 Subject: [PATCH 31/59] add tests for the upcoming changes to the attribute model --- tests/unittests/test_routes.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unittests/test_routes.py b/tests/unittests/test_routes.py index 1a54ca7..32cba04 100644 --- a/tests/unittests/test_routes.py +++ b/tests/unittests/test_routes.py @@ -180,15 +180,15 @@ def test_save_no_name(test_client, init_database): def test_add_one_attribute(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert response.status_code == 200 assert b"TestClass" in response.data assert b"TestAttr" in response.data def test_add_duplicate_attribute(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert response.status_code == 200 assert b"TestClass" in response.data assert b"TestAttr" in response.data @@ -196,7 +196,7 @@ def test_add_duplicate_attribute(test_client, init_database): def test_add_one_attribute_but_then_delete_that_attribute(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert b"TestAttr" in response.data response = test_client.post('/manipCharacteristics/', data={"field[TestAttr][attribute]":"TestAttr", "field[TestAttr][action]":"Delete", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) @@ -206,7 +206,7 @@ def test_add_one_attribute_but_then_delete_that_attribute(test_client, init_data def test_delete_attribute_no_existo (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert b"TestAttr" in response.data response = test_client.post('/manipCharacteristics/', data={"field[TestAttr][attribute]":"TestAttrAAA", "field[TestAttr][action]":"Delete", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) @@ -217,7 +217,7 @@ def test_delete_attribute_no_existo (test_client, init_database): def test_rename_attribute (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert b'TestAttr' in response.data response = test_client.post('/manipCharacteristics/', data={"field[TestAttr][attribute]":"TestAttr", "field[TestAttr][action]":None, "field[ super ][class_name]":"TestClass", "field[TestAttr][new_attribute]":"TestUpdate"}, follow_redirects=True) @@ -226,7 +226,7 @@ def test_rename_attribute (test_client, init_database): def test_rename_attribute_no_existo (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert b"TestAttr" in response.data response = test_client.post('/manipCharacteristics/', data={"field[TestAttr][attribute]":"TestAttrAAA", "field[TestAttr][action]":None, "field[ super ][class_name]":"TestClass", "field[TestAttr][new_attribute]":"TestUpdate"}, follow_redirects=True) @@ -237,7 +237,7 @@ def test_rename_attribute_no_existo (test_client, init_database): def test_attribute_invalid_args (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass'), follow_redirects=True) - response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) + response = test_client.post('/manipCharacteristics/', data={"field[ class ][attrs]":"TestAttr", "field[ class ][attr_type]":"field", "field[ class ][action]":"Add", "field[ super ][class_name]":"TestClass"}, follow_redirects=True) assert b'TestAttr' in response.data response = test_client.post('/manipCharacteristics/', data={"field[TestAttr][attribute]":"TestAttr", "field[TestAttr][action]":None, "field[ super ][class_name]":"TestClass", "field[TestAttr][new_attribute]":None}, follow_redirects=True) From 4546003a0a67539da94476c54d8ad68c79d4b95a Mon Sep 17 00:00:00 2001 From: Deven Date: Thu, 26 Mar 2020 20:38:16 -0400 Subject: [PATCH 32/59] Seperate attributes into fields and methods And fix issues found with the relationship model. And made run.py automatically create the db if it doesn't exist. And renamed createdb to cleardb. --- app_package/core_func.py | 45 +++++++++++++++++++++++++---- app_package/memento/func_objs.py | 20 ++++++------- app_package/models.py | 3 +- app_package/routes.py | 13 +++++---- app_package/templates/index.html | 6 ++++ createdb.py => cleardb.py | 0 run.py | 49 ++++++++++++++++++++++---------- 7 files changed, 99 insertions(+), 37 deletions(-) rename createdb.py => cleardb.py (100%) diff --git a/app_package/core_func.py b/app_package/core_func.py index 9a9b155..f791fd9 100644 --- a/app_package/core_func.py +++ b/app_package/core_func.py @@ -142,7 +142,7 @@ def core_load(data): db.session.rollback() return 1 -def core_add_attr(pName, attr): +def core_add_attr(pName, attr, attrType): """Adds an attribute to class with given name in the database Returns 0 on success, 1 on failure @@ -151,9 +151,19 @@ def core_add_attr(pName, attr): try: if Class.query.get(pName) is None: return 1 - new_attr = Attribute(attribute=attr, class_name=pName) + new_attr = Attribute(attribute=attr, class_name=pName, attr_type=attrType) db.session.add(new_attr) db.session.commit() + + parsedType = parseType(attr) + if parsedType is not None: + # link it to the related class if applicable + ClassList = Class.query.all() + for CurrentClass in ClassList: + if CurrentClass.name == parsedType: + core_add_rel(pName, CurrentClass.name, "agg") + break + return 0 except: db.session.rollback() @@ -190,6 +200,16 @@ def core_update_attr(pName, attr, newAttr): attr_to_update.attribute = newAttr db.session.commit() + + parsedType = parseType(newAttr) + if parsedType is not None: + # link it to the related class if applicable + ClassList = Class.query.all() + for CurrentClass in ClassList: + if CurrentClass.name == parsedType: + core_add_rel(pName, CurrentClass.name, "agg") + break + return 0 except: db.session.rollback() @@ -212,14 +232,14 @@ def core_add_rel(from_name, to_name, rel_type): db.session.rollback() return 1 -def core_del_rel(from_name, to_name, rel_type): +def core_del_rel(from_name, to_name): """Deletes a relationship from class with given target in the database Returns 0 on success, 1 on failure """ try: - rel_to_delete = Relationship.query.get({"from_name": from_name, "to_name": to_name, "rel_type": rel_type}) + rel_to_delete = Relationship.query.get({"from_name": from_name, "to_name": to_name}) if (rel_to_delete is None): return 1 @@ -259,4 +279,19 @@ def core_parse (string): def removeTrailingWhitespace(string): while (string[-1] == ' ' or string[-1] == '\t'): string = string[0:-1] - return string \ No newline at end of file + return string + +def parseType(input): + # remove the crap in parens + front = input.split('(', 1)[0] + tipe = "" + try: + # if it uses a colon + tipe = front.split(':',1)[1] + except: + # if it doesn't use a colon + tipe = front.split(' ')[0] + # if it doesn't have a type + if tipe == front: + tipe = None + return(tipe) \ No newline at end of file diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index 248066c..5bc95a1 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -114,19 +114,21 @@ class add_attr(Command): """Command class for core_add_attr. Accepts a class name and the name of an attribute""" className = '' attrName = '' + attrType = '' - def __init__(self, className, attrName): + def __init__(self, className, attrName, attrType): self.className = className self.attrName = attrName + self.attrType = attrType def execute(self): - return core_add_attr(self.className, self.attrName) + return core_add_attr(self.className, self.attrName, self.attrType) def undo(self): - return core_del_attr(self.className, self.attrName) + return core_del_attr(self.className, self.attrName, self.attrType) def redo(self): - return core_add_attr(self.className, self.attrName) + return core_add_attr(self.className, self.attrName, self.attrType) class del_attr(Command): @@ -184,7 +186,7 @@ def execute(self): return core_add_rel(self.parentName, self.childName, self.relType) def undo(self): - return core_del_rel(self.parentName, self.childName, self.relType) + return core_del_rel(self.parentName, self.childName) def redo(self): return core_add_rel(self.parentName, self.childName, self.relType) @@ -194,21 +196,19 @@ class del_rel(Command): """Command class for core_del_rel. Accepts a class name and the name of the child""" parentName = '' childName = '' - relType = '' - def __init__(self, parentName, childName, relType): + def __init__(self, parentName, childName): self.parentName = parentName self.childName = childName - self.relType = relType def execute(self): - return core_del_rel(self.parentName, self.childName, self.relType) + return core_del_rel(self.parentName, self.childName) def undo(self): return core_add_rel(self.parentName, self.childName, self.relType) def redo(self): - return core_del_rel(self.parentName, self.childName, self.relType) + return core_del_rel(self.parentName, self.childName) class move(Command): diff --git a/app_package/models.py b/app_package/models.py index bba2cf0..e52bc68 100644 --- a/app_package/models.py +++ b/app_package/models.py @@ -27,6 +27,7 @@ class Attribute(db.Model): __tablename__ = 'attribute' attribute = db.Column(db.String(200), primary_key=True) + attr_type = db.Column(db.String(10)) #field, method date_created = db.Column(db.DateTime, default=datetime.utcnow) class_name = db.Column(db.String(200), db.ForeignKey('class.name'), primary_key=True) parent_class = relationship("Class", back_populates="class_attributes", foreign_keys=[class_name], primaryjoin='Class.name==Attribute.class_name') @@ -40,7 +41,7 @@ class Relationship(db.Model): __relationship__ = 'relationship' from_name = db.Column(db.String(200), db.ForeignKey('class.name'), primary_key=True) to_name = db.Column(db.String(200), db.ForeignKey('class.name'), primary_key=True) - rel_type = db.Column(db.String(40)) # agg,comp,gen,none + rel_type = db.Column(db.String(10)) # agg,comp,gen,none parent_class = relationship("Class", back_populates="class_relationships", foreign_keys=[from_name, to_name], primaryjoin='Class.name==Relationship.from_name') diff --git a/app_package/routes.py b/app_package/routes.py index d0a09f0..ac4ff9e 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -134,7 +134,8 @@ def manipCharacteristics(): action = theDict[el]['action'] if action == "Add": - addAttributes(class_name, theDict[el]['attrs']) + if 'attr_type' in theDict[el]: + addAttributes(class_name, theDict[el]['attrs'], theDict[el]['attr_type']) elif action == "Delete": delAttribute(class_name, theDict[el]['attribute']) elif action == "RenameClass": @@ -166,11 +167,11 @@ def updateAttribute(name, oldAttr, newAttr): if cmd_stack.execute(editAttrCmd): flash("ERROR: Unable to update attribute " + oldAttr + " in " + name + " to " + newAttr, 'error') -def addAttributes(name, attrString): +def addAttributes(name, attrString, attrType): """Helper to add attributes to class.""" attrList = core_parse(attrString) for attr in attrList: - addAttrCmd = add_attr(name, attr) + addAttrCmd = add_attr(name, attr, attrType) if cmd_stack.execute(addAttrCmd): flash('ERROR: Unable to add attribute ' + attr + " to " + name, 'error') @@ -187,7 +188,7 @@ def manipRelationship(): action = request.form['action'] rel_type = request.form['rel_type'] if (action == 'delete'): - delRelationship(fro, to, rel_type) + delRelationship(fro, to) elif (action == 'add'): addRelationship(fro, to, rel_type) except: @@ -204,10 +205,10 @@ def addRelationship(fro, to, rel_type): flash("ERROR: Unable to add relationship from " + fro + " to " + child, 'error') -def delRelationship(fro, to, rel_type): +def delRelationship(fro, to): """Helper function to remove relationships from class.""" for child in to: - delRelCmd = del_rel(fro, child, rel_type) + delRelCmd = del_rel(fro, child) if cmd_stack.execute(delRelCmd): flash("ERROR: Unable to delete relationship from " + fro + " to " + child, 'error') diff --git a/app_package/templates/index.html b/app_package/templates/index.html index b1c45bd..f8cbfa9 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -60,6 +60,12 @@

Add Attributes

+
  • + + + + +
  • diff --git a/createdb.py b/cleardb.py similarity index 100% rename from createdb.py rename to cleardb.py diff --git a/run.py b/run.py index bc8a5b1..22face2 100644 --- a/run.py +++ b/run.py @@ -2,7 +2,7 @@ from app_package.core_func import (core_save, core_load, core_parse) from app_package.memento.func_objs import add_class, delete_class, edit_class, add_attr, del_attr, edit_attr, add_rel, del_rel from app_package.models import Class, Attribute, Relationship -from app_package import app, cmd_stack +from app_package import app, cmd_stack, db import webbrowser import json @@ -115,19 +115,24 @@ def do_editAttr(self, args): def do_addRel(self, args): """Accepts a single parent class name followed by a list of child class names separated by commas and adds relationships from parents to children in database. - Usage: addRel , , , ... , + Usage: addRel , , , , ... , + Valid relationship types: agg, comp, gen, none """ argList = core_parse(args) - if len(argList) > 1: + if len(argList) > 2: class_name = argList.pop(0) + rel_type = argList.pop(0).lower() + if not rel_type in ["agg", "comp", "gen", "none"]: + print('ERROR: Invalid relationship type: ' + rel_type + "\n\tValid relationship types: agg, comp, gen, none") + return for rel in argList: - addRelCmd = add_rel(class_name, rel) + addRelCmd = add_rel(class_name, rel, rel_type) if cmd_stack.execute(addRelCmd): - print('ERROR: Unable to add relationship from \'' + class_name + '\' to \'' + rel + '\'') + print('ERROR: Unable to add relationship from \'' + class_name + '\' to \'' + rel + '\' of type \'' + rel_type + '\'') else: - print('Successfully added relationship from \'' + class_name + '\' to \'' + rel + '\'') + print('Successfully added relationship from \'' + class_name + '\' to \'' + rel + '\' of type \'' + rel_type + '\'') else: - print("Usage: addRel , , , ... , ") + print("Usage: addRel , , , , ... , \n\tValid relationship types: agg, comp, gen, none") def do_delRel(self, args): """Accepts a single parent class name followed by a list of child class names separated by commas and removes relationships from parents to children in database. @@ -186,21 +191,34 @@ def do_list(self, args): attributes = classObj.class_attributes if len(attributes) > 0: - listStr += ' > Attributes: ' + listStrFields = ' > Fields:' + showFields = False for attr in attributes: - if attr == attributes[-1]: - listStr += attr.attribute + '\n' - else: - listStr += (attr.attribute + ", ") + if attr.attr_type == "field": + showFields = True + listStrFields += (" " + attr.attribute + ",") + listStrFields = listStrFields[0:-1] + '\n' + if showFields: + listStr += listStrFields + + listStrMethods = ' > Methods:' + showMethods = False + for attr in attributes: + if attr.attr_type == "method": + showMethods = True + listStrMethods += (" " + attr.attribute + ",") + listStrMethods = listStrMethods[0:-1] + '\n' + if showMethods: + listStr += listStrMethods relationships = classObj.class_relationships if len(relationships) > 0: - listStr += ' > Children: ' + listStr += ' > Relationships: ' for rel in relationships: if rel == relationships[-1]: - listStr += rel.to_name + '\n' + listStr += rel.to_name + ' (' + rel.rel_type + ')\n' else: - listStr += (rel.to_name + ", ") + listStr += (rel.to_name + ' (' + rel.rel_type + '), ') print(listStr) @@ -268,4 +286,5 @@ def close(self): if __name__ == '__main__': + db.create_all() replShell().cmdloop() From c7bb1d3cb3ecd5e00c6ea55137db6e402a348590 Mon Sep 17 00:00:00 2001 From: Deven Date: Fri, 27 Mar 2020 21:02:21 -0400 Subject: [PATCH 33/59] add tab completion and make installation suck less --- README.txt | 4 +- install.py | 44 +++++++++++ requirements.txt | 13 ---- run.py | 185 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 224 insertions(+), 22 deletions(-) create mode 100644 install.py delete mode 100644 requirements.txt diff --git a/README.txt b/README.txt index 20ac78c..93718a3 100644 --- a/README.txt +++ b/README.txt @@ -18,7 +18,7 @@ Install all dependencies: - Navigate to the main project folder ('220 cowboy') - Activate environment. - Type 'source env/bin/activate' - - Type 'pip install -r requirements.txt' to install libraries + - Type 'python3 install.py' to install libraries * Both operations below assume you are inside of the activated enviroment * @@ -37,7 +37,7 @@ Install all dependencies: - Navigate to the main project folder ('220 cowboy') - Activate environment. - Type 'env\Scripts\activate' - - Type 'pip install -r requirements.txt' to install libraries + - Type 'python install.py' to install libraries * Both operations below assume you are inside of the activated enviroment * diff --git a/install.py b/install.py new file mode 100644 index 0000000..bbe7ea4 --- /dev/null +++ b/install.py @@ -0,0 +1,44 @@ +import subprocess, sys + +requirements = [ + "Click==7.0", + "Flask==1.1.1", + "flask-marshmallow==0.10.1", + "Flask-SQLAlchemy==2.4.1", + "itsdangerous==1.1.0", + "Jinja2==2.11.1", + "MarkupSafe==1.1.1", + "marshmallow==3.4.0", + "marshmallow-sqlalchemy==0.21.0", + "six==1.14.0", + "SQLAlchemy==1.3.13", + "Werkzeug==0.16.1", + "parse==1.15.0" +] + +windows = [ + "pyreadline" +] + +linux = [ + "readline" +] + +mac = [ + "gnureadline" +] + +def install(list): + for thing in list: + subprocess.check_call([sys.executable, "-m", "pip", "install", thing]) + +if __name__ == "__main__": + from sys import platform + install(requirements) + print("Preparing to install OS specific requirements...") + if platform.startswith("win"): + install(windows) + if platform.startswith("darwin"): + install(mac) + if platform.startswith("linux"): + install(linux) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4f8fdb2..0000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -Click==7.0 -Flask==1.1.1 -flask-marshmallow==0.10.1 -Flask-SQLAlchemy==2.4.1 -itsdangerous==1.1.0 -Jinja2==2.11.1 -MarkupSafe==1.1.1 -marshmallow==3.4.0 -marshmallow-sqlalchemy==0.21.0 -six==1.14.0 -SQLAlchemy==1.3.13 -Werkzeug==0.16.1 -parse==1.15.0 \ No newline at end of file diff --git a/run.py b/run.py index 22face2..25ac912 100644 --- a/run.py +++ b/run.py @@ -5,7 +5,7 @@ from app_package import app, cmd_stack, db import webbrowser import json - +import readline class replShell(cmd.Cmd): intro = 'Welcome to the UML editor shell.\nType help or ? to list commands.\nType web to open web app.\n' @@ -43,6 +43,24 @@ def do_delete(self, args): print('Successfully deleted class \'' + name + '\'') else: print("Usage: delete , , ... , ") + + def complete_delete(self, text, line, begidx, endidx): + btext = core_parse(line[7:])[-1] + allClasses = Class.query.all() + ClassNames = [] + for Klass in allClasses: + if Klass.name.startswith(btext): + tokens = btext.split(" ") + classTokens = Klass.name.split(" ") + + while tokens[0] == classTokens[0]: + tokens.pop(0) + classTokens.pop(0) + + + ClassNames.append(" ".join(classTokens)) + + return ClassNames def do_edit(self, args): """Accepts a single class name followed by a replacement name, separated by commas, and changes instances of old name in database with new name. @@ -60,27 +78,65 @@ def do_edit(self, args): else: print("Usage: edit , ") + def complete_edit(self, text, line, begidx, endidx): + allClasses = Class.query.all() + ClassNames = [] + for Klass in allClasses: + if Klass.name.startswith(text): + ClassNames.append(Klass.name) + return ClassNames + ######################################## Attribute level ######################################### def do_addAttr(self, args): """Accepts a single class name followed by a list of attribute names separated by commas and adds them to the class. - Usage: addAttr , , , ... , + Usage: addAttr , , , , ... , """ argList = core_parse(args) - if len(argList) > 1: + if len(argList) > 2: class_name = argList.pop(0) + attr_type = argList.pop(0).lower() + if not attr_type in ["field", "method"]: + print('ERROR: Invalid attribute type: ' + attr_type + "\n\tValid attribute types: field, method") + return for attr in argList: - addAttrCmd = add_attr(class_name, attr) + addAttrCmd = add_attr(class_name, attr, attr_type) if cmd_stack.execute(addAttrCmd): - print('ERROR: Unable to add attribute \'' + attr + '\'') + print('ERROR: Unable to add ' + attr_type + ' \'' + attr + '\'') else: - print('Successfully added attribute \'' + attr + '\'') + print('Successfully added ' + attr_type + ' \'' + attr + '\'') else: print("Usage: addAttr , , , ... , ") + + def complete_addAttr(self, text, line, begidx, endidx): + btext = core_parse(line[7:])[-1] + allClasses = Class.query.all() + allClassNames = [] + + for Klass in allClasses: + allClassNames.append(Klass.name) + + allClassNames.append("field") + allClassNames.append("method") + + ClassNames = [] + for Klass in allClassNames: + if Klass.startswith(btext): + tokens = btext.split(" ") + classTokens = Klass.split(" ") + + while tokens[0] == classTokens[0]: + tokens.pop(0) + classTokens.pop(0) + + + ClassNames.append(" ".join(classTokens)) + + return ClassNames def do_delAttr(self, args): """Accepts a single class name followed by a list of attribute names separated by commas and removes them from the class. - Usage: delAttr , , , ... , + Usage: delAttr , , , , ... , """ argList = core_parse(args) if len(argList) > 1: @@ -93,6 +149,35 @@ def do_delAttr(self, args): print('Successfully deleted attribute \'' + attr + '\'') else: print("Usage: delAttr , , , ... , ") + + def complete_delAttr(self, text, line, begidx, endidx): + bline = core_parse(line[8:]) + btext = bline[-1] + possibleMatches = [] + + if len(bline) < 2: + allClasses = Class.query.all() + for Klass in allClasses: + possibleMatches.append(Klass.name) + else: + allAttrs = Attribute.query.filter(bline[0] == Attribute.class_name).all() + for Attr in allAttrs: + possibleMatches.append(Attr.attribute) + + Matches = [] + for PM in possibleMatches: + if PM.startswith(btext): + tokens = btext.split(" ") + PMTokens = PM.split(" ") + + while tokens[0] == PMTokens[0]: + tokens.pop(0) + PMTokens.pop(0) + + + Matches.append(" ".join(PMTokens)) + + return Matches def do_editAttr(self, args): """Accepts a single class name followed by an existing attribute within and a new name which will replace said attribute in the class, all separated by commas. @@ -110,6 +195,35 @@ def do_editAttr(self, args): print('Successfully updated attribute \'' + old_name + '\' to \'' + new_name + '\'') else: print("Usage: editAttr , , ") + + def complete_editAttr(self, text, line, begidx, endidx): + bline = core_parse(line[8:]) + btext = bline[-1] + possibleMatches = [] + + if len(bline) < 2: + allClasses = Class.query.all() + for Klass in allClasses: + possibleMatches.append(Klass.name) + elif len(bline) == 2: + allAttrs = Attribute.query.filter(bline[0] == Attribute.class_name).all() + for Attr in allAttrs: + possibleMatches.append(Attr.attribute) + + Matches = [] + for PM in possibleMatches: + if PM.startswith(btext): + tokens = btext.split(" ") + PMTokens = PM.split(" ") + + while tokens[0] == PMTokens[0]: + tokens.pop(0) + PMTokens.pop(0) + + + Matches.append(" ".join(PMTokens)) + + return Matches ########################################## Relationship level ######################################## @@ -134,6 +248,34 @@ def do_addRel(self, args): else: print("Usage: addRel , , , , ... , \n\tValid relationship types: agg, comp, gen, none") + def complete_addRel(self, text, line, begidx, endidx): + btext = core_parse(line[7:])[-1] + allClasses = Class.query.all() + allClassNames = [] + + for Klass in allClasses: + allClassNames.append(Klass.name) + + allClassNames.append("agg") + allClassNames.append("comp") + allClassNames.append("gen") + allClassNames.append("none") + + ClassNames = [] + for Klass in allClassNames: + if Klass.startswith(btext): + tokens = btext.split(" ") + classTokens = Klass.split(" ") + + while tokens[0] == classTokens[0]: + tokens.pop(0) + classTokens.pop(0) + + + ClassNames.append(" ".join(classTokens)) + + return ClassNames + def do_delRel(self, args): """Accepts a single parent class name followed by a list of child class names separated by commas and removes relationships from parents to children in database. Usage: delRel , , , ... , @@ -149,6 +291,35 @@ def do_delRel(self, args): print('Successfully deleted relationship from \'' + class_name + '\' to \'' + rel + '\'') else: print("Usage: delRel , , , ... , ") + + def complete_delRel(self, text, line, begidx, endidx): + bline = core_parse(line[7:]) + btext = bline[-1] + possibleMatches = [] + + if len(bline) < 2: + allClasses = Class.query.all() + for Klass in allClasses: + possibleMatches.append(Klass.name) + else: + allRels = Relationship.query.filter(bline[0] == Relationship.from_name).all() + for Rel in allRels: + possibleMatches.append(Rel.to_name) + + Matches = [] + for PM in possibleMatches: + if PM.startswith(btext): + tokens = btext.split(" ") + PMTokens = PM.split(" ") + + while tokens[0] == PMTokens[0]: + tokens.pop(0) + PMTokens.pop(0) + + + Matches.append(" ".join(PMTokens)) + + return Matches #################################### Other ############################################ From 3b88a13003053f8766d870bbe4caed36506768ae Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Mon, 30 Mar 2020 15:08:27 -0400 Subject: [PATCH 34/59] added checklist and place for Fields and Methods --- tests/unittests/test_run.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 806a145..b695e41 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -1,4 +1,14 @@ -""" Unit tests for run.py/Command Line Interface """ +""" Unit tests for run.py/Command Line Interface + + Tests: + [X] add + [X] delete + [X] edit + [] relationships + [] undo/redo + [] fields + [] methods +""" import run @@ -170,12 +180,7 @@ def test_do_edit_deleted_class(capsys): captured = captureList(capsys) assert captured.out == "thisexists\nbooga\n\n" -################################# ATTRIBUTES ################################ -################################ TEST addAttr ############################### - -################################ TEST delAttr ############################### - -############################### TEST editAttr ############################### +############################### FIELDS/METHODS ############################## ############################### RELATIONSHIPS ############################### ################################ TEST addRel ################################ From 864ac6f5576982dc0d76c42412f9613346f6d8bf Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Mon, 30 Mar 2020 15:35:07 -0400 Subject: [PATCH 35/59] updated cont. integration to use install --- .github/workflows/pythonapp.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/pythonapp.yml diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml new file mode 100644 index 0000000..4b94f8d --- /dev/null +++ b/.github/workflows/pythonapp.yml @@ -0,0 +1,33 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python install.py + + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pip install pytest + pytest \ No newline at end of file From 820dca827a228a69658ac993b69ec7eb6d72e6a2 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Mon, 30 Mar 2020 15:38:23 -0400 Subject: [PATCH 36/59] python3 instead --- .github/workflows/pythonapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 4b94f8d..603f3d0 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -18,7 +18,7 @@ jobs: python-version: 3.8 - name: Install dependencies run: | - python install.py + python3 install.py - name: Lint with flake8 run: | From 9e240e46a26c7cda1280ab26f583cea248b501e9 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Mon, 30 Mar 2020 15:58:15 -0400 Subject: [PATCH 37/59] try libncurses --- .github/workflows/pythonapp.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 603f3d0..fb8577b 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -18,6 +18,7 @@ jobs: python-version: 3.8 - name: Install dependencies run: | + sudo apt-get install libncurses5-dev python3 install.py - name: Lint with flake8 @@ -30,4 +31,4 @@ jobs: - name: Test with pytest run: | pip install pytest - pytest \ No newline at end of file + pytest From e870e94d8234118f048ccede6348db24e7083945 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Mon, 30 Mar 2020 15:58:25 -0400 Subject: [PATCH 38/59] trying 16.04 --- .github/workflows/pythonapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 603f3d0..2161606 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -8,7 +8,7 @@ on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-16.04 steps: - uses: actions/checkout@v2 From ac060d7b1162d6f1e635b57e149a293107f12d88 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Mon, 30 Mar 2020 16:04:39 -0400 Subject: [PATCH 39/59] Update conftest.py --- tests/conftest.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index e309ea2..9e06bfd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,10 @@ NOTE: use 'pytest' to run unit tests while in virtual env In event of module errors, you may need to add '.' to PYTHONPATH """ +import sys, os +myPath = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, myPath + '/../') + from app_package import app, db import pytest from app_package.models import Class, Relationship, Attribute @@ -30,4 +34,4 @@ def init_database(): db.drop_all() - \ No newline at end of file + From ba3ee49bc5b399ccb51e98bdd846abe98764c226 Mon Sep 17 00:00:00 2001 From: Deven Date: Tue, 31 Mar 2020 14:16:37 -0400 Subject: [PATCH 40/59] Add radio buttons to mock relationship adding --- app_package/routes.py | 2 +- app_package/templates/index.html | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app_package/routes.py b/app_package/routes.py index ac4ff9e..3be516a 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -186,10 +186,10 @@ def manipRelationship(): fro = request.form['class_name'] to = request.form.getlist('relationship') action = request.form['action'] - rel_type = request.form['rel_type'] if (action == 'delete'): delRelationship(fro, to) elif (action == 'add'): + rel_type = request.form['rel_type'] addRelationship(fro, to, rel_type) except: flash("Invalid arguments, try again.", 'error') diff --git a/app_package/templates/index.html b/app_package/templates/index.html index f8cbfa9..4e1d03c 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -81,6 +81,15 @@

    Relationships

    {% endfor %} + + + + +
    + + + +
    From 1ff3848aaff27e61c6884231ac5d8ba8b3418327 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Tue, 31 Mar 2020 14:28:29 -0400 Subject: [PATCH 41/59] Make radio buttons more legible --- app_package/templates/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app_package/templates/index.html b/app_package/templates/index.html index 4e1d03c..ec29ed5 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -81,13 +81,16 @@

    Relationships

    {% endfor %} +
    +

    +

    From dbb6a19dc9c6a47cbf496ed64ffc58941dff4ff0 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Wed, 1 Apr 2020 12:44:26 -0400 Subject: [PATCH 42/59] added addAttr and delAttr tests --- run.py | 2 +- tests/unittests/test_run.py | 200 ++++++++++++++++++++++++++++++++++-- 2 files changed, 194 insertions(+), 8 deletions(-) diff --git a/run.py b/run.py index 25ac912..dfff509 100644 --- a/run.py +++ b/run.py @@ -106,7 +106,7 @@ def do_addAttr(self, args): else: print('Successfully added ' + attr_type + ' \'' + attr + '\'') else: - print("Usage: addAttr , , , ... , ") + print("Usage: addAttr , , , , ... , ") def complete_addAttr(self, text, line, begidx, endidx): btext = core_parse(line[7:])[-1] diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index b695e41..536b418 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -1,13 +1,11 @@ """ Unit tests for run.py/Command Line Interface Tests: - [X] add - [X] delete - [X] edit - [] relationships - [] undo/redo - [] fields - [] methods + [X] add [] redo + [X] delete [X] fields + [X] edit [X] methods + [X] relationships [] save + [] undo [] load """ import run @@ -21,6 +19,14 @@ def captureList(capsys): captured = capsys.readouterr() return captured +# builds an editor frame for testing attributes +# so I don't have to retype the same thing over and over again +# also captures the add output for me +def attribute_frame(capsys): + app.do_add("test, morestuff") + captured = capsys.readouterr() + return captured + ################################ TEST COMMANDS ############################## # Test list and maybe save/load @@ -181,6 +187,186 @@ def test_do_edit_deleted_class(capsys): assert captured.out == "thisexists\nbooga\n\n" ############################### FIELDS/METHODS ############################## +################################ TEST addAttr ############################### + +# Fields +def test_addAttr_one_field(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + assert captured.out == "Successfully added field 'onefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\n > Fields: onefield\nmorestuff\n\n" + +def test_addAttr_multi_fields(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("morestuff, field, onefield, twofields, redfield, bluefield") + captured = capsys.readouterr() + assert captured.out == "Successfully added field 'onefield'\nSuccessfully added field 'twofields'\nSuccessfully added field 'redfield'\nSuccessfully added field 'bluefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Fields: onefield, twofields, redfield, bluefield\n\n" + +def test_addAttr_no_fields(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, ") + captured = capsys.readouterr() + assert captured.out == "Usage: addAttr , , , , ... , \n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_addAttr_dup_field(capsys): + captured = attribute_frame(capsys) + # first add a duplicate using multi add + app.do_addAttr("morestuff, field, dup, dup") + captured = capsys.readouterr() + assert captured.out == "Successfully added field 'dup'\nERROR: Unable to add field 'dup'\n" + + app.do_addAttr("morestuff, field, dup") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to add field 'dup'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Fields: dup\n\n" + +# Methods +def test_addAttr_one_method(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, onemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully added method 'onemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\n > Methods: onemethod\nmorestuff\n\n" + +def test_addAttr_multi_methods(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("morestuff, method, onemethod, twomethods, redmethod, bluemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully added method 'onemethod'\nSuccessfully added method 'twomethods'\nSuccessfully added method 'redmethod'\nSuccessfully added method 'bluemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Methods: onemethod, twomethods, redmethod, bluemethod\n\n" + +def test_addAttr_no_method(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, ") + captured = capsys.readouterr() + assert captured.out == "Usage: addAttr , , , , ... , \n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_addAttr_dup_method(capsys): + captured = attribute_frame(capsys) + # first add a duplicate using multi add + app.do_addAttr("morestuff, method, dup, dup") + captured = capsys.readouterr() + assert captured.out == "Successfully added method 'dup'\nERROR: Unable to add method 'dup'\n" + + app.do_addAttr("morestuff, method, dup") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to add method 'dup'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Methods: dup\n\n" + +################################ TEST delAttr ############################### +def test_delAttr_one_field(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_one_method(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, onemethod") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_multi_fields(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield, twofields, redfield, bluefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield, twofields, redfield, bluefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'redfield'\nSuccessfully deleted attribute 'bluefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_multi_methods(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, onemethod, twomethods, redmethod, bluemethod") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod, twomethods, redmethod, bluemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\nSuccessfully deleted attribute 'redmethod'\nSuccessfully deleted attribute 'bluemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_both_same_class(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onemethod") + app.do_addAttr("test, method, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod, onefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'onefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_both_diff_class(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield, twofields") + app.do_addAttr("morestuff, method, onemethod, twomethods") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield, twofields") + app.do_delAttr("morestuff, onemethod, twomethods") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_none(capsys): + captured = attribute_frame(capsys) + + # first start when theres no attributes + app.do_delAttr("test, onefield") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to delete attribute 'onefield'\n" + + # now add an attribute and try to delete nothing + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, ") + captured = capsys.readouterr() + assert captured.out == "Usage: delAttr , , , ... , \n" + + captured = captureList(capsys) + assert captured.out == "test\n > Fields: onefield\nmorestuff\n\n" ############################### RELATIONSHIPS ############################### ################################ TEST addRel ################################ From 5063cbbd12e0dc291e87acae6db2e1f850224cdf Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Thu, 2 Apr 2020 16:14:56 -0400 Subject: [PATCH 43/59] Change relationship list in CLI to client wishes; Add project documentation To prepare for this weekend's release, switch relationship output in CLI to a standalone table which displays from, to, and type. Also, make sure documentation is up-to-date --- app_package/core_func.py | 5 ++++ app_package/memento/command_stack.py | 1 + app_package/models.py | 2 ++ app_package/routes.py | 2 ++ app_package/static/javascript/index.js | 2 ++ app_package/templates/index.html | 2 +- cleardb.py | 2 ++ install.py | 5 ++++ run.py | 36 ++++++++++++++------------ tests/unittests/test_routes.py | 2 +- 10 files changed, 40 insertions(+), 19 deletions(-) diff --git a/app_package/core_func.py b/app_package/core_func.py index f791fd9..25f8dd5 100644 --- a/app_package/core_func.py +++ b/app_package/core_func.py @@ -251,6 +251,7 @@ def core_del_rel(from_name, to_name): return 1 def core_parse (string): + """Parses useful tokens for data manipulation from a string list with comma delimiters""" parensUnmatched = 0 stringBuf = "" listBuf = [] @@ -277,11 +278,15 @@ def core_parse (string): return listBuf def removeTrailingWhitespace(string): + """Helper function which removes trailing whitespace from a string""" while (string[-1] == ' ' or string[-1] == '\t'): string = string[0:-1] return string def parseType(input): + """Parses type from a string attribute in the following formats: + + :""" # remove the crap in parens front = input.split('(', 1)[0] tipe = "" diff --git a/app_package/memento/command_stack.py b/app_package/memento/command_stack.py index 260d960..c4726f9 100644 --- a/app_package/memento/command_stack.py +++ b/app_package/memento/command_stack.py @@ -1,3 +1,4 @@ +"""Command stack which allows undo/redo functionality""" # could (should) be a singleton. diff --git a/app_package/models.py b/app_package/models.py index e52bc68..326e42b 100644 --- a/app_package/models.py +++ b/app_package/models.py @@ -52,12 +52,14 @@ class Meta: model = Class class RelationshipSchema(ma.ModelSchema): + """Meta model used by flask-marshmallow in jsonification.""" member_of = ma.Nested(ClassSchema) class Meta: fields = ("from_name", "to_name", "rel_type") model = Relationship class AttributeSchema(ma.ModelSchema): + """Meta model used by flask-marshmallow in jsonification.""" member_of = ma.Nested(ClassSchema) class Meta: model = Attribute \ No newline at end of file diff --git a/app_package/routes.py b/app_package/routes.py index ac4ff9e..386b7e0 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -226,11 +226,13 @@ def getRelationship(): @app.route("/undo/", methods=['POST']) def undo(): + """Deals with requests from GUI to undo the last command executed by the user""" cmd_stack.undo() return redirect('/') @app.route("/redo/", methods=['POST']) def redo(): + """Deals with requests from GUI to redo the last command undone by the user""" cmd_stack.redo() return redirect('/') diff --git a/app_package/static/javascript/index.js b/app_package/static/javascript/index.js index 77ed154..c119401 100644 --- a/app_package/static/javascript/index.js +++ b/app_package/static/javascript/index.js @@ -43,6 +43,7 @@ jsPlumb.ready(function() { } }); + // Ensure lines remain useful when window resized or zoomed window.onresize = function (){ jsPlumb.repaintEverything(); }; @@ -276,6 +277,7 @@ function ensureValidCoords(name){ } } +// Prevent overlap of dragged elements when dropped function ensureNoOverlap(name, lastMove){ let el = document.getElementById(name); let rect1 = el.getBoundingClientRect(); diff --git a/app_package/templates/index.html b/app_package/templates/index.html index f8cbfa9..b54c4b4 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -57,7 +57,7 @@

    Attributes

    Add Attributes

    • - +
    • diff --git a/cleardb.py b/cleardb.py index f1d0c72..4622038 100644 --- a/cleardb.py +++ b/cleardb.py @@ -1,3 +1,5 @@ +"""Useful to clear and remake the database""" + from app_package import db from app_package.models import Class, ClassSchema, Relationship, Attribute, RelationshipSchema, AttributeSchema diff --git a/install.py b/install.py index bbe7ea4..ac43702 100644 --- a/install.py +++ b/install.py @@ -1,5 +1,7 @@ +"""Install file for requirements""" import subprocess, sys +################################### Generic Requirements ################################## requirements = [ "Click==7.0", "Flask==1.1.1", @@ -16,6 +18,7 @@ "parse==1.15.0" ] +################################### OS Specific ################################## windows = [ "pyreadline" ] @@ -27,8 +30,10 @@ mac = [ "gnureadline" ] +################################################################################## def install(list): + """Helper function to pip install all requirements""" for thing in list: subprocess.check_call([sys.executable, "-m", "pip", "install", thing]) diff --git a/run.py b/run.py index 25ac912..ae62e92 100644 --- a/run.py +++ b/run.py @@ -45,6 +45,7 @@ def do_delete(self, args): print("Usage: delete , , ... , ") def complete_delete(self, text, line, begidx, endidx): + """Provides tab completion data for delete""" btext = core_parse(line[7:])[-1] allClasses = Class.query.all() ClassNames = [] @@ -57,7 +58,6 @@ def complete_delete(self, text, line, begidx, endidx): tokens.pop(0) classTokens.pop(0) - ClassNames.append(" ".join(classTokens)) return ClassNames @@ -79,6 +79,7 @@ def do_edit(self, args): print("Usage: edit , ") def complete_edit(self, text, line, begidx, endidx): + """Provides tab completion data for edit""" allClasses = Class.query.all() ClassNames = [] for Klass in allClasses: @@ -89,7 +90,7 @@ def complete_edit(self, text, line, begidx, endidx): ######################################## Attribute level ######################################### def do_addAttr(self, args): - """Accepts a single class name followed by a list of attribute names separated by commas and adds them to the class. + """Accepts a single class name and attribute type followed by a list of attribute names separated by commas and adds them to the class. Usage: addAttr , , , , ... , """ argList = core_parse(args) @@ -106,9 +107,10 @@ def do_addAttr(self, args): else: print('Successfully added ' + attr_type + ' \'' + attr + '\'') else: - print("Usage: addAttr , , , ... , ") + print("Usage: addAttr , , , , ... , ") def complete_addAttr(self, text, line, begidx, endidx): + """Provides tab completion data for addAttr""" btext = core_parse(line[7:])[-1] allClasses = Class.query.all() allClassNames = [] @@ -136,7 +138,7 @@ def complete_addAttr(self, text, line, begidx, endidx): def do_delAttr(self, args): """Accepts a single class name followed by a list of attribute names separated by commas and removes them from the class. - Usage: delAttr , , , , ... , + Usage: delAttr , , , ... , """ argList = core_parse(args) if len(argList) > 1: @@ -151,6 +153,7 @@ def do_delAttr(self, args): print("Usage: delAttr , , , ... , ") def complete_delAttr(self, text, line, begidx, endidx): + """Provides tab completion data for delAttr""" bline = core_parse(line[8:]) btext = bline[-1] possibleMatches = [] @@ -197,6 +200,7 @@ def do_editAttr(self, args): print("Usage: editAttr , , ") def complete_editAttr(self, text, line, begidx, endidx): + """Provides tab completion data for editAttr""" bline = core_parse(line[8:]) btext = bline[-1] possibleMatches = [] @@ -228,7 +232,7 @@ def complete_editAttr(self, text, line, begidx, endidx): ########################################## Relationship level ######################################## def do_addRel(self, args): - """Accepts a single parent class name followed by a list of child class names separated by commas and adds relationships from parents to children in database. + """Accepts a single 'from' class name and relationship type followed by a list of 'to' class names separated by commas and adds these relationships to the database. Usage: addRel , , , , ... , Valid relationship types: agg, comp, gen, none """ @@ -237,7 +241,7 @@ def do_addRel(self, args): class_name = argList.pop(0) rel_type = argList.pop(0).lower() if not rel_type in ["agg", "comp", "gen", "none"]: - print('ERROR: Invalid relationship type: ' + rel_type + "\n\tValid relationship types: agg, comp, gen, none") + print('ERROR: Invalid relationship type: ' + rel_type + "\n Valid relationship types: agg, comp, gen, none") return for rel in argList: addRelCmd = add_rel(class_name, rel, rel_type) @@ -246,9 +250,10 @@ def do_addRel(self, args): else: print('Successfully added relationship from \'' + class_name + '\' to \'' + rel + '\' of type \'' + rel_type + '\'') else: - print("Usage: addRel , , , , ... , \n\tValid relationship types: agg, comp, gen, none") + print("Usage: addRel , , , , ... , \n Valid relationship types: agg, comp, gen, none") def complete_addRel(self, text, line, begidx, endidx): + """Provides tab completion data for addRel""" btext = core_parse(line[7:])[-1] allClasses = Class.query.all() allClassNames = [] @@ -277,7 +282,7 @@ def complete_addRel(self, text, line, begidx, endidx): return ClassNames def do_delRel(self, args): - """Accepts a single parent class name followed by a list of child class names separated by commas and removes relationships from parents to children in database. + """Accepts a single 'from' class name followed by a list of 'to' class names separated by commas and removes these relationships from the database. Usage: delRel , , , ... , """ argList = core_parse(args) @@ -293,6 +298,7 @@ def do_delRel(self, args): print("Usage: delRel , , , ... , ") def complete_delRel(self, text, line, begidx, endidx): + """Provides tab completion data for delRel""" bline = core_parse(line[7:]) btext = bline[-1] possibleMatches = [] @@ -381,15 +387,11 @@ def do_list(self, args): listStrMethods = listStrMethods[0:-1] + '\n' if showMethods: listStr += listStrMethods - - relationships = classObj.class_relationships - if len(relationships) > 0: - listStr += ' > Relationships: ' - for rel in relationships: - if rel == relationships[-1]: - listStr += rel.to_name + ' (' + rel.rel_type + ')\n' - else: - listStr += (rel.to_name + ' (' + rel.rel_type + '), ') + + relationships = Relationship.query.order_by(Relationship.from_name).all() + listStr += "Relationships:\n" + for rel in relationships: + listStr += " " + rel.from_name + " -> " + rel.to_name + " (" + rel.rel_type + ")\n" print(listStr) diff --git a/tests/unittests/test_routes.py b/tests/unittests/test_routes.py index 32cba04..8c57090 100644 --- a/tests/unittests/test_routes.py +++ b/tests/unittests/test_routes.py @@ -1,4 +1,4 @@ -"""Unit tests for core route functionality.""" +"""Unit tests for core route functionality (GUI).""" import io ################################ TEST INDEX ################################ From a101ee209735b6ba089a5f815228436d6b186f6e Mon Sep 17 00:00:00 2001 From: NatureElf Date: Thu, 2 Apr 2020 18:49:32 -0400 Subject: [PATCH 44/59] GUI now updates with correct line overlay based on relationship type --- app_package/static/css/main.css | 6 +++++ app_package/static/javascript/index.js | 34 ++++++++++++++++++++++++-- app_package/templates/index.html | 12 ++++----- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/app_package/static/css/main.css b/app_package/static/css/main.css index ba8111e..3e7738d 100644 --- a/app_package/static/css/main.css +++ b/app_package/static/css/main.css @@ -637,3 +637,9 @@ body{ from {opacity: 0;} to {opacity: 1;} } + +.labelStyle { + color: #6B6E70; + font-size: 30px; + font-weight: bold; +} \ No newline at end of file diff --git a/app_package/static/javascript/index.js b/app_package/static/javascript/index.js index 77ed154..50d7c30 100644 --- a/app_package/static/javascript/index.js +++ b/app_package/static/javascript/index.js @@ -238,6 +238,35 @@ function closeFlashMsg(){ this.style.display = "none"; } +//agg-> Open Diamond +//comp-> filled diamond +//gen-> empty arrow +//none-> plain arrow + +//Get overlay returns the an array for the overlay for relationship types +function getOverlay(relType){ + switch(relType){ + case "agg": + return ["Label", {location:1, width:15, length:15, "label":"â—‡", cssClass:"labelStyle"}]; + case "comp": + return ["Diamond", {location:1, width:18, length:18}]; + case "gen": + return ["PlainArrow", {location:1, width:15, length:15}]; + default: + return ["Arrow", {location:1, width:15, length:15, foldback:.25}]; + } +} + +//getConnector is used to get the connector becuase for the agg relationship I needed +// to give the non-filled diamond a gap from the class +function getConnector(relType, from, to){ + if(relType == "agg"){ + return [(from != to ? "Flowchart" : "Bezier"),{gap:[0,11]}]; + }else{ + return (from != to ? "Flowchart" : "Bezier"); + } +} + // Get relationship data from database and use jsPlumb to draw lines function renderLines(){ let xReq = new XMLHttpRequest(); @@ -247,15 +276,16 @@ function renderLines(){ let data = JSON.parse(this.responseText); for(var i = 0; i < data.length; ++i) { let from = data[i].from_name; + let relType = data[i].rel_type; let to = data[i].to_name; jsPlumb.connect({ source:from, target:to, anchor:"Continuous", endpoint:"Blank", - connector:(from != to ? "Flowchart" : "Bezier"), + connector:getConnector(relType, from, to), paintStyle:{ stroke: '#6B6E70', strokeWidth:2 }, - overlays:[["PlainArrow", {location:1, width:10, length:10}]] + overlays:[getOverlay(relType)] }); } } diff --git a/app_package/templates/index.html b/app_package/templates/index.html index ec29ed5..e9db853 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -60,12 +60,10 @@

      Add Attributes

    • -
    • - - - - -
    • + + + +
    @@ -91,7 +89,7 @@

    Relationships


    - +
    From 08cf4a26a1419f4c295b635fd15d29509e4b0a9a Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Thu, 2 Apr 2020 23:30:03 -0400 Subject: [PATCH 45/59] Fix field/method delimiting; Fix other bugs The branch Ryan put in seemed to be pretty broken, so I started over from the beginning on a branch from develop. Hopefully it works now. Also, discovered a bug in JSONification of database entries that broke save/load. Fixed it after quite a few hours of hateful coding. --- app_package/core_func.py | 8 +++-- app_package/memento/func_objs.py | 12 ++++--- app_package/models.py | 35 ++++++++++++-------- app_package/routes.py | 14 +++++--- app_package/templates/index.html | 50 ++++++++++++++++++++-------- tests/unittests/test_routes.py | 56 +++++++++++++++----------------- 6 files changed, 107 insertions(+), 68 deletions(-) diff --git a/app_package/core_func.py b/app_package/core_func.py index f791fd9..3add7f4 100644 --- a/app_package/core_func.py +++ b/app_package/core_func.py @@ -12,7 +12,7 @@ def core_add(class_name): try: if "'" in class_name or '"' in class_name: return 1 - new_class = Class(name=class_name) + new_class = Class(name=class_name, x=0, y=0) db.session.add(new_class) db.session.commit() return 0 @@ -124,7 +124,8 @@ def core_load(data): for attr in element["class_attributes"]: newAttr = Attribute( attribute=attr["attribute"], - class_name=attr["class_name"] + class_name=attr["class_name"], + attr_type=attr["attr_type"] ) db.session.add(newAttr) @@ -132,7 +133,8 @@ def core_load(data): for rel in element["class_relationships"]: newRel = Relationship( from_name=rel["from_name"], - to_name=rel["to_name"] + to_name=rel["to_name"], + rel_type=rel["rel_type"] ) db.session.add(newRel) diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index 5bc95a1..31e2289 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -125,7 +125,7 @@ def execute(self): return core_add_attr(self.className, self.attrName, self.attrType) def undo(self): - return core_del_attr(self.className, self.attrName, self.attrType) + return core_del_attr(self.className, self.attrName) def redo(self): return core_add_attr(self.className, self.attrName, self.attrType) @@ -135,16 +135,18 @@ class del_attr(Command): """Command class for core_del_attr. Accepts a class name and the name of an attribute to remove""" className = '' attrName = '' + attrType = '' - def __init__(self, className, attrName): + def __init__(self, className, attrName, attrType): self.className = className self.attrName = attrName + self.attrType = attrType def execute(self): return core_del_attr(self.className, self.attrName) def undo(self): - return core_add_attr(self.className, self.attrName) + return core_add_attr(self.className, self.attrName, self.attrType) def redo(self): return core_del_attr(self.className, self.attrName) @@ -196,10 +198,12 @@ class del_rel(Command): """Command class for core_del_rel. Accepts a class name and the name of the child""" parentName = '' childName = '' + relType = '' - def __init__(self, parentName, childName): + def __init__(self, parentName, childName, relType): self.parentName = parentName self.childName = childName + self.relType = relType def execute(self): return core_del_rel(self.parentName, self.childName) diff --git a/app_package/models.py b/app_package/models.py index e52bc68..5a8cb47 100644 --- a/app_package/models.py +++ b/app_package/models.py @@ -3,6 +3,7 @@ from app_package import db, ma from datetime import datetime from sqlalchemy.orm import relationship +from marshmallow import INCLUDE, fields, Schema class Class(db.Model): @@ -45,19 +46,25 @@ class Relationship(db.Model): parent_class = relationship("Class", back_populates="class_relationships", foreign_keys=[from_name, to_name], primaryjoin='Class.name==Relationship.from_name') -class ClassSchema(ma.ModelSchema): +class RelationshipSchema(Schema): """Meta model used by flask-marshmallow in jsonification.""" + from_name = fields.String() + to_name = fields.String() + rel_type = fields.String() + +class AttributeSchema(Schema): + """Meta model used by flask-marshmallow in jsonification.""" + attribute = fields.String() + attr_type = fields.String() + date_created = fields.DateTime() + class_name = fields.String() - class Meta: - model = Class - -class RelationshipSchema(ma.ModelSchema): - member_of = ma.Nested(ClassSchema) - class Meta: - fields = ("from_name", "to_name", "rel_type") - model = Relationship - -class AttributeSchema(ma.ModelSchema): - member_of = ma.Nested(ClassSchema) - class Meta: - model = Attribute \ No newline at end of file + +class ClassSchema(Schema): + """Meta model used by flask-marshmallow in jsonification.""" + name = fields.String() + date_created = fields.DateTime() + x = fields.Int() + y = fields.Int() + class_attributes = fields.Nested(AttributeSchema, many=True) + class_relationships = fields.Nested(RelationshipSchema, many=True) \ No newline at end of file diff --git a/app_package/routes.py b/app_package/routes.py index 3be516a..a8586d5 100644 --- a/app_package/routes.py +++ b/app_package/routes.py @@ -155,7 +155,8 @@ def update(oldName, newName): def delAttribute(name, attr): """Helper to remove attributes from class.""" - delAttrCmd = del_attr(name, attr) + attr_to_del = Attribute.query.get({"class_name":name, "attribute":attr}) + delAttrCmd = del_attr(name, attr, attr_to_del.attr_type) if cmd_stack.execute(delAttrCmd): flash("ERROR: Unable to remove attribute " + attr + " from " + name, 'error') @@ -207,10 +208,15 @@ def addRelationship(fro, to, rel_type): def delRelationship(fro, to): """Helper function to remove relationships from class.""" + print(getRelationship()) for child in to: - delRelCmd = del_rel(fro, child) - if cmd_stack.execute(delRelCmd): - flash("ERROR: Unable to delete relationship from " + fro + " to " + child, 'error') + rel_to_del = Relationship.query.get({"from_name":fro, "to_name":child}) + if rel_to_del is None: + flash("ERROR: No such relationship from " + fro + " to " + child, 'error') + else: + delRelCmd = del_rel(fro, child, rel_to_del.rel_type) + if cmd_stack.execute(delRelCmd): + flash("ERROR: Unable to delete relationship from " + fro + " to " + child, 'error') @app.route("/getRelationships/", methods=['POST']) diff --git a/app_package/templates/index.html b/app_package/templates/index.html index ec29ed5..e2c9775 100644 --- a/app_package/templates/index.html +++ b/app_package/templates/index.html @@ -32,11 +32,18 @@

    -
    -

    Attributes

    + {% for attr in attributes if attr.class_name == class.name %} + {% if loop.first %} +
    + {% endif %} + {% endfor %} + {% for attr in attributes if attr.class_name == class.name and attr.attr_type == 'field' %} + {% if loop.first %} +

    Fields

    + {% endif %} + {% endfor %}
      - {% for attr in attributes %} - {% if attr.class_name == class.name %} + {% for attr in attributes if attr.class_name == class.name and attr.attr_type == 'field' %}
    • @@ -49,10 +56,29 @@

      Attributes

    • - {% endif %} {% endfor %}
    -
    @@ -91,7 +115,7 @@

    Relationships


    - +
    diff --git a/tests/unittests/test_routes.py b/tests/unittests/test_routes.py index 32cba04..e7c1bdd 100644 --- a/tests/unittests/test_routes.py +++ b/tests/unittests/test_routes.py @@ -81,45 +81,41 @@ def test_delete_no_name (test_client, init_database): def test_load_good (test_client, init_database): jFile = io.BytesIO(b"""[ - { - "class_attributes": [], - "class_relationships": [], - "date_created": "2020-02-19T21:22:18.116981", - "name": "Plane", - "x": 542, - "y": 514 - }, - { - "class_attributes": [], - "class_relationships": [], - "date_created": "2020-02-19T21:22:18.116981", - "name": "pardner", - "x": 285, - "y": 201 - }, { "class_attributes": [ { - "attribute": "seven", - "class_name": "Howdy" + "attr_type": "field", + "attribute": "please help", + "class_name": "Friendship ended with SQLALCHEMY-MARSHMALLOW", + "date_created": "2020-04-03T03:10:40.883665" } ], - "class_relationships": [ + "class_relationships": [], + "date_created": "2020-04-03T03:09:38.770317", + "name": "Friendship ended with SQLALCHEMY-MARSHMALLOW", + "x": 675, + "y": 42 + }, + { + "class_attributes": [ { - "from_name": "Howdy", - "to_name": "Plane" + "attr_type": "method", + "attribute": "seriously though", + "class_name": "Now DEPRESSION is my best friend", + "date_created": "2020-04-03T03:11:00.813333" } ], - "date_created": "2020-02-19T21:22:18.116981", - "name": "Howdy", - "x": 305, - "y": 413 + "class_relationships": [], + "date_created": "2020-04-03T03:09:38.790341", + "name": "Now DEPRESSION is my best friend", + "x": 100, + "y": 74 } - ]""") +]""") response = test_client.post('/load/', data=dict(file=(jFile, 'temp.json')), content_type='multipart/form-data', follow_redirects=True) - assert b"Howdy" in response.data - assert b"pardner" in response.data - assert b"Plane" in response.data + assert b"Friendship ended" in response.data + assert b"please help" in response.data + assert b"DEPRESSION" in response.data def test_load_invalid_json_attributes (test_client, init_database): jFile = io.BytesIO(b"""[ @@ -295,7 +291,7 @@ def test_add_one_relationship_but_its_for_a_class_that_doesnt_exist(test_client, def test_delete_a_relationship_that_didnt_exist_in_the_first_place(test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1 TestClass2'), follow_redirects=True) response = test_client.post('/manipRelationship/', data=dict(class_name='TestClass2', relationship=['TestClass69'], rel_type="agg", action="delete"), follow_redirects=True) - assert b"ERROR: Unable to delete relationship from" in response.data + assert b"ERROR: No such relationship from" in response.data def test_manip_relationship_invalid_args (test_client, init_database): test_client.post('/', data=dict(class_name='TestClass1, TestClass2'), follow_redirects=True) From 72b52bdbf73983c1f348684a8916209e731c5806 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Fri, 3 Apr 2020 15:13:31 -0400 Subject: [PATCH 46/59] Fix mismatch in expected testing output This time on the right branch! --- run.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/run.py b/run.py index ae62e92..03b0bb3 100644 --- a/run.py +++ b/run.py @@ -389,9 +389,10 @@ def do_list(self, args): listStr += listStrMethods relationships = Relationship.query.order_by(Relationship.from_name).all() - listStr += "Relationships:\n" - for rel in relationships: - listStr += " " + rel.from_name + " -> " + rel.to_name + " (" + rel.rel_type + ")\n" + if len(relationships) > 0: + listStr += "Relationships:\n" + for rel in relationships: + listStr += " " + rel.from_name + " -> " + rel.to_name + " (" + rel.rel_type + ")\n" print(listStr) From ce2826fd197ca136239fbb208384074c5e354cbe Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Fri, 3 Apr 2020 15:47:24 -0400 Subject: [PATCH 47/59] Fix bug with attr/rel types in undo/redo --- app_package/memento/func_objs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app_package/memento/func_objs.py b/app_package/memento/func_objs.py index 31e2289..e02fe1a 100644 --- a/app_package/memento/func_objs.py +++ b/app_package/memento/func_objs.py @@ -69,12 +69,12 @@ def undo(self): result = core_add(self.className) if result == 0: for attr in self.attributes: - core_add_attr(self.className, attr.attribute) + core_add_attr(self.className, attr.attribute, attr.attr_type) print(self.relationships) for rel in self.relationships: print(rel.from_name) - core_add_rel(rel.from_name, rel.to_name) + core_add_rel(rel.from_name, rel.to_name, rel.rel_type) class_ = Class.query.get_or_404(self.className) class_.x = self.xPos @@ -88,6 +88,8 @@ def redo(self): class_ = Class.query.get_or_404(self.className) self.xPos = class_.x self.yPos = class_.y + self.attributes = Attribute.query.filter(self.className == Attribute.class_name).all() + self.relationships = Relationship.query.filter(self.className == Relationship.from_name).all() return core_delete(self.className) From 41aa8047acfd3dea8af004dff96aa8b4c846fb2d Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Sun, 5 Apr 2020 13:42:11 -0400 Subject: [PATCH 48/59] finished attribute tests --- tests/unittests/test_run.py | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 536b418..db07afe 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -4,7 +4,7 @@ [X] add [] redo [X] delete [X] fields [X] edit [X] methods - [X] relationships [] save + [] relationships [] save [] undo [] load """ @@ -368,6 +368,48 @@ def test_delAttr_none(capsys): captured = captureList(capsys) assert captured.out == "test\n > Fields: onefield\nmorestuff\n\n" +################################ TEST editAttr ############################## +def test_editAttr_fields(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + + app.do_editAttr("test, onefield, twofield") + captured = capsys.readouterr() + assert captured.out == "Successfully updated attribute 'onefield' to 'twofield'\n" + + captured = captureList(capsys) + assert captured.out == "test\n > Fields: twofield\nmorestuff\n\n" + +def test_editAttr_methods(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("morestuff, method, onemethod") + captured = capsys.readouterr() + + app.do_editAttr("morestuff, onemethod, twomethod") + captured = capsys.readouterr() + assert captured.out == "Successfully updated attribute 'onemethod' to 'twomethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Methods: twomethod\n\n" + +def test_editAttr_none(capsys): + captured = attribute_frame(capsys) + # First test editing a nonexistent attribute + app.do_editAttr("test, thisdoesntexist, stilldoesnt") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to update attribute 'thisdoesntexist' to 'stilldoesnt'\n" + + # Now test giving an attribute no name + app.do_addAttr("morestuff, field, yeet") + captured = capsys.readouterr() + app.do_editAttr("morestuff, yeet, ") + captured = capsys.readouterr() + assert captured.out == "Usage: editAttr , , \n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n > Fields: yeet\n\n" + ############################### RELATIONSHIPS ############################### ################################ TEST addRel ################################ From 534104458f6aeda3c0e5eeca1b5b471f5c3953b9 Mon Sep 17 00:00:00 2001 From: NatureElf Date: Sun, 5 Apr 2020 14:39:36 -0400 Subject: [PATCH 49/59] Updated ReadMe --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.txt | 50 -------------------------------------------------- 2 files changed, 54 insertions(+), 50 deletions(-) create mode 100644 README.md delete mode 100644 README.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..124e71d --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Installation + +### Python: +- Make sure that python3 is installed on your computer +- Also make sure that python is located as a path in your enviroment variables + +### Install virtualenv: +- Type `pip install virtualenv` + +### Create a virtual environment (only done once): +- Navigate to directory root +- Type `virtualenv env` + +**Here is where different operating systems require different commands.** +- [Windows Installation](#windows) +- [Linux/Mac Installation](#linux-or-mac) + + + +## LINUX or MAC + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `source env/bin/activate` +- Type `python3 install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To create a new Database: +- Type `python3 createdb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- `python3 run.py` +- If you would like the web view, type `web` in the console that appears + +## WINDOWS + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `env\Scripts\activate` +- Type `python install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To create a new Database: +- `python createdb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- python run.py +- If you would like the web view, type 'web' in the console that appears \ No newline at end of file diff --git a/README.txt b/README.txt deleted file mode 100644 index 93718a3..0000000 --- a/README.txt +++ /dev/null @@ -1,50 +0,0 @@ -Python: - - Make sure that python3 is installed on your computer - - Also make sure that python is located as a path in your enviroment variables - -Install virtualenv: - - Type 'pip install virtualenv' - -Create a virtual environment (only done once): - - Navigate to directory root - - Type 'virtualenv env' - -Here is where different operating systems require different commands. Please follow the section for your operating system. - - --------- LINUX/MAC -------- - -Install all dependencies: - - Navigate to the main project folder ('220 cowboy') - - Activate environment. - - Type 'source env/bin/activate' - - Type 'python3 install.py' to install libraries - -* Both operations below assume you are inside of the activated enviroment * - -To create a new Database: - - Type 'python3 createdb.py' - - If a warning containing (SQLALCHEMY_TRACK_MODIFICATIONS) is thrown it can be safely ignored - -To run the application: - - python3 run.py - - If you would like the web view, type 'web' in the console that appears - - --------- WINDOWS -------- - -Install all dependencies: - - Navigate to the main project folder ('220 cowboy') - - Activate environment. - - Type 'env\Scripts\activate' - - Type 'python install.py' to install libraries - -* Both operations below assume you are inside of the activated enviroment * - -To create a new Database: - - python createdb.py - - If a warning containing (SQLALCHEMY_TRACK_MODIFICATIONS) is thrown it can be safely ignored - -To run the application: - - python run.py - - If you would like the web view, type 'web' in the console that appears From a597c4364c944ec8954c3c79c7d1c25a140d9228 Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Sun, 5 Apr 2020 15:00:49 -0400 Subject: [PATCH 50/59] writing delAttr tests in a seperate file --- tests/unittests/test_run.py | 93 ------------------------------------- 1 file changed, 93 deletions(-) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index db07afe..8149170 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -274,99 +274,6 @@ def test_addAttr_dup_method(capsys): assert captured.out == "test\nmorestuff\n > Methods: dup\n\n" ################################ TEST delAttr ############################### -def test_delAttr_one_field(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, field, onefield") - captured = capsys.readouterr() - - app.do_delAttr("test, onefield") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onefield'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_one_method(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, method, onemethod") - captured = capsys.readouterr() - - app.do_delAttr("test, onemethod") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onemethod'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_multi_fields(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, field, onefield, twofields, redfield, bluefield") - captured = capsys.readouterr() - - app.do_delAttr("test, onefield, twofields, redfield, bluefield") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'redfield'\nSuccessfully deleted attribute 'bluefield'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_multi_methods(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, method, onemethod, twomethods, redmethod, bluemethod") - captured = capsys.readouterr() - - app.do_delAttr("test, onemethod, twomethods, redmethod, bluemethod") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\nSuccessfully deleted attribute 'redmethod'\nSuccessfully deleted attribute 'bluemethod'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_both_same_class(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, field, onemethod") - app.do_addAttr("test, method, onefield") - captured = capsys.readouterr() - - app.do_delAttr("test, onemethod, onefield") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'onefield'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_both_diff_class(capsys): - captured = attribute_frame(capsys) - app.do_addAttr("test, field, onefield, twofields") - app.do_addAttr("morestuff, method, onemethod, twomethods") - captured = capsys.readouterr() - - app.do_delAttr("test, onefield, twofields") - app.do_delAttr("morestuff, onemethod, twomethods") - captured = capsys.readouterr() - assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\n" - - captured = captureList(capsys) - assert captured.out == "test\nmorestuff\n\n" - -def test_delAttr_none(capsys): - captured = attribute_frame(capsys) - - # first start when theres no attributes - app.do_delAttr("test, onefield") - captured = capsys.readouterr() - assert captured.out == "ERROR: Unable to delete attribute 'onefield'\n" - - # now add an attribute and try to delete nothing - app.do_addAttr("test, field, onefield") - captured = capsys.readouterr() - - app.do_delAttr("test, ") - captured = capsys.readouterr() - assert captured.out == "Usage: delAttr , , , ... , \n" - - captured = captureList(capsys) - assert captured.out == "test\n > Fields: onefield\nmorestuff\n\n" ################################ TEST editAttr ############################## def test_editAttr_fields(capsys): From 8343a1ba8f4f0fd52409260a404719422d83ec96 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Sun, 5 Apr 2020 15:03:46 -0400 Subject: [PATCH 51/59] Fix bugs related to deleting rels/attrs in CLI --- run.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/run.py b/run.py index 03b0bb3..19e81f4 100644 --- a/run.py +++ b/run.py @@ -144,7 +144,8 @@ def do_delAttr(self, args): if len(argList) > 1: class_name = argList.pop(0) for attr in argList: - delAttrCmd = del_attr(class_name, attr) + attr_to_del = Attribute.query.get({"class_name":class_name, "attribute":attr}) + delAttrCmd = del_attr(class_name, attr, attr_to_del.attr_type) if cmd_stack.execute(delAttrCmd): print('ERROR: Unable to delete attribute \'' + attr + '\'') else: @@ -289,7 +290,8 @@ def do_delRel(self, args): if len(argList) > 1: class_name = argList.pop(0) for rel in argList: - delRelCmd = del_rel(class_name, rel) + rel_to_del = Relationship.query.get({"from_name":class_name, "to_name":rel}) + delRelCmd = del_rel(class_name, rel, rel_to_del.rel_type) if cmd_stack.execute(delRelCmd): print('ERROR: Unable to delete relationship from \'' + class_name + '\' to \'' + rel + '\'') else: From 594f52493fbbe2f9467a20a643056263108a8dbe Mon Sep 17 00:00:00 2001 From: Ethan Gingrich Date: Sun, 5 Apr 2020 15:08:50 -0400 Subject: [PATCH 52/59] adding delAttr tests back in after bugfix --- tests/unittests/test_run.py | 93 +++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/unittests/test_run.py b/tests/unittests/test_run.py index 8149170..db07afe 100644 --- a/tests/unittests/test_run.py +++ b/tests/unittests/test_run.py @@ -274,6 +274,99 @@ def test_addAttr_dup_method(capsys): assert captured.out == "test\nmorestuff\n > Methods: dup\n\n" ################################ TEST delAttr ############################### +def test_delAttr_one_field(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_one_method(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, onemethod") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_multi_fields(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield, twofields, redfield, bluefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield, twofields, redfield, bluefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'redfield'\nSuccessfully deleted attribute 'bluefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_multi_methods(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, method, onemethod, twomethods, redmethod, bluemethod") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod, twomethods, redmethod, bluemethod") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\nSuccessfully deleted attribute 'redmethod'\nSuccessfully deleted attribute 'bluemethod'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_both_same_class(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onemethod") + app.do_addAttr("test, method, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, onemethod, onefield") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'onefield'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_both_diff_class(capsys): + captured = attribute_frame(capsys) + app.do_addAttr("test, field, onefield, twofields") + app.do_addAttr("morestuff, method, onemethod, twomethods") + captured = capsys.readouterr() + + app.do_delAttr("test, onefield, twofields") + app.do_delAttr("morestuff, onemethod, twomethods") + captured = capsys.readouterr() + assert captured.out == "Successfully deleted attribute 'onefield'\nSuccessfully deleted attribute 'twofields'\nSuccessfully deleted attribute 'onemethod'\nSuccessfully deleted attribute 'twomethods'\n" + + captured = captureList(capsys) + assert captured.out == "test\nmorestuff\n\n" + +def test_delAttr_none(capsys): + captured = attribute_frame(capsys) + + # first start when theres no attributes + app.do_delAttr("test, onefield") + captured = capsys.readouterr() + assert captured.out == "ERROR: Unable to delete attribute 'onefield'\n" + + # now add an attribute and try to delete nothing + app.do_addAttr("test, field, onefield") + captured = capsys.readouterr() + + app.do_delAttr("test, ") + captured = capsys.readouterr() + assert captured.out == "Usage: delAttr , , , ... , \n" + + captured = captureList(capsys) + assert captured.out == "test\n > Fields: onefield\nmorestuff\n\n" ################################ TEST editAttr ############################## def test_editAttr_fields(capsys): From bca8888738bf7e9fef4963b11820fb0d68d38ee6 Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Sun, 5 Apr 2020 15:14:47 -0400 Subject: [PATCH 53/59] Fix bug w/ deleting attr/rel which does not exist in CLI --- run.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/run.py b/run.py index 19e81f4..63739cf 100644 --- a/run.py +++ b/run.py @@ -145,8 +145,7 @@ def do_delAttr(self, args): class_name = argList.pop(0) for attr in argList: attr_to_del = Attribute.query.get({"class_name":class_name, "attribute":attr}) - delAttrCmd = del_attr(class_name, attr, attr_to_del.attr_type) - if cmd_stack.execute(delAttrCmd): + if attr_to_del is None or cmd_stack.execute(del_attr(class_name, attr, attr_to_del.attr_type)): print('ERROR: Unable to delete attribute \'' + attr + '\'') else: print('Successfully deleted attribute \'' + attr + '\'') @@ -291,8 +290,7 @@ def do_delRel(self, args): class_name = argList.pop(0) for rel in argList: rel_to_del = Relationship.query.get({"from_name":class_name, "to_name":rel}) - delRelCmd = del_rel(class_name, rel, rel_to_del.rel_type) - if cmd_stack.execute(delRelCmd): + if rel_to_del is None or cmd_stack.execute(del_rel(class_name, rel, rel_to_del.rel_type)): print('ERROR: Unable to delete relationship from \'' + class_name + '\' to \'' + rel + '\'') else: print('Successfully deleted relationship from \'' + class_name + '\' to \'' + rel + '\'') From 05e9eeb101fd926269abbaec07595bdc56db6b66 Mon Sep 17 00:00:00 2001 From: NatureElf Date: Sun, 5 Apr 2020 15:18:16 -0400 Subject: [PATCH 54/59] Added USERGUIDE --- README.md | 54 +++++++++++++++++++++++++++++++++++++++ USERGUIDE.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 README.md create mode 100644 USERGUIDE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..124e71d --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# Installation + +### Python: +- Make sure that python3 is installed on your computer +- Also make sure that python is located as a path in your enviroment variables + +### Install virtualenv: +- Type `pip install virtualenv` + +### Create a virtual environment (only done once): +- Navigate to directory root +- Type `virtualenv env` + +**Here is where different operating systems require different commands.** +- [Windows Installation](#windows) +- [Linux/Mac Installation](#linux-or-mac) + + + +## LINUX or MAC + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `source env/bin/activate` +- Type `python3 install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To create a new Database: +- Type `python3 createdb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- `python3 run.py` +- If you would like the web view, type `web` in the console that appears + +## WINDOWS + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `env\Scripts\activate` +- Type `python install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To create a new Database: +- `python createdb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- python run.py +- If you would like the web view, type 'web' in the console that appears \ No newline at end of file diff --git a/USERGUIDE.md b/USERGUIDE.md new file mode 100644 index 0000000..756b590 --- /dev/null +++ b/USERGUIDE.md @@ -0,0 +1,72 @@ +# Commands + +## Class + +- add + - Accepts a single class name OR a list separated by commas and adds them to the database. + Usage: add , , ... , + +- delete + - Accepts a single class name OR a list separated by commas and removes them from the database. + Usage: delete , , ... , +- edit + - Accepts a single class name followed by a replacement name, separated by commas, and changes instances of old name in database with new name. + Usage: edit , +- list + - Lists every class in the database. + Usage: list + +## Attributes + +- addAttr + - Accepts a single class name and attribute type followed by a list of attribute names separated by commas and adds them to the class. + Usage: addAttr , , , , ... , +- delAttr + - Accepts a single class name followed by a list of attribute names separated by commas and removes them from the class. + Usage: delAttr , , , ... , +- editAttr + - Accepts a single class name followed by an existing attribute within and a new name which will replace said attribute in the class, all separated by commas. + Usage: editAttr , , + +## Relationships + +- addRel + - Accepts a single 'from' class name and relationship type followed by a list of 'to' class names separated by commas and adds these relationships to the database. + Usage: addRel , , , , ... , + Valid relationship types: agg, comp, gen, none +- delRel + - Accepts a single 'from' class name followed by a list of 'to' class names separated by commas and removes these relationships from the database. + Usage: delRel , , , ... , + +## Other Commands + +- help + - List available commands with "help" or detailed help with "help cmd". + Usage: help +- exit + - Exits the UML shell. + Usage: exit +- load + - Loads the contents of a previously saved diagram into the database. + Usage: load +- save + - Saves the contents of the database into a requested file. + Usage: save +- undo + - Reverses your last action. Optionally provide amount. + Usage: undo <# of undo's> +- redo + - Reverse of undo. Will execute undone command again. + Usage: redo +- web + - Starts the web app in the user's default browser. + Usage: web + +## Future Commands + +### Not implemented at this time, but possibly in the future + +- playback + - Playback commands from a file: PLAYBACK rose.cmd +- record + - Save future commands to filename: RECORD rose.cmd From 88e656ab6fdb7a326ee8345ad30b9a5d771279e7 Mon Sep 17 00:00:00 2001 From: e-gingrich <51967475+e-gingrich@users.noreply.github.com> Date: Sun, 5 Apr 2020 15:18:26 -0400 Subject: [PATCH 55/59] Rename README.md to README --- README.md => README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename README.md => README (99%) diff --git a/README.md b/README similarity index 99% rename from README.md rename to README index 124e71d..e97107e 100644 --- a/README.md +++ b/README @@ -51,4 +51,4 @@ ### To run the application: - python run.py -- If you would like the web view, type 'web' in the console that appears \ No newline at end of file +- If you would like the web view, type 'web' in the console that appears From 1b16030831b5a7b55478daeb87e07fd7c7ca5e73 Mon Sep 17 00:00:00 2001 From: e-gingrich <51967475+e-gingrich@users.noreply.github.com> Date: Sun, 5 Apr 2020 15:18:54 -0400 Subject: [PATCH 56/59] Revert last change --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md From e5acdf4ea67de676fc0a8be6748da42bcb67f42b Mon Sep 17 00:00:00 2001 From: NatureElf Date: Sun, 5 Apr 2020 15:22:52 -0400 Subject: [PATCH 57/59] Updated USERGUIDE and deleted the README.txt --- README.txt | 50 -------------------------------------------------- USERGUIDE.md | 35 ++++++++++++++++++----------------- 2 files changed, 18 insertions(+), 67 deletions(-) delete mode 100644 README.txt diff --git a/README.txt b/README.txt deleted file mode 100644 index 93718a3..0000000 --- a/README.txt +++ /dev/null @@ -1,50 +0,0 @@ -Python: - - Make sure that python3 is installed on your computer - - Also make sure that python is located as a path in your enviroment variables - -Install virtualenv: - - Type 'pip install virtualenv' - -Create a virtual environment (only done once): - - Navigate to directory root - - Type 'virtualenv env' - -Here is where different operating systems require different commands. Please follow the section for your operating system. - - --------- LINUX/MAC -------- - -Install all dependencies: - - Navigate to the main project folder ('220 cowboy') - - Activate environment. - - Type 'source env/bin/activate' - - Type 'python3 install.py' to install libraries - -* Both operations below assume you are inside of the activated enviroment * - -To create a new Database: - - Type 'python3 createdb.py' - - If a warning containing (SQLALCHEMY_TRACK_MODIFICATIONS) is thrown it can be safely ignored - -To run the application: - - python3 run.py - - If you would like the web view, type 'web' in the console that appears - - --------- WINDOWS -------- - -Install all dependencies: - - Navigate to the main project folder ('220 cowboy') - - Activate environment. - - Type 'env\Scripts\activate' - - Type 'python install.py' to install libraries - -* Both operations below assume you are inside of the activated enviroment * - -To create a new Database: - - python createdb.py - - If a warning containing (SQLALCHEMY_TRACK_MODIFICATIONS) is thrown it can be safely ignored - -To run the application: - - python run.py - - If you would like the web view, type 'web' in the console that appears diff --git a/USERGUIDE.md b/USERGUIDE.md index 756b590..edae181 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -1,5 +1,6 @@ # Commands + ## Class - add @@ -15,53 +16,53 @@ - list - Lists every class in the database. Usage: list - + ## Attributes - addAttr - Accepts a single class name and attribute type followed by a list of attribute names separated by commas and adds them to the class. - Usage: addAttr , , , , ... , + - Usage: addAttr , , , , ... , - delAttr - Accepts a single class name followed by a list of attribute names separated by commas and removes them from the class. - Usage: delAttr , , , ... , + - Usage: delAttr , , , ... , - editAttr - Accepts a single class name followed by an existing attribute within and a new name which will replace said attribute in the class, all separated by commas. - Usage: editAttr , , - + - Usage: editAttr , , + ## Relationships - addRel - Accepts a single 'from' class name and relationship type followed by a list of 'to' class names separated by commas and adds these relationships to the database. - Usage: addRel , , , , ... , - Valid relationship types: agg, comp, gen, none + - Usage: addRel , , , , ... , + - Valid relationship types: agg, comp, gen, none - delRel - Accepts a single 'from' class name followed by a list of 'to' class names separated by commas and removes these relationships from the database. - Usage: delRel , , , ... , - + - Usage: delRel , , , ... , + ## Other Commands - help - List available commands with "help" or detailed help with "help cmd". - Usage: help + - Usage: help - exit - Exits the UML shell. - Usage: exit + - Usage: exit - load - Loads the contents of a previously saved diagram into the database. - Usage: load + - Usage: load - save - Saves the contents of the database into a requested file. - Usage: save + - Usage: save - undo - Reverses your last action. Optionally provide amount. - Usage: undo <# of undo's> + - Usage: undo <# of undo's> - redo - Reverse of undo. Will execute undone command again. - Usage: redo + - Usage: redo - web - Starts the web app in the user's default browser. - Usage: web - + - Usage: web + ## Future Commands ### Not implemented at this time, but possibly in the future From f1e49b1db20ac789077af7ea44f6285f602f0581 Mon Sep 17 00:00:00 2001 From: NatureElf Date: Sun, 5 Apr 2020 15:24:08 -0400 Subject: [PATCH 58/59] fixed a minor format issue --- USERGUIDE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/USERGUIDE.md b/USERGUIDE.md index edae181..78261c6 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -5,17 +5,17 @@ - add - Accepts a single class name OR a list separated by commas and adds them to the database. - Usage: add , , ... , + - Usage: add , , ... , - delete - Accepts a single class name OR a list separated by commas and removes them from the database. - Usage: delete , , ... , + - Usage: delete , , ... , - edit - Accepts a single class name followed by a replacement name, separated by commas, and changes instances of old name in database with new name. - Usage: edit , + - Usage: edit , - list - Lists every class in the database. - Usage: list + - Usage: list ## Attributes From 9f1b6805e45ad949326726bb9ce5fe96303db8cd Mon Sep 17 00:00:00 2001 From: Nate Remlinger Date: Sun, 5 Apr 2020 16:21:39 -0400 Subject: [PATCH 59/59] Add documentation to project Saw an issue in documentation (createdb.py) and added some other info I thought was useful --- .coverage | Bin 53248 -> 0 bytes .gitignore | 1 + README.md | 28 ++++++++++++++++--- USERGUIDE.md | 64 +++++++++++++++++++++++++++++++++++++++++++- Useful Commands.txt | 28 ------------------- 5 files changed, 88 insertions(+), 33 deletions(-) delete mode 100644 .coverage delete mode 100644 Useful Commands.txt diff --git a/.coverage b/.coverage deleted file mode 100644 index 6287aaf1090947f66a132fa9c0beed29f8250d08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI4Uu+{s9mjXQ8?T+%**je$>zYP4FUXfRPVA(|sR0Bb%|pdexPRrKrh999lI)%B z_3f_nhYE_Xh(q+T5|2noK%ZK8sVbogA$a2fM4u23^bH|I3Jd3!dU8Q$ zb;v);JFd(>D)Q!2N-q5!#e61z%`D}9kYAYiU2ZG;;>1hFjck)5;6MNbKmY_lASBSc zm@^8ePph}T8rZd_3_QCoz37-`6>hzh|R~rf# zRON|v5>$7+25VpQ>~?)q`g6kW*RbxAo;yKQThDDR1g>a0?Xa3{-w7PIE#!`@cLUit zu!3_8EpY^IxHcr~I#=j=lrt`Ch|VDM0XgI3aprYWuI(mR2N}6&${gz4?zU=#{6@Fc z;l`mHQNe*bD(9%Yfg4x$eYed4Z8@m#zUEAjbBji~x^3rHSN0o!P7HF+h1F=gbAl^D z7VwdYbm6g6szO#Ncl=vT`r&2S-M|g6EoywLLWkTpPG*e4nKSC0TcM#a9;@aAe&3Vb zXEgob5vIfOqwR)!$$lf@Ues&~cRlPfoLfYVL)JXpXBH9|w#OY!xN$<#^aHgvR+e-S zwWeF!Z)>5&_>wvZjRi@v^=`Y)UB%}*)ZH!V*&A{`j*W(e589B(bSl|&VeQN))9IUl zRq1=jd#BSzVR~AZmsxa>@%UHB@Y-zlc<)o{jQe`B=0pyCOHP9WYS0C7^+Um zNEcS7M^z_IXH{ZPrf29zVQNb4MLmMYCH{`>s|1G(y1-o%!TVRgV+Wh!UfnsE6fcgY z3m=;rr8ui#Rrr{G{!%Jb`Hm6NZ_%L7Uu~bt95PV(Gjg}IWjARw>DZ1(U6#h+Xa*3R zuwheTyXJO-(0=(cG*6y;2Kq(<2GMRS`xa3BBzAOHd&00JNY0w4ea zAOHd&00Q?-`9V#aTH;3y7R!s}QhBjdc}!H!FP9gWD<3se+H@h_s6RJf zo?rTss?YRhlSeF`F0L+Dt5h0>pHEo?pn*e;heA9osQM9>o@8B64gMqI!zMcS8KK} z=Qo2^^SVI=tQ~y;;oC*R5}rv@2`h&wfj5u+D!o%6hgG zMntc^kruz5gCt+oN%F!`Np86f*<^*UYDX!y&*2Lx61#L%+FF)Fye*6Ly{M94-u}OD z8;qKb-m9_BJ`&0M|C#*5ig|VNvy(4P{wx1*{sr@u{N9JPo1@+!00JNY0w4eaAOHd& z00JPODA)Dgs(P5U*YM)@fc1aI)_ZIJg#rfaf5XyyD~Bo}T0V|i|EIsG_s$(k{xG89 z_5avYdT-&#B=^_<`jLw5um81cdT;5-v>jUir!MNfV)igLc+dJD@BhQt1_B@e0w4ea zAOHd&00JNY0w4ea$C`kqYD%8>|5fuJ3Vq-}00ck)1V8`;KmY_l00ck)1V8`;jv)a} z%V)#?|C_F2zGLp0zcznne$V`t`4tL)0|5{K0T2KI5C8!X009sH0T2KI5V&6g*^I7c zAIQA*cA=az$oru2*4xiM{m#Fq3u(G4r~kD3m+ha{S^Dd$^5ehn>`l##QN+i`e)8AC z?@v;2X6fXwUR(H4LD7|razfGbIz>OEFaN^&=K0t6KJk!7m&@AiJL;?T^m(Pc^Md+Z zTFHG~E%U)hQS6D-cMBO+&t@O^`Li#-68`_+{H0>POYi^RGyh<|Y5vyy4F$k~00@8p z2!H?xfB*=900@8p2!H?x{2vnF8whG96P^qsJf+j&X>2S!>3Vq5wD6Ql>2wbu-2bQh z2yh?(0w4eaAOHd&00JNY0w4eaAOHf#m;mqpWBq@O8yHms0T2KI5C8!X009sH0T2KI n5C8!p!2kcp`X8nM0w4eaAOHd&00JNY0w4eaAOHf#o4|hnb|Qqk diff --git a/.gitignore b/.gitignore index 0070bf5..6500001 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ app_package/database.db env .vscode **/*.pyc +.coverage \ No newline at end of file diff --git a/README.md b/README.md index 27caa40..0a777d4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# Cowboy Emoji 7 UML Editor +This project is an open source UML editor built using Flask, jsPlumb, SQLAlchemy, and other useful open source projects. Contains a command line interface, as well as a graphical interface that runs inside your browser. This project exists as a passion project for us at Cowboy Emoji 7--passion for the Unified Modeling Language and, to a lesser extent, passion for passing grades and degrees. + # Installation ### Python: @@ -27,8 +30,9 @@ ***Both operations below assume you are inside of the activated enviroment*** -### To create a new Database: -- Type `python3 createdb.py` +### To clear and create a new Database: +- Note: A database is created automatically in running the application if one does not already exist. There is no need to run this command unless you have a database and want a new, empty one. +- Type `python3 cleardb.py` - If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored ### To run the application: @@ -45,11 +49,27 @@ ***Both operations below assume you are inside of the activated enviroment*** -### To create a new Database: -- `python createdb.py` +### To clear and create a new Database: +- Note: A database is created automatically in running the application if one does not already exist. There is no need to run this command unless you have a database and want a new, empty one. +- `python cleardb.py` - If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored ### To run the application: - python run.py - If you would like the web view, type 'web' in the console that appears +# Tests +We have a continuous integration setup that runs our current suite of pytest tests with every push and pull request. + +*pytest tests can be found in /tests/ , feel free to contribute* + +To run tests manually, ensure pytest is installed on your system and run 'pytest' in the top level of the project. + +# Use +See USERGUIDE.md + +# Contribute +See CONTRIBUTING.md and CODE_OF_CONDUCT.md + +# License +See LICENSE and NOTICE.txt diff --git a/USERGUIDE.md b/USERGUIDE.md index 78261c6..9cec8fd 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -1,6 +1,65 @@ +# Installation + +### Python: +- Make sure that python3 is installed on your computer +- Also make sure that python is located as a path in your enviroment variables + +### Install virtualenv: +- Type `pip install virtualenv` + +### Create a virtual environment (only done once): +- Navigate to directory root +- Type `virtualenv env` + +**Here is where different operating systems require different commands.** +- [Windows Installation](#windows) +- [Linux/Mac Installation](#linux-or-mac) + +## LINUX or MAC + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `source env/bin/activate` +- Type `python3 install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To clear and create a new Database: +- Note: A database is created automatically in running the application if one does not already exist. There is no need to run this command unless you have a database and want a new, empty one. +- Type `python3 cleardb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- `python3 run.py` +- If you would like the web view, type `web` in the console that appears + +## WINDOWS + +### Install all dependencies: +- Navigate to the main project folder (*'220 cowboy'*) +- Activate environment. + - Type `env\Scripts\activate` +- Type `python install.py` to install libraries + +***Both operations below assume you are inside of the activated enviroment*** + +### To clear and create a new Database: +- Note: A database is created automatically in running the application if one does not already exist. There is no need to run this command unless you have a database and want a new, empty one. +- `python cleardb.py` +- If a warning containing (*SQLALCHEMY_TRACK_MODIFICATIONS*) is thrown it can be safely ignored + +### To run the application: +- python run.py +- If you would like the web view, type 'web' in the console that appears + # Commands - +## Graphical Interface +A graphical interface exists which allows you to do everything the command line interface does, as well as click and drag classes to give them position and create a useful UML diagram. + +*Use the 'web' command to open this interface type in your browser.* + ## Class - add @@ -71,3 +130,6 @@ - Playback commands from a file: PLAYBACK rose.cmd - record - Save future commands to filename: RECORD rose.cmd + +# Advanced +Database contents are saved and loaded using the JSON file format. It is entirely legal to edit an existing save file with other data and load that data into the system. Just be careful! The JSON must adhere to the specific format of a save file to be properly loaded. \ No newline at end of file diff --git a/Useful Commands.txt b/Useful Commands.txt deleted file mode 100644 index d07803f..0000000 --- a/Useful Commands.txt +++ /dev/null @@ -1,28 +0,0 @@ -Python: - - Make sure that python3 is installed on your computer - - Also make sure that python is located as a path in your enviroment variables - -Install virtualenv: - - Type 'pip install virtualenv' - -Create a virtual environment (only done once): - - Navigate to directory root - - Type 'virtualenv env' - -Install all dependencies: - - Navigate to the main project folder ('220 cowboy') - - Activate environment. - - Navigate to your env\scripts\activate and type 'activate' - - Type 'pip install -r requirements.txt' to install libraries - -To create a new Database: - - In console, navigate to the app folder (2020 cowboy) - - Type python3(linux/mac) or python (windows) - - Type 'from app_package import db' - - Type 'from app_package.models import ClassSchema, SaveSchema' (plus any other models) - - Type 'db.create_all()' - - Finally 'exit()' - -To run the website: - - python3 run.py (linux/mac) / python run.py (windows) - \ No newline at end of file