--- a/src/catedit/__init__.py Fri Jan 02 12:05:47 2015 +0100
+++ b/src/catedit/__init__.py Fri Jan 02 16:46:56 2015 +0100
@@ -2,12 +2,16 @@
__init__.py:
module main file used to configure the Flask app
"""
+
+from logging import FileHandler, Formatter
+
from flask import Flask, session
from flask.ext.github import GitHub
from flask.ext.cache import Cache
-from settings import AppSettings
-from config import AppConfig
-from logging import FileHandler, Formatter
+from flask.ext.restful import Api
+
+from catedit.config import AppConfig
+from catedit.settings import AppSettings
# set up app and database
app = Flask(__name__)
@@ -15,8 +19,30 @@
cache = Cache(app, config={"CACHE_TYPE": "simple"})
app.config.from_object(AppConfig)
+#github
github = GitHub(app)
+#api
+api = Api(app)
+
+
+#views
+from catedit.views import cat_editor, cat_recap, github_login,\
+ github_callback, logout
+
+from catedit.resources import CategoryAPI, CategoryChangesAPI
+
+
+api.add_resource(CategoryAPI,
+ '/category/<string:cat_id>',
+ '/category',
+ endpoint='category')
+api.add_resource(CategoryChangesAPI,
+ '/category-changes/<string:cat_id>',
+ '/category-changes',
+ endpoint='category_changes')
+
+
# set up logging
if app.config["LOGGING"]:
file_handler = FileHandler(filename=app.config["LOG_FILE_PATH"])
--- a/src/catedit/api.py Fri Jan 02 12:05:47 2015 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,350 +0,0 @@
-"""
-api.py:
-contains the api that links the views (views.py) to the model (models.py) and
-its persistence method (persistence.py). As it only trades rdf graphs
-serializations strings, it isn't bound to one specific view
-"""
-
-from rdflib import Graph
-from flask.ext.restful import Resource, Api, reqparse
-from flask import request, session
-from catedit import app, cache
-from catedit.models import Category, CategoryManager
-import catedit.persistence
-from io import StringIO
-
-api = Api(app)
-
-logger = app.logger
-
-cat_parser = reqparse.RequestParser()
-cat_parser.add_argument('label', type=str)
-cat_parser.add_argument('description', type=str)
-cat_parser.add_argument('commit_message', type=str)
-cat_parser.add_argument('property_predicate', type=str, action="append")
-cat_parser.add_argument('property_object', type=str, action="append")
-cat_parser.add_argument('delete_message', type=str)
-
-
-class CategoryAPI(Resource):
- """
- The API to create and edit categories, returns rdf graph serializations
- when successful
- """
- @classmethod
- @cache.memoize(timeout=3600)
- def get(cls, cat_id=None):
- """
- API to get the category of id cat_id, or if cat_id is None,
- get the list of category
- """
- cat_manager_instance = CategoryManager(
- getattr(catedit.persistence, app.config["PERSISTENCE_METHOD"])()
- )
- if cat_id is not None:
- cat = cat_manager_instance.load_category(cat_id)
- return cat.cat_graph.serialize(format='turtle').decode("utf-8")
- else:
- response = []
- for cat in cat_manager_instance.list_categories():
- response.append(cat.cat_graph.serialize(format='turtle')
- .decode("utf-8"))
- return response
-
- # update category cat_id
- @classmethod
- def put(cls, cat_id=None):
- """
- API to edit the category of id cat_id
- * If None and persistence support change sets, will submit all
- changes to category list
- """
- cat_manager_instance = CategoryManager(
- getattr(catedit.persistence, app.config["PERSISTENCE_METHOD"])()
- )
- args = cat_parser.parse_args()
- if (cat_id is None):
- if (cat_manager_instance.persistence.session_compliant is True):
- logger.debug("Submitting - deleted categories are:"
- + str(session.get("deleted_categories", []))
- + " and modified categories are:"
- + str(session.get("modified_categories", [])))
- cat_manager_instance.save_changes(
- deleted_cat_list = session.get("deleted_categories", []),
- modified_cat_list = session.get("modified_categories", []),
- message=args["commit_message"]
- )
- else:
- new_property_list = []
- logger.debug(args["property_predicate"])
- logger.debug(args["property_object"])
- if args["property_predicate"] is not None and \
- args["property_object"] is not None:
- for property_predicate, property_object in zip(
- args["property_predicate"],
- args["property_object"]):
- if property_object:
- property_object_to_append = property_object
- # if URIRef category, we must prefix id with namespace
- if (app.config["PROPERTY_LIST"]
- [property_predicate]
- ["object_type"]) == "uriref-category":
- property_object_to_append = \
- app.config["CATEGORY_NAMESPACE"] \
- + property_object
- logger.debug(property_object_to_append)
- new_property_list.append((property_predicate,
- property_object_to_append))
- logger.debug(new_property_list)
-
- # is the edition occuring on an already modified category?
- if cat_id in [category["name"] for category
- in session.get("modified_categories", [])]:
- for element in session.get("modified_categories", []):
- if element["name"] == cat_id:
- cat_graph = Graph()
- cat_graph.parse(
- source=StringIO(element["content"]),
- format="turtle"
- )
- cat = Category(graph=cat_graph)
- else:
- cat = cat_manager_instance.load_category(cat_id)
-
- cat.edit_category(new_description=args["description"],
- new_label=args["label"],
- new_other_properties=new_property_list)
-
- session["modified_categories"][:] = [
- elt for elt in session.get("modified_categories", [])
- if elt["name"] != cat.cat_id
- ]
- session["modified_categories"].append(
- {"name": cat.cat_id,
- "content": str(
- cat.cat_graph.serialize(format="turtle"), "utf-8"
- )}
- )
-
- # Now we must clean the deleted categories list in case the
- # modified category was deleted before being edited
- for element in session.get("deleted_categories", []):
- if element["name"] == cat.cat_id:
- session["deleted_categories"].remove(element)
-
- logger.debug("put id: "+cat.cat_id)
- cache.clear()
- return 204
-
- @classmethod
- def post(cls):
- """
- API to create a new category
- """
- args = cat_parser.parse_args()
- property_list = []
- logger.debug(args["property_predicate"])
- logger.debug(args["property_object"])
- for property_predicate, property_object in zip(
- request.form.getlist('property_predicate'),
- request.form.getlist('property_object')):
- if property_object:
- if (app.config["PROPERTY_LIST"]
- [property_predicate]
- ["object_type"]) == "uriref-category":
- property_list.append((property_predicate,
- app.config["CATEGORY_NAMESPACE"]
- + property_object))
- else:
- property_list.append((property_predicate,
- property_object))
- logger.debug(property_list)
- cat = Category(label=args["label"],
- description=args["description"],
- other_properties=property_list)
-
- session["modified_categories"][:] = [
- elt for elt in session.get("modified_categories", [])
- if elt["name"] != cat.cat_id
- ]
- session["modified_categories"].append(
- {"name": cat.cat_id,
- "content": str(
- cat.cat_graph.serialize(format="turtle"), "utf-8"
- )}
- )
-
- logger.debug("post id: "+cat.cat_id)
- cache.clear()
- return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 201
-
- @classmethod
- def delete(cls, deleted_cat_id):
- """
- API to delete the category of id cat_id or restore it from the
- deletion list
- """
- args = cat_parser.parse_args()
- if (deleted_cat_id in [
- element["name"] for element in session.get("deleted_categories", [])
- ]):
- session["deleted_categories"].remove({"name": deleted_cat_id})
- # warning, not safe if 2 files share the same name (or category id)
- # but that shouldn't happen
- else:
- session["deleted_categories"].append({"name": deleted_cat_id})
- # now we must clean the modified categories list in case the
- # deleted category was modified before
- for element in session.get("modified_categories", []):
- if element["name"] == deleted_cat_id:
- session["modified_categories"].remove(element)
-
- # Now we also have to clean up categories that reference the
- # deleted category
- cat_manager_instance = CategoryManager(
- getattr(
- catedit.persistence, app.config["PERSISTENCE_METHOD"]
- )()
- )
- cat_list = cat_manager_instance.list_categories()
- # first we edit what was already modified before the deletion
- logger.debug(session["modified_categories"])
- element_list = list(session.get("modified_categories", []))
- if deleted_cat_id in [element["name"] for
- element in session.get("deleted_categories",
- [])]:
- for element in element_list:
- logger.debug(str(element))
- modified_cat_graph = Graph()
- modified_cat_graph.parse(
- source=StringIO(element["content"]),
- format="turtle"
- )
- modified_cat = Category(graph=modified_cat_graph)
- if (modified_cat.cat_id !=
- app.config["CATEGORY_NAMESPACE"] + deleted_cat_id):
- new_property_list = []
- for (predicate, obj) in modified_cat.properties:
- if not (
- (app.config["PROPERTY_LIST"]
- [predicate]
- ["object_type"] == "uriref-category")
- and
- (obj == (app.config["CATEGORY_NAMESPACE"] +
- deleted_cat_id))
- ):
- new_property_list.append((predicate, obj))
-
- if new_property_list != modified_cat.properties:
- logger.debug("Modifying modified category")
- modified_cat.edit_category(
- new_other_properties=new_property_list
- )
- session["modified_categories"][:] = [
- elt for elt in session.get(
- "modified_categories", []
- )
- if elt["name"] != modified_cat.cat_id
- ]
- session["modified_categories"].append(
- {"name": modified_cat.cat_id,
- "content": str(
- modified_cat.cat_graph
- .serialize(format="turtle"),
- "utf-8"
- )}
- )
- # now we check if an unmodified category reference the deleted
- # category
- for cat in cat_list:
- if cat.cat_id not in [
- element["name"] for element in
- session.get("modified_categories", [])
- ] and cat.cat_id not in [
- element["name"] for element in
- session.get("deleted_categories", [])
- ]:
- new_property_list = []
- for (predicate, obj) in cat.properties:
- if not (
- (app.config["PROPERTY_LIST"]
- [predicate]
- ["object_type"] == "uriref-category")
- and
- (obj == (app.config["CATEGORY_NAMESPACE"] +
- deleted_cat_id))
- ):
- new_property_list.append((predicate, obj))
-
- if new_property_list != cat.properties:
- logger.debug("Modifying untouched category")
- cat.edit_category(
- new_other_properties=new_property_list
- )
- session["modified_categories"][:] = [
- elt for elt in session.get(
- "modified_categories", []
- )
- if elt["name"] != cat.cat_id
- ]
- session["modified_categories"].append(
- {"name": cat.cat_id,
- "content": str(
- cat.cat_graph.serialize(format="turtle"),
- "utf-8"
- )}
- )
-
- logger.debug("delete id: " + deleted_cat_id)
- cache.clear()
- return 204
-
-class CategoryChangesAPI(Resource):
- """
- API for getting and deleting category changes, returns a dict when
- succesful if category is a modified one, returns only the cat_id if it
- is a deleted one
-
- All changes and deletions are saved in session["modified_categories"]
- and session["deleted_categories"]
- """
- @classmethod
- def get(cls, modified_cat_id=None):
- """
- API to get the pending changes for category cat_id
- """
- logger.debug(modified_cat_id)
- logger.debug(session.get("modified_categories", []))
- if modified_cat_id is None:
- return {
- "modified_categories": session.get("modified_categories", []),
- "deleted_categories": session.get("deleted_categories", [])
- }, 201
- else:
- for category in session.get("modified_categories", []):
- logger.debug(category)
- if category["name"] == modified_cat_id:
- return { "type": "modified", "category": category }, 201
- for category in session.get("deleted_categories", []):
- logger.debug(category)
- if category["name"] == modified_cat_id:
- return { "type": "deleted", "category": category }, 201
- return 404
-
- def delete(cls, cat_id=None):
- """
- API to delete the category cat_id from the changelist or if cat_id
- is None, delete the whole changelist
- """
- session["modified_categories"] = []
- session["deleted_categories"] = []
- return 204
-
-api.add_resource(CategoryAPI,
- '/category/<string:cat_id>',
- '/category',
- endpoint='category')
-api.add_resource(CategoryChangesAPI,
- '/category-changes/<string:cat_id>',
- '/category-changes',
- endpoint='category_changes')
--- a/src/catedit/main.py Fri Jan 02 12:05:47 2015 +0100
+++ b/src/catedit/main.py Fri Jan 02 16:46:56 2015 +0100
@@ -3,9 +3,6 @@
script that is used to boot up the application
"""
from catedit import app
-from catedit.api import api
-from catedit.views import cat_editor, cat_recap, github_login, \
- github_callback, logout
if __name__ == '__main__':
app.run(host=app.config["HOST"])
--- a/src/catedit/models.py Fri Jan 02 12:05:47 2015 +0100
+++ b/src/catedit/models.py Fri Jan 02 16:46:56 2015 +0100
@@ -9,10 +9,10 @@
from uuid import uuid4
from io import StringIO
from slugify import slugify
+import logging
from catedit import app
-
-logger = app.logger
+logger = logging.getLogger(__name__)
class Category(object):
--- a/src/catedit/persistence.py Fri Jan 02 12:05:47 2015 +0100
+++ b/src/catedit/persistence.py Fri Jan 02 16:46:56 2015 +0100
@@ -9,9 +9,8 @@
for categories)
"""
from abc import ABCMeta, abstractmethod
-from catedit import app, github
+from catedit import github, app
from base64 import b64decode
-from flask import session
from flask.ext.github import GitHubError
import os
import json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/resources.py Fri Jan 02 16:46:56 2015 +0100
@@ -0,0 +1,336 @@
+"""
+api.py:
+contains the api that links the views (views.py) to the model (models.py) and
+its persistence method (persistence.py). As it only trades rdf graphs
+serializations strings, it isn't bound to one specific view
+"""
+
+
+from rdflib import Graph
+from flask.ext.cache import Cache
+from flask.ext.restful import Resource, reqparse
+from flask import request, session
+from catedit import app, cache
+from catedit.models import Category, CategoryManager
+import catedit.persistence
+from io import StringIO
+import logging
+
+logger = app.logger
+
+cat_parser = reqparse.RequestParser()
+cat_parser.add_argument('label', type=str)
+cat_parser.add_argument('description', type=str)
+cat_parser.add_argument('commit_message', type=str)
+cat_parser.add_argument('property_predicate', type=str, action="append")
+cat_parser.add_argument('property_object', type=str, action="append")
+cat_parser.add_argument('delete_message', type=str)
+
+
+class CategoryAPI(Resource):
+ """
+ The API to create and edit categories, returns rdf graph serializations
+ when successful
+ """
+ @cache.memoize(timeout=3600)
+ def get(self, cat_id=None):
+ """
+ API to get the category of id cat_id, or if cat_id is None,
+ get the list of category
+ """
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence, app.config["PERSISTENCE_METHOD"])()
+ )
+ if cat_id is not None:
+ cat = cat_manager_instance.load_category(cat_id)
+ return cat.cat_graph.serialize(format='turtle').decode("utf-8")
+ else:
+ response = []
+ for cat in cat_manager_instance.list_categories():
+ response.append(cat.cat_graph.serialize(format='turtle')
+ .decode("utf-8"))
+ return response
+
+ # update category cat_id
+ def put(self, cat_id=None):
+ """
+ API to edit the category of id cat_id
+ * If None and persistence support change sets, will submit all
+ changes to category list
+ """
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence, app.config["PERSISTENCE_METHOD"])()
+ )
+ args = cat_parser.parse_args()
+ if (cat_id is None):
+ if (cat_manager_instance.persistence.session_compliant is True):
+ logger.debug("Submitting - deleted categories are:"
+ + str(session.get("deleted_categories", []))
+ + " and modified categories are:"
+ + str(session.get("modified_categories", [])))
+ cat_manager_instance.save_changes(
+ deleted_cat_list = session.get("deleted_categories", []),
+ modified_cat_list = session.get("modified_categories", []),
+ message=args["commit_message"]
+ )
+ else:
+ new_property_list = []
+ logger.debug(args["property_predicate"])
+ logger.debug(args["property_object"])
+ if args["property_predicate"] is not None and \
+ args["property_object"] is not None:
+ for property_predicate, property_object in zip(
+ args["property_predicate"],
+ args["property_object"]):
+ if property_object:
+ property_object_to_append = property_object
+ # if URIRef category, we must prefix id with namespace
+ if (app.config["PROPERTY_LIST"]
+ [property_predicate]
+ ["object_type"]) == "uriref-category":
+ property_object_to_append = \
+ app.config["CATEGORY_NAMESPACE"] \
+ + property_object
+ logger.debug(property_object_to_append)
+ new_property_list.append((property_predicate,
+ property_object_to_append))
+ logger.debug(new_property_list)
+
+ # is the edition occuring on an already modified category?
+ if cat_id in [category["name"] for category
+ in session.get("modified_categories", [])]:
+ for element in session.get("modified_categories", []):
+ if element["name"] == cat_id:
+ cat_graph = Graph()
+ cat_graph.parse(
+ source=StringIO(element["content"]),
+ format="turtle"
+ )
+ cat = Category(graph=cat_graph)
+ else:
+ cat = cat_manager_instance.load_category(cat_id)
+
+ cat.edit_category(new_description=args["description"],
+ new_label=args["label"],
+ new_other_properties=new_property_list)
+
+ session["modified_categories"][:] = [
+ elt for elt in session.get("modified_categories", [])
+ if elt["name"] != cat.cat_id
+ ]
+ session["modified_categories"].append(
+ {"name": cat.cat_id,
+ "content": str(
+ cat.cat_graph.serialize(format="turtle"), "utf-8"
+ )}
+ )
+
+ # Now we must clean the deleted categories list in case the
+ # modified category was deleted before being edited
+ for element in session.get("deleted_categories", []):
+ if element["name"] == cat.cat_id:
+ session["deleted_categories"].remove(element)
+
+ logger.debug("put id: "+cat.cat_id)
+ cache.clear()
+ return 204
+
+ def post(self):
+ """
+ API to create a new category
+ """
+ args = cat_parser.parse_args()
+ property_list = []
+ logger.debug(args["property_predicate"])
+ logger.debug(args["property_object"])
+ for property_predicate, property_object in zip(
+ request.form.getlist('property_predicate'),
+ request.form.getlist('property_object')):
+ if property_object:
+ if (app.config["PROPERTY_LIST"]
+ [property_predicate]
+ ["object_type"]) == "uriref-category":
+ property_list.append((property_predicate,
+ app.config["CATEGORY_NAMESPACE"]
+ + property_object))
+ else:
+ property_list.append((property_predicate,
+ property_object))
+ logger.debug(property_list)
+ cat = Category(label=args["label"],
+ description=args["description"],
+ other_properties=property_list)
+
+ session["modified_categories"][:] = [
+ elt for elt in session.get("modified_categories", [])
+ if elt["name"] != cat.cat_id
+ ]
+ session["modified_categories"].append(
+ {"name": cat.cat_id,
+ "content": str(
+ cat.cat_graph.serialize(format="turtle"), "utf-8"
+ )}
+ )
+
+ logger.debug("post id: "+cat.cat_id)
+ cache.clear()
+ return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 201
+
+ def delete(self, deleted_cat_id):
+ """
+ API to delete the category of id cat_id or restore it from the
+ deletion list
+ """
+ if (deleted_cat_id in [
+ element["name"] for element in session.get("deleted_categories", [])
+ ]):
+ session["deleted_categories"].remove({"name": deleted_cat_id})
+ # warning, not safe if 2 files share the same name (or category id)
+ # but that shouldn't happen
+ else:
+ session["deleted_categories"].append({"name": deleted_cat_id})
+ # now we must clean the modified categories list in case the
+ # deleted category was modified before
+ for element in session.get("modified_categories", []):
+ if element["name"] == deleted_cat_id:
+ session["modified_categories"].remove(element)
+
+ # Now we also have to clean up categories that reference the
+ # deleted category
+ cat_manager_instance = CategoryManager(
+ getattr(
+ catedit.persistence, app.config["PERSISTENCE_METHOD"]
+ )()
+ )
+ cat_list = cat_manager_instance.list_categories()
+ # first we edit what was already modified before the deletion
+ logger.debug(session["modified_categories"])
+ element_list = list(session.get("modified_categories", []))
+ if deleted_cat_id in [element["name"] for
+ element in session.get("deleted_categories",
+ [])]:
+ for element in element_list:
+ logger.debug(str(element))
+ modified_cat_graph = Graph()
+ modified_cat_graph.parse(
+ source=StringIO(element["content"]),
+ format="turtle"
+ )
+ modified_cat = Category(graph=modified_cat_graph)
+ if (modified_cat.cat_id !=
+ app.config["CATEGORY_NAMESPACE"] + deleted_cat_id):
+ new_property_list = []
+ for (predicate, obj) in modified_cat.properties:
+ if not (
+ (app.config["PROPERTY_LIST"]
+ [predicate]
+ ["object_type"] == "uriref-category")
+ and
+ (obj == (app.config["CATEGORY_NAMESPACE"] +
+ deleted_cat_id))
+ ):
+ new_property_list.append((predicate, obj))
+
+ if new_property_list != modified_cat.properties:
+ logger.debug("Modifying modified category")
+ modified_cat.edit_category(
+ new_other_properties=new_property_list
+ )
+ session["modified_categories"][:] = [
+ elt for elt in session.get(
+ "modified_categories", []
+ )
+ if elt["name"] != modified_cat.cat_id
+ ]
+ session["modified_categories"].append(
+ {"name": modified_cat.cat_id,
+ "content": str(
+ modified_cat.cat_graph
+ .serialize(format="turtle"),
+ "utf-8"
+ )}
+ )
+ # now we check if an unmodified category reference the deleted
+ # category
+ for cat in cat_list:
+ if cat.cat_id not in [
+ element["name"] for element in
+ session.get("modified_categories", [])
+ ] and cat.cat_id not in [
+ element["name"] for element in
+ session.get("deleted_categories", [])
+ ]:
+ new_property_list = []
+ for (predicate, obj) in cat.properties:
+ if not (
+ (app.config["PROPERTY_LIST"]
+ [predicate]
+ ["object_type"] == "uriref-category")
+ and
+ (obj == (app.config["CATEGORY_NAMESPACE"] +
+ deleted_cat_id))
+ ):
+ new_property_list.append((predicate, obj))
+
+ if new_property_list != cat.properties:
+ logger.debug("Modifying untouched category")
+ cat.edit_category(
+ new_other_properties=new_property_list
+ )
+ session["modified_categories"][:] = [
+ elt for elt in session.get(
+ "modified_categories", []
+ )
+ if elt["name"] != cat.cat_id
+ ]
+ session["modified_categories"].append(
+ {"name": cat.cat_id,
+ "content": str(
+ cat.cat_graph.serialize(format="turtle"),
+ "utf-8"
+ )}
+ )
+
+ logger.debug("delete id: " + deleted_cat_id)
+ cache.clear()
+ return 204
+
+class CategoryChangesAPI(Resource):
+ """
+ API for getting and deleting category changes, returns a dict when
+ succesful if category is a modified one, returns only the cat_id if it
+ is a deleted one
+
+ All changes and deletions are saved in session["modified_categories"]
+ and session["deleted_categories"]
+ """
+ def get(self, modified_cat_id=None):
+ """
+ API to get the pending changes for category cat_id
+ """
+ logger.debug(modified_cat_id)
+ logger.debug(session.get("modified_categories", []))
+ if modified_cat_id is None:
+ return {
+ "modified_categories": session.get("modified_categories", []),
+ "deleted_categories": session.get("deleted_categories", [])
+ }, 201
+ else:
+ for category in session.get("modified_categories", []):
+ logger.debug(category)
+ if category["name"] == modified_cat_id:
+ return { "type": "modified", "category": category }, 201
+ for category in session.get("deleted_categories", []):
+ logger.debug(category)
+ if category["name"] == modified_cat_id:
+ return { "type": "deleted", "category": category }, 201
+ return 404
+
+ def delete(self, cat_id=None):
+ """
+ API to delete the category cat_id from the changelist or if cat_id
+ is None, delete the whole changelist
+ """
+ session["modified_categories"] = []
+ session["deleted_categories"] = []
+ return 204
--- a/src/catedit/views.py Fri Jan 02 12:05:47 2015 +0100
+++ b/src/catedit/views.py Fri Jan 02 16:46:56 2015 +0100
@@ -9,7 +9,7 @@
from flask import render_template, request, redirect, url_for, session
from flask.ext.github import GitHubError
from flask_wtf import Form
-from catedit.api import CategoryAPI, CategoryChangesAPI
+from catedit.resources import CategoryAPI, CategoryChangesAPI
from wtforms import StringField, TextAreaField
from wtforms.validators import DataRequired
from rdflib import Graph