# HG changeset patch # User Nicolas DURAND # Date 1422617895 -3600 # Node ID 8c32ea41b39189612bbb4865b93559845db00355 # Parent 67d2ddbebf7e771deaa986fa09c867fbe6898dac New version + Reworked login process to suppress unauthenticated request + reworked file persistence + added tests module and API tests + reworked how the list repositories user can access is generated (can now only access repositories that are both in his repository list AND in the config repository list, so there is no need to add every new user to all repositories) diff -r 67d2ddbebf7e -r 8c32ea41b391 setup.py --- a/setup.py Tue Jan 13 10:43:26 2015 +0100 +++ b/setup.py Fri Jan 30 12:38:15 2015 +0100 @@ -2,7 +2,7 @@ # Will set CURRENT_VERSION to the current version string and VERSION to the # current version tuple -exec(compile(open("src/catedit/version.py"), "version.py", "exec")) +exec(compile(open("src/catedit/version.py").read(), "version.py", "exec")) setup( name='catedit', diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/__init__.py --- a/src/catedit/__init__.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/__init__.py Fri Jan 30 12:38:15 2015 +0100 @@ -38,10 +38,14 @@ CsrfProtect(app) # Github -app.config["GITHUB_CLIENT_ID"] = app.config["PERSISTENCE_CONFIG"] \ - ["GITHUB_CLIENT_ID"] -app.config["GITHUB_CLIENT_SECRET"] = app.config["PERSISTENCE_CONFIG"] \ - ["GITHUB_CLIENT_SECRET"] +app.config["GITHUB_CLIENT_ID"] = app.config["PERSISTENCE_CONFIG"].get( + "GITHUB_CLIENT_ID", + "local_persistence" +) +app.config["GITHUB_CLIENT_SECRET"] = app.config["PERSISTENCE_CONFIG"].get( + "GITHUB_CLIENT_SECRET", + "local_persistence" +) github = GitHub(app) # Api diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/catedit_tests.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/catedit/catedit_tests.py Fri Jan 30 12:38:15 2015 +0100 @@ -0,0 +1,265 @@ +import catedit +import unittest +from catedit.models import Category +from rdflib import Graph +from io import StringIO +from flask import session + +class CateditTestCase(unittest.TestCase): + + def setUp(self): + catedit.app.config['TESTING'] = True + catedit.app.config.from_object(catedit.settings.AppSettings) + catedit.app.config['PERSISTENCE_CONFIG'] = { + "METHOD": "PersistenceToFile", + "FILE_SAVE_DIRECTORY": "../../files/", + "REPOSITORY_LIST": ["local"] + } + self.app = catedit.app.test_client() + self.category_api = catedit.resources.CategoryAPI() + self.category_changes_api = catedit.resources.CategoryChangesAPI() + + def tearDown(self): + pass + + def test_1_1_model_create_category(self): + pass + + def test_1_2_model_access_data(self): + pass + + def test_1_3_model_editing_category(self): + pass + + def test_1_4_model_save_load(self): + pass + + def test_2_1_api_empty(self): + with catedit.app.test_request_context(): + session["modified_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + session["deleted_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + print("Testing empty category list") + assert self.category_api.get(repository="local") == ([], 200) + print("Testing empty category changes list") + assert self.category_changes_api.get(repository="local") == ( + { + "modified_categories": {}, + "deleted_categories": {} + }, + 200 + ) + print("Testing getting dummy category id") + assert self.category_api.get( + repository="local", + cat_id="nil" + ) == 404 + print("Testing getting dummy category change id") + assert self.category_changes_api.get( + repository="local", + modified_cat_id="nil" + ) == ({ + "type": "untouched", + "category": "nil" + }, 200) + + def test_2_2_api_creation(self): + with catedit.app.test_request_context(): + session["modified_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + session["deleted_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + + print("Testing posting new category in changelist") + cat_data = { + "label": "new_label", + "description": "new_description", + "properties": [], + } + (cat_id, cat_serial, code) = self.category_api.post( + repository="local", + cat_data=cat_data + ) + cat_graph = Graph() + cat_graph.parse(source=StringIO(cat_serial), format='turtle') + cat_object = Category(graph=cat_graph) + assert cat_object.label == "new_label" + assert cat_object.description == "new_description" + assert cat_object.properties == [] + assert code == 201 + print("Checking if new category can be found in changelist") + assert self.category_changes_api.get( + repository="local", + modified_cat_id=cat_id + ) != 404 + + def test_2_3_api_edit_changes(self): + with catedit.app.test_request_context(): + session["modified_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + session["deleted_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + + print("Creating new category changes that will be edited") + cat_data = { + "label": "new_label", + "description": "new_description", + "properties": [], + } + (cat_id, cat_serial, code) = self.category_api.post( + repository="local", + cat_data=cat_data + ) + assert code == 201 + + print("Testing editing new category") + edited_cat_data = { + "label": "new_label_edited", + "description": "new_description_edited", + "properties": [("comment","edited_category")], + } + code = self.category_api.put( + repository="local", + cat_id=cat_id, + cat_data=edited_cat_data + ) + assert code == 204 + + (cat_changes, code) = self.category_changes_api.get( + repository="local", + modified_cat_id=cat_id + ) + + cat_graph = Graph() + cat_graph.parse( + source=StringIO(cat_changes["category"][cat_id]), + format='turtle' + ) + cat_object = Category(graph=cat_graph) + assert cat_object.label == "new_label_edited" + assert cat_object.description == "new_description_edited" + assert cat_object.properties == [("comment","edited_category")] + + def test_2_4_api_delete_changes(self): + with catedit.app.test_request_context(): + session["modified_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + session["deleted_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + + print("Creating new category changes that will be deleted") + cat_data = { + "label": "new_label", + "description": "new_description", + "properties": [], + } + (cat_id, cat_serial, code) = self.category_api.post( + repository="local", + cat_data=cat_data + ) + assert session["modified_categories"]["local"] != {} + assert code == 201 + + print("Deleting changes for created category") + code = self.category_changes_api.delete( + repository="local", + modified_cat_id=cat_id + ) + assert code == 204 + assert session["modified_categories"]["local"] == {} + assert session["deleted_categories"]["local"] == {} + + def test_2_5_api_submit_edit_delete_category(self): + with catedit.app.test_request_context(): + session["modified_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + session["deleted_categories"] = { + repo: {} for repo in catedit.app.config["PERSISTENCE_CONFIG"] + ["REPOSITORY_LIST"] + } + + print("Creating new categories") + cat_data_1 = { + "label": "new_label_1", + "description": "new_description_1", + "properties": [], + } + (cat_id_1, cat_serial_1, code) = self.category_api.post( + repository="local", + cat_data=cat_data_1 + ) + assert code == 201 + + cat_data_2 = { + "label": "new_label_2", + "description": "new_description_2", + "properties": [], + } + (cat_id_2, cat_serial_2, code) = self.category_api.post( + repository="local", + cat_data=cat_data_2 + ) + assert code == 201 + + print("Submitting changes") + code = self.category_api.put( + repository="local" + ) + assert code == 204 + + print("Deleting categories") + (categories, code) = self.category_api.get( + repository="local" + ) + for category_content in categories: + cat_graph = Graph() + cat_graph.parse( + source=StringIO(category_content), + format='turtle' + ) + cat_object = Category(graph=cat_graph) + + assert cat_object.label == "new_label_1" or \ + cat_object.label == "new_label_2" + assert cat_object.description == "new_description_1" or \ + cat_object.description == "new_description_2" + code = self.category_api.delete( + repository="local", + deleted_cat_id=cat_object.cat_id + ) + assert code == 204 + assert session["deleted_categories"] != {} + + print("Submitting deletions") + code = self.category_api.put( + repository="local" + ) + assert code == 204 + assert session["modified_categories"]["local"] == {} + assert session["deleted_categories"]["local"] == {} + + def test_3_views(self): + print("Starting views.py tests...") + print("Completed views.py tests!") + +if __name__ == '__main__': + unittest.main() diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/config.py.tmpl --- a/src/catedit/config.py.tmpl Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/config.py.tmpl Fri Jan 30 12:38:15 2015 +0100 @@ -38,7 +38,8 @@ You then need additional parameters related to the chosen METHOD. If "PersistenceToFile" (Note: currently not supported) - * FILE_SAVE_PATH is the path to save the category files + * FILE_SAVE_DIRECTORY is the path to save the category files + * REPOSITORY_LIST should be set to ["local"] If "PersistenceToGithub" * REPOSITORY_LIST is the list of repository available for users (Note: diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/models.py --- a/src/catedit/models.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/models.py Fri Jan 30 12:38:15 2015 +0100 @@ -35,7 +35,7 @@ other_properties=None, graph=None): if not graph: # cat_id = .hex - Alternate method of generating ids - cat_id = str(uuid4())[:8]+"_"+slugify(label) + cat_id = slugify(label)+"_"+str(uuid4())[:8] self.cat_graph = Graph() self.this_category = URIRef(app.config["CATEGORY_NAMESPACE"] + cat_id) @@ -212,9 +212,11 @@ Loads a category from its id """ cat_serial = self.persistence.load(name=cat_id) - loaded_cat_graph = Graph() - loaded_cat_graph.parse(source=StringIO(cat_serial), format='turtle') - cat = Category(graph=loaded_cat_graph) + cat = None + if cat_serial != "": + loaded_cat_graph = Graph() + loaded_cat_graph.parse(source=StringIO(cat_serial), format='turtle') + cat = Category(graph=loaded_cat_graph) return cat def save_changes(self, diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/persistence.py --- a/src/catedit/persistence.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/persistence.py Fri Jan 30 12:38:15 2015 +0100 @@ -69,22 +69,41 @@ * name : name of the file to write in/read from * content : desired content of the file when writing """ + def __init__(self, **kwargs): + pass + @property def session_compliant(self): """ Not session compliant: each modification is submitted """ - return False + return True def save(self, **kwargs): """ Saves to a file + + Expected args: + * deletion_dict + * modification_dict """ - path_to_save = app.config["PERSISTENCE_CONFIG"]["FILE_SAVE_DIRECTORY"] \ - + kwargs["name"] - file_to_save = open(path_to_save, 'wb') - file_to_save.write(kwargs["content"]) - file_to_save.close() + modification_dict = kwargs["modification_dict"] + deletion_dict = kwargs["deletion_dict"] + + for (file_name, file_content) in modification_dict.items(): + path_to_save = app.config["PERSISTENCE_CONFIG"] \ + ["FILE_SAVE_DIRECTORY"] \ + + file_name + print(path_to_save) + file_to_save = open(path_to_save, 'wb') + file_to_save.write(bytes(file_content, "utf-8")) + file_to_save.close() + + for file_name in deletion_dict.keys(): + path_to_delete = app.config["PERSISTENCE_CONFIG"] \ + ["FILE_SAVE_DIRECTORY"] \ + + file_name + os.remove(path_to_delete) def load(self, **kwargs): """ @@ -92,9 +111,13 @@ """ path_to_load = app.config["PERSISTENCE_CONFIG"]["FILE_SAVE_DIRECTORY"] \ + kwargs["name"] - file_to_load = open(path_to_load, 'rb') - file_content = file_to_load.read() - file_to_load.close() + try: + file_to_load = open(path_to_load, 'rb') + file_content = str(file_to_load.read(), "utf-8") + print(file_content) + file_to_load.close() + except FileNotFoundError: + file_content="" return file_content def delete(self, **kwargs): @@ -115,10 +138,11 @@ ["FILE_SAVE_DIRECTORY"]): if not file_name or file_name[0] == ".": continue - path_to_load = open(app.config["PERSISTENCE_CONFIG"] - ["FILE_SAVE_DIRECTORY"] + file_name) - file_content = path_to_load.read() - path_to_load.close() + path_to_load = app.config["PERSISTENCE_CONFIG"] \ + ["FILE_SAVE_DIRECTORY"] + file_name + file_to_load = open(path_to_load, 'rb') + file_content = str(file_to_load.read(), "utf-8") + file_to_load.close() file_content_list.append(file_content) # logger.debug(file_content_list) return file_content_list @@ -200,16 +224,16 @@ ) logger.debug(str(ref_master)) except GitHubError as ghe: - logger.debug("GitHubError trying to get the reference " + logger.error("GitHubError trying to get the reference " + "to the master branch") - logger.debug( + logger.error( "Endpoint: " + "repos/" + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_OWNER"] + "/" + self.repository + "/git/refs/heads/master" ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) # point 2 @@ -223,9 +247,9 @@ ) logger.debug(str(last_commit_master)) except GitHubError as ghe: - logger.debug("GitHubError trying to get the commit associated " + logger.error("GitHubError trying to get the commit associated " + "to the latest reference to the master branch") - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) # Point 3 @@ -240,10 +264,10 @@ ) logger.debug(str(last_commit_tree)) except GitHubError as ghe: - logger.debug("GitHubError trying to get the tree from the commit " + logger.error("GitHubError trying to get the tree from the commit " + "associated to the latest reference to the master " + "branch") - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) # Point 4 @@ -300,12 +324,12 @@ blob_sha = new_blob["sha"] break except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to post a new" + "blob with following data: " + str(new_blob_data) ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str( github.get("rate_limit")["resources"] )) @@ -344,12 +368,12 @@ data=new_blob_data ) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to post a new blob with following" + "data: " + str(new_blob_data) ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) new_tree_data["tree"].append({ "path": app.config["PERSISTENCE_CONFIG"] @@ -370,11 +394,11 @@ data=new_tree_data ) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to post a new tree with following data: " + str(new_tree_data) ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) # Point 5 new_commit_data = {"message": kwargs["message"], @@ -391,11 +415,11 @@ ) logger.debug(str(new_commit)) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to post a new commit with following data: " + str(new_commit_data) ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) # Point 6 @@ -411,12 +435,12 @@ ) logger.debug(str(new_head)) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to edit the head to the master branch" + "with the following data: " + str(new_head_data) ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) @@ -424,6 +448,7 @@ """ Loads from a Github repository """ + file_content="" try: filedict = github.get("repos/" + app.config["PERSISTENCE_CONFIG"] @@ -435,10 +460,11 @@ + kwargs["name"]) file_content = str(b64decode(filedict["content"]), "utf-8") except GitHubError as ghe: - logger.debug("Github Error trying to get file: "+kwargs["name"]) - logger.debug("Github sent an error, if 404, either you may not " - + "have access to the repository or it doesn't exist ") - logger.debug(ghe.response.text) + logger.error("Github Error trying to get file: "+kwargs["name"]) + logger.error("Github sent an error, if 404, either you may not " + + "have access to the repository or the file doesn't " + + "exist ") + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) return file_content @@ -449,7 +475,6 @@ Expected kwargs are: * name : the name of the file to delete * message : the commit message for the deletion - """ pass @@ -470,12 +495,11 @@ for github_file in files_in_repo] # logger.debug(filenames_list) except GitHubError as ghe: - logger.debug("Github Error trying to get the file list in the " + logger.error("Github Error trying to get the file list in the " + "category repository") - logger.debug("NOTE: Github sent an error, if 404 either you " - + "may not have access to the repository or it " - + "doesn't exist or there isn't any files in it") - logger.debug(ghe.response.text) + logger.error("IMPORTANT: This message can mean there is no " + + "category in the repository " + self.repository) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) file_content_list = [] @@ -492,8 +516,8 @@ file_content_list.append(str(b64decode(filedict["content"]), "utf-8")) except GitHubError as ghe: - logger.debug("Github Error trying to get file: "+filename) - logger.debug(ghe.response.text) + logger.error("Github Error trying to get file: "+filename) + logger.error(ghe.response.text) logger.debug(str(github.get("rate_limit")["resources"])) # logger.debug(file_content_list) return file_content_list diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/resources.py --- a/src/catedit/resources.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/resources.py Fri Jan 30 12:38:15 2015 +0100 @@ -44,13 +44,19 @@ ) if cat_id is not None: cat = cat_manager_instance.load_category(cat_id) - return cat.cat_graph.serialize(format='turtle').decode("utf-8") + if cat != None: + return cat.cat_graph \ + .serialize(format='turtle') \ + .decode("utf-8"), \ + 200 + else: + return 404 else: response = [] for cat in cat_manager_instance.list_categories(): response.append(cat.cat_graph.serialize(format='turtle') .decode("utf-8")) - return response + return response, 200 # update category cat_id def put(self, repository, cat_id=None, cat_data=None): @@ -93,6 +99,10 @@ .get(repository, {}), message=args["commit_message"] ) + session["deleted_categories"]["local"] = {} + session["modified_categories"]["local"] = {} + cache.clear() + return 204 else: # is the edition occuring on an already modified category? if cat_id in session.get("modified_categories", {}) \ @@ -123,9 +133,7 @@ in session["deleted_categories"][repository].items() if cat_name != cat.cat_id } - logger.debug("put id: "+cat.cat_id) - cache.clear() return 204 def post(self, repository, cat_data): @@ -154,8 +162,9 @@ ) logger.debug("post id: "+cat.cat_id) - cache.clear() - return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 201 + return cat.cat_id, \ + cat.cat_graph.serialize(format='turtle').decode("utf-8"), \ + 201 def delete(self, repository, deleted_cat_id): """ @@ -283,7 +292,6 @@ ) logger.debug("delete id: " + deleted_cat_id) - cache.clear() return 204 @@ -302,16 +310,14 @@ """ API to get the pending changes for category cat_id """ - logger.debug(modified_cat_id) - logger.debug(str(session.get("modified_categories", {}))) - + if modified_cat_id is None: return { "modified_categories": session.get("modified_categories", {}) .get(repository, {}), "deleted_categories": session.get("deleted_categories", {}) .get(repository, {}) - }, 201 + }, 200 else: if modified_cat_id in session.get("modified_categories", {}) \ .get(repository, {}): @@ -322,7 +328,7 @@ [repository] [modified_cat_id] } - }, 201 + }, 200 if modified_cat_id in session.get("deleted_categories", {}) \ .get(repository, {}): return { @@ -332,11 +338,11 @@ [repository] [modified_cat_id] } - }, 201 + }, 200 return { "type": "untouched", "category": modified_cat_id - }, 201 + }, 200 def delete(self, repository, modified_cat_id=None): """ diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/settings.py --- a/src/catedit/settings.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/settings.py Fri Jan 30 12:38:15 2015 +0100 @@ -9,7 +9,7 @@ WTF_CSRF_ENABLED = True - # RDF Namespace and prefixes + # RDF Namespace and prefixes - Must end with # ONTOLOGY_NAMESPACE = "http://ld.iri-research.org/ontology/categorisation#" CATEGORY_NAMESPACE = "http://ld.iri-research.org/ontology/categorisation/category#" diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/templates/catbase.html --- a/src/catedit/templates/catbase.html Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/templates/catbase.html Fri Jan 30 12:38:15 2015 +0100 @@ -38,7 +38,7 @@ +{% endblock page_content%} diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/templates/catmodifs.html --- a/src/catedit/templates/catmodifs.html Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/templates/catmodifs.html Fri Jan 30 12:38:15 2015 +0100 @@ -1,5 +1,5 @@ {% extends "catbase.html" %} -{% if not session["user_logged"] or not session["user_can_edit"] %} +{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %} {% set readonly="readonly" %} {% else %} {% set readonly=False %} diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/templates/catrecap.html --- a/src/catedit/templates/catrecap.html Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/templates/catrecap.html Fri Jan 30 12:38:15 2015 +0100 @@ -1,10 +1,5 @@ {% extends "catbase.html" %} -{% if not session["user_logged"] or not session["user_can_edit"] %} - {% set readonly="readonly" %} -{% else %} - {% set readonly=False %} -{% endif %} -{% if not session["user_logged"] or not session["user_can_edit"] %} +{% if not session["user_logged"] or not session["user_can_edit"][current_repository] %} {% set readonly="readonly" %} {% else %} {% set readonly=False %} @@ -85,7 +80,7 @@ {% if (cat.state != "deleted") %} - + @@ -95,7 +90,7 @@
- +
diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/version.py --- a/src/catedit/version.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/version.py Fri Jan 30 12:38:15 2015 +0100 @@ -6,7 +6,7 @@ __all__ = ["VERSION", "get_version", "CURRENT_VERSION"] -VERSION = (0, 1, 2) +VERSION = (0, 1, 3) def get_version(): """ diff -r 67d2ddbebf7e -r 8c32ea41b391 src/catedit/views.py --- a/src/catedit/views.py Tue Jan 13 10:43:26 2015 +0100 +++ b/src/catedit/views.py Fri Jan 30 12:38:15 2015 +0100 @@ -3,60 +3,23 @@ The views functions that handle the front-end of the application """ -from catedit import app, github +from catedit import app, github, cache from catedit.models import Category +from requests import get +from requests.auth import HTTPBasicAuth import catedit.persistence from flask import render_template, request, redirect, url_for, session, abort from flask.ext.github import GitHubError from flask_wtf import Form from catedit.resources import CategoryAPI, CategoryChangesAPI -from wtforms import StringField, TextAreaField, FormField, FieldList, HiddenField +from wtforms import StringField, TextAreaField, FormField, PasswordField, \ + FieldList, HiddenField from wtforms.validators import DataRequired, Optional, AnyOf from rdflib import Graph from io import StringIO logger = app.logger - -class PropertyForm(Form): - """ - Form of a given property, each one is a couple of hidden fields that - can be Javascript-generated in the template - """ - property_predicate = HiddenField( - validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())] - ) - property_object = HiddenField( - validators=[DataRequired()] - ) - - -class CategoryForm(Form): - """ - Custom form class for creating a category with the absolute minimal - attributes (label and description) - """ - label = StringField( - "Nom de la categorie (obligatoire)", - validators=[DataRequired()] - ) - description = TextAreaField( - "Description de la categorie (obligatoire)", - validators=[DataRequired()] - ) - properties = FieldList(FormField(PropertyForm), validators=[Optional()]) - - -class CommitForm(Form): - """ - Custom form class for commiting changes - """ - commit_message = StringField( - "Message de soumission (obligatoire)", - validators=[DataRequired()] - ) - - @app.route('/', methods=['GET']) @app.route('/index', methods=['GET']) def cat_index(): @@ -118,10 +81,12 @@ modified_cat_dict = {} serialized_cat_list = [] if session.get("user_logged", None) is not None: - serialized_cat_list = cat_api_instance.get(repository=repository) - cat_changes = cat_changes_api_instance.get(repository=repository) - modified_cat_dict = cat_changes[0]["modified_categories"] - deleted_cat_dict = cat_changes[0]["deleted_categories"] + serialized_cat_list = cat_api_instance.get(repository=repository) \ + [0] + cat_changes = cat_changes_api_instance.get(repository=repository) \ + [0] + modified_cat_dict = cat_changes["modified_categories"] + deleted_cat_dict = cat_changes["deleted_categories"] # logger.debug(serialized_cat_list) cat_list = [] original_cat_list = [] @@ -159,9 +124,9 @@ # now we must find the not yet submitted categories that were created cat_state = "" logger.debug("Edited cat list: " - + str(edited_cat_list) + + str([cat.label for cat in edited_cat_list]) + " - Original cat list: " - + str(original_cat_list)) + + str([cat.label for cat in original_cat_list])) for category in edited_cat_list: if category.cat_id not in [cat.cat_id for cat in original_cat_list]: @@ -180,6 +145,15 @@ cat_list=cat_list, current_repository=repository) +class CommitForm(Form): + """ + Custom form class for commiting changes + """ + commit_message = StringField( + "Message de soumission (obligatoire)", + validators=[DataRequired()] + ) + @app.route('//catmodifs/delete-modifs-', defaults={'deleted_cat_id': None}, @@ -224,7 +198,7 @@ if session.get("user_logged", None) is not None: serialized_cat_list = cat_api_instance.get( repository=repository - ) + )[0] changes_list = cat_changes_api_instance.get( repository=repository )[0] @@ -330,6 +304,36 @@ return redirect(url_for('cat_modifs', repository=repository)) + +class PropertyForm(Form): + """ + Form of a given property, each one is a couple of hidden fields that + can be Javascript-generated in the template + """ + property_predicate = HiddenField( + validators=[AnyOf(values=app.config["PROPERTY_LIST"].keys())] + ) + property_object = HiddenField( + validators=[DataRequired()] + ) + + +class CategoryForm(Form): + """ + Custom form class for creating a category with the absolute minimal + attributes (label and description) + """ + label = StringField( + "Nom de la categorie (obligatoire)", + validators=[DataRequired()] + ) + description = TextAreaField( + "Description de la categorie (obligatoire)", + validators=[DataRequired()] + ) + properties = FieldList(FormField(PropertyForm), validators=[Optional()]) + + @app.route('//cateditor', methods=['GET', 'POST']) @app.route('//cateditor/', @@ -346,7 +350,7 @@ cat_changes_api_instance = CategoryChangesAPI() specific_serialized_cat = "" - # serialization of the category of id cat_id, either from + # Serialization of the category of id cat_id, either from # CategoryChangesAPI (if it was modified) or from CategoryAPI (if it # was not) @@ -358,6 +362,17 @@ current_cat_properties = [] # Args for the template, if we create a new category they are not used + cat_list = [] + # Category list that will be used by the template + deleted_cat_dict = {} + # Deleted categories we won't append to cat_list + modified_cat_dict = {} + # Modified categories to append to cat_list in case label changed + serialized_cat_list = [] + # Existing categories we get from CategoryAPI + cat_changes = {} + # Changes list we get from CategoryChangesAPI + if cat_id is not None: if session.get("user_logged", None) is not None: changes_response = cat_changes_api_instance.get( @@ -374,7 +389,7 @@ specific_serialized_cat = cat_api_instance.get( repository=repository, cat_id=cat_id - ) + )[0] logger.debug(specific_serialized_cat) cat_rdf_graph = Graph() @@ -386,6 +401,36 @@ current_cat_properties = current_cat.properties logger.debug(current_cat.properties) + serialized_cat_list = cat_api_instance.get(repository)[0] + cat_changes = cat_changes_api_instance.get(repository)[0] + deleted_cat_dict = cat_changes["deleted_categories"] + modified_cat_dict = cat_changes["modified_categories"] + logger.debug(changes_response) + + for modified_cat_name in modified_cat_dict.keys(): + modified_cat_rdf_graph = Graph() + modified_cat_rdf_graph.parse( + source=StringIO(modified_cat_dict[modified_cat_name]), + format='turtle' + ) + modified_cat = Category(graph=modified_cat_rdf_graph) + cat_list.append({"cat_label": modified_cat.label, + "cat_description": modified_cat.description, + "cat_id": modified_cat.cat_id, + "cat_properties": modified_cat.properties}) + + for serialized_cat in serialized_cat_list: + cat_rdf_graph = Graph() + cat_rdf_graph.parse(source=StringIO(serialized_cat), + format='turtle') + cat = Category(graph=cat_rdf_graph) + if cat.cat_id not in deleted_cat_dict.keys() and \ + cat.cat_id not in modified_cat_dict.keys(): + cat_list.append({"cat_label": cat.label, + "cat_description": cat.description, + "cat_id": cat.cat_id, + "cat_properties": cat.properties}) + cat_form = CategoryForm(request.form) if request.method == "GET": @@ -396,66 +441,32 @@ cat_form.label.data = current_cat.label cat_form.description.data = current_cat.description for (cat_predicate, cat_object) in current_cat_properties: + if app.config["PROPERTY_LIST"] \ + [cat_predicate] \ + ["object_type"] == "uriref-category": + namespace, object_id = cat_object.split("#",1) + else: + object_id = cat_object cat_form.properties.append_entry({ "property_predicate": cat_predicate, - "property_object": cat_object + "property_object": object_id }) - - # deleted categories we won't append to cat_list - deleted_cat_dict = {} - # modified categories to append to cat_list in case label changed - modified_cat_dict = {} - # existing categories we get from CategoryAPI - serialized_cat_list = [] - # changes list we get from CategoryChangesAPI - cat_changes = {} - # list of category that will be used in property editor, list of dict # {"cat_id": cat.cat_id, # "cat_label": cat.label, # "cat_description": cat.description, # "cat_properties": cat.properties} - cat_list = [] - - if session.get("user_logged", None) is not None: - serialized_cat_list = cat_api_instance.get(repository) - cat_changes = cat_changes_api_instance.get(repository)[0] - deleted_cat_dict = cat_changes["deleted_categories"] - modified_cat_dict = cat_changes["modified_categories"] - logger.debug(changes_response) - - for modified_cat_name in modified_cat_dict.keys(): - modified_cat_rdf_graph = Graph() - modified_cat_rdf_graph.parse( - source=StringIO(modified_cat_dict[modified_cat_name]), - format='turtle' - ) - modified_cat = Category(graph=modified_cat_rdf_graph) - cat_list.append({"cat_label": modified_cat.label, - "cat_description": modified_cat.description, - "cat_id": modified_cat.cat_id, - "cat_properties": modified_cat.properties}) - - for serialized_cat in serialized_cat_list: - cat_rdf_graph = Graph() - cat_rdf_graph.parse(source=StringIO(serialized_cat), - format='turtle') - cat = Category(graph=cat_rdf_graph) - if cat.cat_id not in deleted_cat_dict.keys() and \ - cat.cat_id not in modified_cat_dict.keys(): - cat_list.append({"cat_label": cat.label, - "cat_description": cat.description, - "cat_id": cat.cat_id, - "cat_properties": cat.properties}) - - deleted_cat_list = list(deleted_cat_dict.keys()) - + logger.debug("category list that can be linked :" + str(cat_list)) + logger.debug( + "deleted categories list that can't be linked :" + + str(list(deleted_cat_dict.keys())) + ) return render_template('cateditor.html', cat_id=current_cat_id, cat_properties=current_cat_properties, form=cat_form, cat_list=cat_list, - deleted_cat_list=deleted_cat_list, + deleted_cat_list=list(deleted_cat_dict.keys()), current_repository=repository) elif request.method == "POST": """ @@ -466,8 +477,6 @@ if (session.get("user_logged", None) is not None and session.get("user_can_edit", False) is not False): - logger.debug(cat_form.errors) - if cat_form.validate_on_submit(): cat_data = {} cat_data["label"] = cat_form.label.data @@ -477,7 +486,6 @@ cat_property["property_object"]) for cat_property in cat_form.properties.data ] - logger.debug(cat_data) if cat_id is not None: logger.debug(str(cat_data)) @@ -498,10 +506,12 @@ # changes the user did return render_template('cateditor.html', cat_id=cat_id, - cat_properties=cat.properties, + cat_properties=current_cat_properties, form=cat_form, cat_list=cat_list, - deleted_cat_list=deleted_cat_list, + deleted_cat_list=list( + deleted_cat_dict.keys() + ), current_repository=repository) # If user wasn't logged or couldn't edit but somehow submitted a POST @@ -509,7 +519,21 @@ return redirect(url_for('cat_index')) -@app.route('/catedit-github-login') +class LoginForm(Form): + """ + Custom form class for commiting changes + """ + user_login = StringField( + "Nom d'utilisateur Github", + validators=[DataRequired()] + ) + user_password = PasswordField( + "Mot de passe Github", + validators=[DataRequired()] + ) + + +@app.route('/catedit-login', methods=["GET", "POST"]) def github_login(): """ Function that manages authentication (Github), login @@ -518,10 +542,7 @@ in local files, used for debugging), creates a mock user named "FileEditUser" """ - if getattr(catedit.persistence, - app.config["PERSISTENCE_CONFIG"] - ["METHOD"])().session_compliant is True: - session["save_user_changes"] = True + if not session.get("user_logged", False): session["modified_categories"] = { repo: {} for repo in app.config["PERSISTENCE_CONFIG"] ["REPOSITORY_LIST"] @@ -530,18 +551,81 @@ repo: {} for repo in app.config["PERSISTENCE_CONFIG"] ["REPOSITORY_LIST"] } - if app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToGithub": - logger.debug(str(github.get("rate_limit")["resources"])) - return github.authorize( - scope="repo", - redirect_uri=url_for('github_callback', _external=True) - ) - elif app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToFile": - session["user_logged"] = True - session["user_can_edit"] = True - session["user_login"] = "FileEditUser" + if app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToGithub": + login_form = LoginForm(request.form) + if request.method == "POST": + if login_form.validate_on_submit(): + # We'll try to get the auth token for given username + attempted_login = login_form.user_login.data + try: + auth_response = get( + "https://api.github.com/" + + "authorizations", + auth=HTTPBasicAuth( + login_form.user_login.data, + login_form.user_password.data + ) + ) + for auth in auth_response.json(): + if auth["app"]["client_id"] \ + == app.config["GITHUB_CLIENT_ID"]: + session["user_code"] = auth["token"] + session["user_logged"] = True + except: + logger.debug( + "Error requesting authorizations for" + + " user. Either the user is new to catedit, or " + + "entered a wrong username/password" + ) + logger.debug( + "user token found by request: " + + str(session.get("user_code", None)) + ) + if session.get("user_code", None) == None: + # We didn't get it, so we direct the user to the login page + # with a link to github oauth system + return render_template( + 'catlogin.html', + form = login_form + ) + else: + # we did get it, so we redirect to callback function + # to wrap up user auth + return redirect(url_for('github_callback')) + else: + # form didn't validate, so we send it back to user + return render_template( + 'catlogin.html', + form = login_form + ) + elif request.method == "GET": + # We'll render the login form + return render_template( + 'catlogin.html', + form=login_form, + ) + elif app.config["PERSISTENCE_CONFIG"]["METHOD"] == "PersistenceToFile": + session["user_logged"] = True + session["user_can_edit"] = {} + session["user_can_edit"]["local"] = True + session["user_login"] = "FileEditUser" + return redirect(url_for('cat_index')) + else: return redirect(url_for('cat_index')) +@app.route('/catedit-login-confirm', methods=["GET", "POST"]) +def github_login_confirm(): + """ + Function called if the user is new or revoked the auth token + """ + if not session.get("user_logged", False): + if request.method == "POST": + return github.authorize( + scope="repo", + redirect_uri=url_for('github_callback', _external=True) + ) + else: + return redirect(url_for('cat_index')) @app.route('/catedit-github-callback') @github.authorized_handler @@ -550,41 +634,51 @@ Function that handles callback from Github after succesful login """ session.permanent = False - session["user_code"] = oauth_code + if session.get("user_code", None) == None: + # That means we got here using github callback and not the login form + session["user_code"] = oauth_code + logger.debug(session["user_code"]) session["user_logged"] = True session["user_login"] = "auth-error" try: + logger.debug( + "after login: " + + str(github.get("rate_limit")["resources"]) + ) session["user_login"] = github.get("user")["login"] - logger.debug(str(github.get("rate_limit")["resources"])) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to get the user login" ) - logger.debug(ghe.request.text) + logger.error(ghe.response.text) try: repo_list = [] repo_list = github.get("user/repos") logger.debug(str(github.get("rate_limit")["resources"])) for repo in repo_list: logger.debug(repo["name"]) - session["user_can_edit"] = True user_repos_name = [repo["name"] for repo in repo_list] logger.debug( str(user_repos_name) + " " + str(app.config["PERSISTENCE_CONFIG"] ["REPOSITORY_LIST"]) ) - if not all((repo in user_repos_name) - for repo in app.config["PERSISTENCE_CONFIG"] - ["REPOSITORY_LIST"]): - session["user_can_edit"] = False + session["user_repositories"] = list( + set(user_repos_name).intersection( + app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"] + ) + ) + session["user_can_edit"] = {} + for repo in session["user_repositories"]: + if repo in app.config["PERSISTENCE_CONFIG"]["REPOSITORY_LIST"]: + session["user_can_edit"][repo] = True logger.debug(session["user_can_edit"]) except GitHubError as ghe: - logger.debug( + logger.error( "GitHubError trying to get the list of repository for user " + session["user_login"] ) - logger.debug(ghe.response.text) + logger.error(ghe.response.text) return redirect(url_for('cat_index')) @@ -608,11 +702,10 @@ Github, else when logging back in, github will send the app the same oauth code """ + session["user_logged"] = None session["user_code"] = None - session["user_logged"] = None session["user_login"] = None session["user_can_edit"] = None - session["save_user_changes"] = None session["modified_categories"] = { repo: {} for repo in app.config["PERSISTENCE_CONFIG"] ["REPOSITORY_LIST"]