Added support for making multiple changes in a single commit, using the flask dict "session" (keys used are "modified_categories" and "deleted_categories") + Added a view for editing categories that allows the user to see what he has added, deleted or edited + Made it so an unauthenticated user should not generate any Github api request (you have to be logged to see the categories now).
--- a/src/catedit/__init__.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/__init__.py Mon Dec 29 18:01:09 2014 +0100
@@ -8,6 +8,7 @@
from settings import AppSettings
from config import AppConfig
from logging import FileHandler, Formatter
+from os import urandom
# set up app and database
app = Flask(__name__)
@@ -36,4 +37,4 @@
# session management
-app.secret_key = "extremely_secure_and_very_secret_key"
+app.secret_key = urandom(24)
--- a/src/catedit/api.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/api.py Mon Dec 29 18:01:09 2014 +0100
@@ -9,19 +9,19 @@
from flask import request
from catedit import app, cache
from catedit.models import Category, CategoryManager
-
+import catedit.persistence
api = Api(app)
-LOGGER = app.logger
+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)
+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):
@@ -36,7 +36,10 @@
API to get the category of id cat_id, or if cat_id is None,
get the list of category
"""
- cat_manager_instance = CategoryManager()
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence,
+ app.config["PERSISTENCE_METHOD"])()
+ )
if cat_id is not None:
cat = cat_manager_instance.load_cat(cat_id)
return cat.cat_graph.serialize(format='turtle').decode("utf-8")
@@ -49,53 +52,59 @@
# update category cat_id
@classmethod
- def put(cls, cat_id):
+ 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
"""
- args = CAT_PARSER.parse_args()
- cat_manager_instance = CategoryManager()
-
- 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)
- cat = cat_manager_instance.load_cat(cat_id)
- cat.edit_category(new_description=args["description"],
- new_label=args["label"],
- new_other_properties=new_property_list)
- cat_manager_instance.save_cat(cat, message=args["commit_message"])
- LOGGER.debug("put id: "+cat.cat_id)
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence, app.config["PERSISTENCE_METHOD"])()
+ )
+ args = cat_parser.parse_args()
+ if (cat_id is None and
+ cat_manager_instance.persistence.session_compliant is True):
+ cat_manager_instance.save_cat(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)
+ cat = cat_manager_instance.load_cat(cat_id)
+ cat.edit_category(new_description=args["description"],
+ new_label=args["label"],
+ new_other_properties=new_property_list)
+ cat_manager_instance.save_cat(cat, message=args["commit_message"])
+ logger.debug("put id: "+cat.cat_id)
cache.clear()
- return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 200
- # Maybe not send the whole cat back, see if it's worth it
+ return 204
@classmethod
def post(cls):
"""
API to create a new category
"""
- args = CAT_PARSER.parse_args()
+ args = cat_parser.parse_args()
property_list = []
- LOGGER.debug(args["property_predicate"])
- LOGGER.debug(args["property_object"])
+ 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')):
@@ -109,13 +118,16 @@
else:
property_list.append((property_predicate,
property_object))
- LOGGER.debug(property_list)
+ logger.debug(property_list)
cat = Category(label=args["label"],
description=args["description"],
other_properties=property_list)
- cat_manager_instance = CategoryManager()
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence,
+ app.config["PERSISTENCE_METHOD"])()
+ )
cat_manager_instance.save_cat(cat, message=args["commit_message"])
- LOGGER.debug("post id: "+cat.cat_id)
+ logger.debug("post id: "+cat.cat_id)
cache.clear()
return cat.cat_graph.serialize(format='turtle').decode("utf-8"), 201
@@ -124,14 +136,17 @@
"""
API to delete the category of id cat_id
"""
- args = CAT_PARSER.parse_args()
- cat_manager_instance = CategoryManager()
+ args = cat_parser.parse_args()
+ cat_manager_instance = CategoryManager(
+ getattr(catedit.persistence,
+ app.config["PERSISTENCE_METHOD"])()
+ )
if args["delete_message"] is None:
message = "Deleting category "+cat_id
else:
message = args["delete_message"]
cat_manager_instance.delete_cat(cat_id, message=message)
- LOGGER.debug("delete id: "+cat_id)
+ logger.debug("delete id: "+cat_id)
cache.clear()
return 204
--- a/src/catedit/main.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/main.py Mon Dec 29 18:01:09 2014 +0100
@@ -2,7 +2,6 @@
main.py:
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, \
--- a/src/catedit/models.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/models.py Mon Dec 29 18:01:09 2014 +0100
@@ -6,14 +6,15 @@
"""
from rdflib import Graph, RDF, RDFS, Literal, URIRef
-# from uuid import uuid4
+from rdflib.namespace import SKOS
+from flask import session
+from uuid import uuid4
from io import StringIO
from slugify import slugify
from catedit import app
-import catedit.persistence
-LOGGER = app.logger
+logger = app.logger
class Category(object):
@@ -35,8 +36,8 @@
def __init__(self, label=None, description=None,
other_properties=None, graph=None):
if not graph:
- # cat_id = uuid4().hex - Alternate method of generating ids
- cat_id = "category_id_"+slugify(label)
+ # cat_id = .hex - Alternate method of generating ids
+ cat_id = str(uuid4())[:8]+"_"+slugify(label)
self.cat_graph = Graph()
self.this_category = URIRef(app.config["CATEGORY_NAMESPACE"] +
cat_id)
@@ -112,22 +113,20 @@
property_list.append((key, obj.toPython()))
return property_list
- def edit_category(self, new_label=False,
- new_description=False,
- new_other_properties=False):
+ def edit_category(self, new_label="",
+ new_description="",
+ new_other_properties=None):
"""
Edits category
* new_label is the new label of the category
* new_description is the new description of the category
* new_other_property is the new property list of the category
- Every argument is optional and defaults to False for consistency,
- as giving a property list that is None means we want
- to delete all properties
+ new_other_properties default is None because it can't be [], as
+ that would mean we want to delete all properties
"""
- if (new_label is not False) and \
- (new_label is not None) and \
+ if (new_label != "") and \
(new_label != self.label):
self.cat_graph.remove((self.this_category,
RDFS.label,
@@ -136,8 +135,7 @@
RDFS.label,
Literal(new_label)))
- if (new_description is not False) and \
- (new_description is not None) and \
+ if (new_description != "") and \
(new_description != self.description):
self.cat_graph.remove((self.this_category,
RDF.Description,
@@ -147,23 +145,19 @@
RDF.Description,
Literal(new_description)))
- if new_other_properties is not False or \
- (new_other_properties is None and
- self.properties is not None):
- LOGGER.debug("before suppressing properties: ")
- LOGGER.debug(self.properties)
- LOGGER.debug("will replace with: ")
- LOGGER.debug(new_other_properties)
+ logger.debug("current properties: "
+ +str(self.properties)
+ +" new properties: "
+ +str(new_other_properties))
+
+ if new_other_properties is not None and \
+ (new_other_properties != self.properties):
for key in app.config["PROPERTY_LIST"]:
self.cat_graph.remove((self.this_category,
app.config["PROPERTY_LIST"]
[key]
["rdflib_class"],
None))
- LOGGER.debug("now properties are: ")
- LOGGER.debug(self.properties)
- LOGGER.debug("making new properties: ")
- LOGGER.debug(new_other_properties)
for (predicate, obj) in new_other_properties:
self.cat_graph.add((self.this_category,
app.config["PROPERTY_LIST"]
@@ -178,68 +172,128 @@
"""
CategoryManager class
This class deals with creation and loading of Category objects
- Persistence method is set in config.py and used to save
- and load categories
"""
+ def __init__(self, persistence_method):
+ """
+ persistence_method is a class that must have 4 methods:
+
+ * save
+ * load
+ * delete
+ * list
+
+ It must save and return RDF graph serializations.
+ See persistence.py for details
+ """
+ self.persistence = persistence_method
+
def load_cat(self, cat_id):
"""
Loads a category from its id
"""
- persistence = getattr(catedit.persistence,
- app.config["PERSISTENCE_METHOD"])()
- cat_serial = persistence.load(name=cat_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)
return cat
- def save_cat(self, cat, message=None):
- """
- Saves a category, message serves as commit message
- if github is used as a persistence method
+ def save_cat(self, cat=None, message=None):
"""
- persistence = getattr(catedit.persistence,
- app.config["PERSISTENCE_METHOD"])()
- persistence.save(content=cat.cat_graph.serialize(format='turtle'),
- name=cat.cat_id, message=message)
+ Saves a category, message serves as commit message if github
+ is used as a persistence method
+
+ * If cat is None and the persistence method supports submitting
+ changes to multiple files in one go, then it will call
+ submit_changes method
+ """
+ if cat is None:
+ self.persistence.submit_changes(message=message)
+ else:
+ self.persistence.save(content=cat.cat_graph
+ .serialize(format='turtle'),
+ name=cat.cat_id,
+ message=message)
def delete_cat(self, deleted_cat_id, message=None):
"""
- Deletes a category from its id, message serves
- as commit message if github is used as a persistence method
+ Deletes a category from its id, message serves as commit message if
+ github is used as a persistence method
"""
+ self.persistence.delete(name=deleted_cat_id, message=message)
+ # Now we must clean up the categories that reference the deleted cat
cat_list = self.list_cat()
- for cat in cat_list:
- if cat.cat_id != app.config["CATEGORY_NAMESPACE"]+deleted_cat_id:
- new_property_list_for_cat = []
- 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_for_cat.append((predicate, obj))
- cat.edit_category(
- new_other_properties=new_property_list_for_cat
- )
- self.save_cat(
- cat,
- message=message+", cleaning up other properties"
- )
- persistence = getattr(catedit.persistence,
- app.config["PERSISTENCE_METHOD"])()
- persistence.delete(name=deleted_cat_id, message=message)
+ # first we edit what was already modified before the deletion
+ logger.debug(session["modified_categories"])
+ element_list = list(session["modified_categories"])
+ if deleted_cat_id in [element["name"] for \
+ element in session["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_modified_cat = []
+ 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_for_modified_cat.append(
+ (predicate, obj)
+ )
+ if (new_property_list_for_modified_cat
+ != modified_cat.properties):
+ logger.debug("Modifying modified category")
+ modified_cat.edit_category(
+ new_other_properties=\
+ new_property_list_for_modified_cat
+ )
+ self.save_cat(
+ modified_cat,
+ message=message+", cleaning up other properties"
+ )
+ # 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["modified_categories"]] and \
+ cat.cat_id not in [element["name"] for \
+ element in session["deleted_categories"]]:
+ new_property_list_for_cat = []
+ 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_for_cat.append((predicate, obj))
+ if(new_property_list_for_cat!=cat.properties):
+ logger.debug("Modifying untouched category")
+ cat.edit_category(
+ new_other_properties=new_property_list_for_cat
+ )
+ self.save_cat(
+ cat,
+ message=message+", cleaning up other properties"
+ )
+
def list_cat(self):
"""
Lists all categories available
"""
- persistence = getattr(catedit.persistence,
- app.config["PERSISTENCE_METHOD"])()
- cat_serial_list = persistence.list()
- # LOGGER.debug(cat_serial_list)
+ cat_serial_list = self.persistence.list()
+ # logger.debug(cat_serial_list)
cat_list = []
for cat_serial in cat_serial_list:
loaded_cat_graph = Graph()
--- a/src/catedit/persistence.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/persistence.py Mon Dec 29 18:01:09 2014 +0100
@@ -11,11 +11,12 @@
from abc import ABCMeta, abstractmethod
from catedit import app, github
from base64 import b64encode, b64decode
+from flask import session
from flask.ext.github import GitHubError
import os
import json
-LOGGER = app.logger
+logger = app.logger
class Persistence:
@@ -25,6 +26,21 @@
__metaclass__ = ABCMeta
@abstractmethod
+ def session_compliant(self):
+ """
+ Abstract - Can this persistence submit a changeset?
+ """
+ return
+
+ @abstractmethod
+ def submit_changes(self, **kwargs):
+ """
+ Abstract - Submit all saved objects, only useful if persistence
+ method can submit a changeset
+ """
+ return
+
+ @abstractmethod
def save(self, **kwargs):
"""
Abstract - Saves object
@@ -62,6 +78,19 @@
* name : name of the file to write in/read from
* content : desired content of the file when writing
"""
+ @property
+ def session_compliant(self):
+ """
+ Not session compliant: each modification is submitted
+ """
+ return False
+
+ def submit_changes(self, **kwargs):
+ """
+ As each modification is submitted, this is where we save to a file
+ """
+
+
def save(self, **kwargs):
"""
Saves to a file
@@ -101,7 +130,7 @@
file_content = path_to_load.read()
path_to_load.close()
file_content_list.append(file_content)
- # LOGGER.debug(file_content_list)
+ # logger.debug(file_content_list)
return file_content_list
@@ -116,50 +145,197 @@
* message : when saving or deleting, commit message to be saved
to Github
"""
+ @property
+ def session_compliant(self):
+ """
+ Not session compliant: each modification is comitted
+ """
+ return True
+
+ def submit_changes(self, **kwargs):
+ """
+ Saves all the recorded files in the session dict to a Github
+ repository
+
+ Expected kwargs is:
+ * message: the commit message to document the changes
+
+ IMPORTANT: To save to a file Github, we have to use Git
+ internal mechanics:
+ 1) Get the current reference for master branch
+ 2) Get the latest commit on that reference
+ 3) Get the tree associated to this commit
+ 4) Create a new tree using the old tree and the session dict that
+ contains all the changes (keys are "modified_categories" and
+ "deleted_categories")
+ 4-1) This requires creating new blobs for new files
+ 5) Create a new commit referencing the previous commit as parent
+ and the new tree we just created
+ 6) Update the master branch reference so it points on this new
+ commit
+
+ About point 4):
+ We have 3 list of elements: files already in repo, modified files,
+ and deleted_files
+ First, we loop on the files already in repo: this allows us to
+ update it (if it's in both original file list and modified files
+ list) or delete it (if it's in both original file list and deleted
+ files list)
+ Then, we loop on the modified files list. If it isn't in the
+ original file list, then it's a new file and we append it to the
+ tree
+ """
+
+ # point 1
+ ref_master = github.get("repos/"
+ + app.config["REPOSITORY_OWNER"] + "/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/refs/heads/master")
+ logger.debug(str(ref_master))
+ # point 2
+ last_commit_master = github.get("repos/"
+ + app.config["REPOSITORY_OWNER"] + "/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/commits/"
+ + ref_master["object"]["sha"])
+
+ logger.debug(str(last_commit_master))
+ # point 3
+ last_commit_tree = github.get("repos/"
+ + app.config["REPOSITORY_OWNER"] + "/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/trees/"
+ + last_commit_master["tree"]["sha"]
+ + "?recursive=1")
+
+ logger.debug(str(last_commit_tree))
+ # point 4
+ new_tree_data = {"tree": []}
+ for element in last_commit_tree["tree"]:
+ if element["type"] == "blob":
+ # test if element is in deleted categories, if it is,
+ # no point doing anything, the file won't be in the new tree
+ if not(element["path"] in [(app.config["CATEGORIES_PATH"]
+ + category["name"])
+ for category in
+ session["deleted_categories"]]):
+
+ # the element is either modified or untouched so in both
+ # case we'll need a blob sha
+ blob_sha = ""
+
+ # test if element is in modified categories
+ if element["path"] in [(app.config["CATEGORIES_PATH"]
+ + category["name"])
+ for category in
+ session["modified_categories"]]:
+ # find element in modified categories
+ for category in session["modified_categories"]:
+ if element["path"] == (
+ app.config["CATEGORIES_PATH"]
+ + category["name"]
+ ):
+ # 4-1 for edited files
+ new_blob_data = {
+ "content": category["content"],
+ "encoding": "utf-8"
+ }
+ new_blob = github.post(
+ "repos/"
+ + app.config["REPOSITORY_OWNER"]
+ +"/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/blobs",
+ data=new_blob_data
+ )
+ blob_sha = new_blob["sha"]
+ break
+
+ # this means element is an untouched file
+ else:
+ blob_sha = element["sha"]
+ new_tree_data["tree"].append({"path": element["path"],
+ "mode": element["mode"],
+ "type": "blob",
+ "sha": blob_sha })
+ logger.debug(str(new_tree_data["tree"]))
+ # Now we'll add new files in the tree
+ for category in session["modified_categories"]:
+ logger.debug(app.config["CATEGORIES_PATH"]+category["name"]
+ + " should not be in "
+ + str([ file["path"] for
+ file in last_commit_tree["tree"]]))
+ if (app.config["CATEGORIES_PATH"]+category["name"] not in
+ [ file["path"] for file in last_commit_tree["tree"]]):
+
+ # 4-1 for added files
+ new_blob_data={"content": category["content"],
+ "encoding": "utf-8"}
+ new_blob = github.post("repos/"
+ + app.config["REPOSITORY_OWNER"]+"/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/blobs",
+ data=new_blob_data)
+ new_tree_data["tree"].append({
+ "path": app.config["CATEGORIES_PATH"]
+ + category["name"],
+ "mode": "100644",
+ "type": "blob",
+ "sha": new_blob["sha"] })
+ logger.debug(str(new_tree_data))
+
+ # Finally, we post the new tree
+ new_tree_response = github.post("repos/"
+ + app.config["REPOSITORY_OWNER"]+"/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/trees",
+ data=new_tree_data)
+
+ # point 5
+ new_commit_data = {"message": kwargs["message"],
+ "parents": [last_commit_master["sha"]],
+ "tree": new_tree_response["sha"]}
+ logger.debug(str(new_commit_data))
+ new_commit = github.post("repos/"
+ + app.config["REPOSITORY_OWNER"]+"/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/commits",
+ data=new_commit_data)
+ logger.debug(str(new_commit))
+
+ # point 6
+ new_head_data = {"sha": new_commit["sha"], "force": "true"}
+ logger.debug(str(new_head_data))
+ new_head = github.patch("repos/"
+ + app.config["REPOSITORY_OWNER"] + "/"
+ + app.config["REPOSITORY_NAME"]
+ + "/git/refs/heads/master",
+ data=json.dumps(new_head_data))
+ logger.debug(str(new_head))
+ session["deleted_categories"] = []
+ session["modified_categories"] = []
+
def save(self, **kwargs):
"""
- Saves to a Github repository
+ Saves given file to the session dict
- IMPORTANT: To save to a file Github, we use a PUT request, and
- if the file already exists we must put its sha in the request data.
- The first try-except block actually expects an error if creating a
- new file because we need to check if the file already exists
+ Expected kwargs should be:
+ * name: the name of the file to save
+ * content: the content of the file to save
"""
- # LOGGER.debug(kwargs["content"])
- request_data = {"content": str(b64encode(kwargs["content"]), "ascii"),
- "message": kwargs["message"]}
- try:
- filedict = github.get("repos/"
- + app.config["REPOSITORY_OWNER"]+"/"
- + app.config["REPOSITORY_NAME"]
- + "/contents/"
- + app.config["CATEGORIES_PATH"]
- + kwargs["name"])
- request_data["sha"] = filedict["sha"]
- except GitHubError as ghe:
- LOGGER.debug("Github sent an error, either: \
- 1- You're trying to create a new file named : "
- + kwargs["name"] + " OR \
- 2- You're trying to edit a file named : "
- + kwargs["name"] + ", \
- in which case something went wrong \
- trying to get its sha")
- LOGGER.debug(ghe.response.json())
- # LOGGER.debug(json.dumps(request_data))
- try:
- github.request('PUT',
- "repos/"
- + app.config["REPOSITORY_OWNER"]+"/"
- + app.config["REPOSITORY_NAME"]
- + "/contents/"
- + app.config["CATEGORIES_PATH"]
- + kwargs["name"],
- data=json.dumps(request_data))
- except GitHubError as ghe:
- LOGGER.debug("Github Error trying to update 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.json())
+ session["modified_categories"][:] = [elt \
+ for elt in session["modified_categories"] \
+ if elt["name"] != kwargs["name"]]
+ session["modified_categories"].append(
+ {"name": kwargs["name"],
+ "content": str(kwargs["content"],"utf-8")}
+ )
+ # Now we must clean the deleted categories list in case the modified
+ # category was deleted before being edited
+
+ for element in session["deleted_categories"]:
+ if element["name"] == kwargs["name"]:
+ session["deleted_categories"].remove(element)
def load(self, **kwargs):
"""
@@ -174,40 +350,31 @@
+ 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 \
+ 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.debug(ghe.response.text)
return file_content
def delete(self, **kwargs):
"""
Deletes from a Github repository
+ Calling delete for a file already deleted will restore it
+ (by deleting it from the delete files list)
"""
- request_data = {"message": kwargs["message"]}
- try:
- filedict = github.get("repos/"
- + app.config["REPOSITORY_OWNER"]+"/"
- + app.config["REPOSITORY_NAME"]
- + "/contents/"
- + app.config["CATEGORIES_PATH"]
- + kwargs["name"])
- request_data["sha"] = filedict["sha"]
- except GitHubError as ghe:
- LOGGER.debug("Github Error trying to get sha for \
- file: "+kwargs["name"])
- LOGGER.debug(ghe.response.text)
+ if (kwargs["name"] in [element["name"] for \
+ element in session["deleted_categories"]]):
+ session["deleted_categories"].remove({"name": kwargs["name"]})
+ # warning, not safe if 2 files share the same name (or category id)
+ # but that shouldn't happen
+ else:
+ session["deleted_categories"].append({"name": kwargs["name"]})
+ # now we must clean the modified categories list in case the
+ # deleted category was modified before
+ for element in session["modified_categories"]:
+ if element["name"]==kwargs["name"]:
+ session["modified_categories"].remove(element)
- try:
- github.request('DELETE',
- "repos/catedit-system/"
- + app.config["REPOSITORY_NAME"]
- + "/contents/categories/"
- + kwargs["name"],
- data=json.dumps(request_data))
- except GitHubError as ghe:
- LOGGER.debug("Github Error trying to delete file: "+kwargs["name"])
- LOGGER.debug(ghe.response.text)
def list(self, **kwargs):
"""
@@ -222,14 +389,14 @@
+ app.config["CATEGORIES_PATH"])
filenames_list = [github_file["name"]
for github_file in files_in_repo]
- # LOGGER.debug(filenames_list)
+ # logger.debug(filenames_list)
except GitHubError as ghe:
- LOGGER.debug("Github Error trying to get the file list in the \
+ logger.debug("Github Error trying to get the file list in the \
category repository")
- LOGGER.debug("NOTE: Github sent an error, if 404 either you \
+ 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.debug(ghe.response.text)
file_content_list = []
for filename in filenames_list:
@@ -243,7 +410,53 @@
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.debug(file_content_list)
+ logger.debug("Github Error trying to get file: "+filename)
+ logger.debug(ghe.response.text)
+ #logger.debug(file_content_list)
return file_content_list
+
+
+class PersistenceToGist(Persistence):
+ """
+ This persistence class saves to Github gists
+
+ It uses the Flask session object and an optional param in the save
+ method to submit a set of changes instead of one change at a time
+
+ Expected kwargs for saving to/loading from a Github repository are:
+ * name : name of the file to write in/read from
+ * content : desired content of the file when writing
+ * message : when saving and/or deleting, commit message to be saved
+ to Github
+ """
+ @property
+ def session_compliant(self):
+ """
+ Session compliant: each modification is stored in flask session
+ object and every stacked changes can be submitted at once
+ """
+ return True
+
+ def save(self, **kwargs):
+ """
+ TODO : Docstring
+ """
+ return
+
+ def delete(self, **kwargs):
+ """
+ TODO : Docstring
+ """
+ return
+
+ def load(self, **kwargs):
+ """
+ TODO : Docstring
+ """
+ return
+
+ def list(self, **kwargs):
+ """
+ TODO : Docstring
+ """
+ return
--- a/src/catedit/static/css/style.css Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/static/css/style.css Mon Dec 29 18:01:09 2014 +0100
@@ -14,3 +14,8 @@
.hidden{
display:none;
}
+
+.navbar-brand
+{
+ padding:8px;
+}
Binary file src/catedit/static/img/catedit_brand.png has changed
--- a/src/catedit/static/js/property_functions.js Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/static/js/property_functions.js Mon Dec 29 18:01:09 2014 +0100
@@ -86,26 +86,31 @@
case "property_type_default":
document.getElementById("literal-field").className = "hidden form-control";
document.getElementById("uriref-category-field").className = "hidden form-control";
+ document.getElementById("uriref-category-field-text").className = "hidden";
document.getElementById("uriref-link-field").className = "hidden form-control";
break;
case "literal":
document.getElementById("literal-field").className = "visible form-control";
document.getElementById("uriref-category-field").className = "hidden form-control";
+ document.getElementById("uriref-category-field-text").className = "hidden";
document.getElementById("uriref-link-field").className = "hidden form-control";
break;
case "uriref-category":
document.getElementById("literal-field").className = "hidden form-control";
document.getElementById("uriref-category-field").className = "visible form-control";
+ document.getElementById("uriref-category-field-text").className = "visible";
document.getElementById("uriref-link-field").className = "hidden form-control";
break;
case "uriref-link":
document.getElementById("literal-field").className = "hidden form-control";
document.getElementById("uriref-category-field").className = "hidden form-control";
+ document.getElementById("uriref-category-field-text").className = "hidden";
document.getElementById("uriref-link-field").className = "visible form-control";
break;
default:
document.getElementById("literal-field").className = "hidden form-control";
document.getElementById("uriref-category-field").className = "hidden form-control";
+ document.getElementById("uriref-category-field-text").className = "hidden";
document.getElementById("uriref-link-field").className = "hidden form-control";
}
}
--- a/src/catedit/templates/cateditor.html Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/templates/cateditor.html Mon Dec 29 18:01:09 2014 +0100
@@ -17,18 +17,24 @@
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
+ <a class="navbar-brand" href="{{ url_for('cat_recap') }}">
+ <img alt="Brand" src="{{ url_for('static', filename='img/catedit_brand.png') }}" class="navbar-img" width="32" height="32">
+ </a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" id="logo" href="{{ url_for('cat_recap') }}"><span class="glyphicon glyphicon-picture"/></a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
- <li><a href="{{ url_for('cat_recap') }}">Liste des catégories</a></li>
- <li class="active"><a>Editeur de catégorie: {% if cat_id: %} Edition {% else %} Création {% endif %}</a></li>
+ <li><a href="#">Page d'accueil</a></li>
+ {% if session.get("user_logged", None) %}
+ <li><a href="{{ url_for('cat_recap') }}">Vue d'ensemble</a></li>
+ <li class="active"><a>{% if cat_id: %}Editer{% else %}Créer{% endif %} une catégorie</a></li>
+ <li><a href="{{ url_for('cat_modifs') }}">Mes modifications</a></li>
+ {% endif %}
</ul>
<div class="navbar-text navbar-right">
{% if not session.get("user_logged", None)%} Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
@@ -51,7 +57,7 @@
<span class="sr-only">Attention:</span>
Vous devez être authentifié pour modifier les catégories.
</div> {% endif %}
- {% if form.label.errors or form.description.errors or form.commit_message.errors %}
+ {% if form.label.errors or form.description.errors %}
<div class="alert alert-danger">
<strong>
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
@@ -70,9 +76,6 @@
{% if form.description.errors %}
{% set description_placeholder="Champ obligatoire" %}
{% endif %}
- {% if form.commit_message.errors %}
- {% set commit_placeholder="Champ obligatoire" %}
- {% endif %}
{{ form.label.label }} <br> {{ form.label(size=40, class="form-control", readonly=readonly, placeholder=label_placeholder) }} <br>
{{ form.description.label }} <br> {{ form.description(size=150, class="form-control", readonly=readonly, placeholder=description_placeholder) }} <br>
<label>Propriétés </label>
@@ -88,12 +91,16 @@
</select>
<input type="text" id="literal-field" class="hidden form-control">
<input type="text" id="uriref-link-field" placeholder="http://my-example.com" class="hidden form-control">
- <select id="uriref-category-field" class="hidden form-control">
+ <select class="hidden form-control" id="uriref-category-field">
<option value="default" selected> Liste des catégories </option>
{% for cat in cat_list %}
- <option value="{{ cat.cat_id }}"> {{ cat.cat_label }} </option>
+ <option value="{{ cat.cat_id }}"> {{ cat.cat_label }} </option>
{% endfor %}
</select>
+ <div class="alert alert-warning hidden" role="alert" id="uriref-category-field-text">
+ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+ Les catégories actuellement en suppression (voir onglet "Mes modifications") ne sont pas référençables
+ </div>
</div>
<div id="properties" class="row hidden">
<div class="row">
@@ -107,17 +114,19 @@
{% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
{% for cat in cat_list %}
{% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
- <input type="hidden" id="property_predicate{{ property_count }}" name="property_predicate" value="{{predicate}}"/>
- <input type="hidden" id="property_object{{ property_count }}" name="property_object" value="{{cat.cat_id}}"/>
- <td id="predicate_td{{ property_count }}">
- <strong>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</strong>
- </td>
- <td id="object_td{{ property_count }}">
- {{ cat.cat_label }}
- </td>
- <td id="delete_button_td{{ property_count }}">
- <input type="button" id="property_delete_button{{ property_count }}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties')" value="Supprimer">
- </td>
+ {% if cat.cat_id not in deleted_cat_list %}
+ <input type="hidden" id="property_predicate{{ property_count }}" name="property_predicate" value="{{predicate}}"/>
+ <input type="hidden" id="property_object{{ property_count }}" name="property_object" value="{{cat.cat_id}}"/>
+ <td id="predicate_td{{ property_count }}">
+ <strong>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</strong>
+ </td>
+ <td id="object_td{{ property_count }}">
+ {{ cat.cat_label }}
+ </td>
+ <td id="delete_button_td{{ property_count }}">
+ <input type="button" id="property_delete_button{{ property_count }}" class="btn btn-default property-delete-button" onClick="CatEditScripts.removeProperty({{ property_count }}, 'properties')" value="Supprimer">
+ </td>
+ {% endif %}
{% endif %}
{% endfor %}
{% else %}
@@ -145,7 +154,6 @@
</div>
</div>
</div><br>
- {{ form.commit_message.label }} <br> {{ form.commit_message(size=150, class="form-control", readonly=readonly, placeholder=description_placeholder) }} <br>
<br><input type="submit" value="Sauvegarder" class="btn btn-default">
</form>
{% if readonly %} </fieldset> {% endif %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/catedit/templates/catmodifs.html Mon Dec 29 18:01:09 2014 +0100
@@ -0,0 +1,348 @@
+{% if not session["user_logged"] or not session["user_can_edit"] %}
+ {% set readonly="readonly" %}
+{% else %}
+ {% set readonly=False %}
+{% endif %}
+<!DOCTYPE html>
+<html lang="fr">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Mes modifications</title>
+ <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
+ <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
+ <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
+ <script>
+ $(document).ready(function(){
+ {% for cat in cat_list %}
+ $("#properties_{{cat.cat_id}}").hide();
+ $("#info_button_{{cat.cat_id}}").click(function(){
+ $("#properties_{{cat.cat_id}}").slideToggle();
+ });
+ $("#delete_cat_{{cat.cat_id}}").hide();
+ $("#delete_button_{{cat.cat_id}}").click(function(){
+ $("#delete_cat_{{cat.cat_id}}").slideToggle();
+ });
+ {% endfor %}
+ {% for cat in modified_cat_list %}
+ $("#properties_modified_{{cat.cat_id}}").hide();
+ $("#info_button_modified_{{cat.cat_id}}").click(function(){
+ $("#properties_modified_{{cat.cat_id}}").slideToggle();
+ });
+ $("#delete_modified_{{cat.cat_id}}").hide();
+ $("#remove_modifs_modified_{{cat.cat_id}}").click(function(){
+ $("#delete_modified_{{cat.cat_id}}").slideToggle();
+ });
+ {% endfor %}
+ {% for cat in created_cat_list %}
+ $("#properties_created_{{cat.cat_id}}").hide();
+ $("#info_button_created_{{cat.cat_id}}").click(function(){
+ $("#properties_created_{{cat.cat_id}}").slideToggle();
+ });
+ $("#delete_created_{{cat.cat_id}}").hide();
+ $("#remove_modifs_created_{{cat.cat_id}}").click(function(){
+ $("#delete_created_{{cat.cat_id}}").slideToggle();
+ });
+ {% endfor %}
+ });
+ </script>
+ </head>
+ <body>
+ <div class="navbar navbar-inverse" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ <a class="navbar-brand" href="{{ url_for('cat_recap') }}">
+ <img alt="Brand" src="{{ url_for('static', filename='img/catedit_brand.png') }}" class="navbar-img" width="32" height="32">
+ </a>
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ </div>
+ <div class="collapse navbar-collapse">
+ <ul class="nav navbar-nav">
+ <li><a href="#">Page d'accueil</a></li>
+ {% if session.get("user_logged", None) %}
+ <li><a href="{{ url_for('cat_recap') }}">Vue d'ensemble</a></li>
+ <li><a href="{{ url_for('cat_editor') }}">Créer une catégorie</a></li>
+ <li class="active"><a>Mes modifications</a></li>
+ {% endif %}
+ </ul>
+ <div class="navbar-text navbar-right">
+ {% if not session.get("user_logged", None)%}Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
+ {% else %} Authentifié: {{ session["user_login"] }} - <a href="{{ url_for('logout') }}" class="navbar-link">Quitter</a>{% endif %}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="container">
+ <h2> Créer une catégorie <a href="{{url_for('cat_editor')}}" class="btn btn-default {% if readonly %}disabled{% endif %}"><span class="glyphicon glyphicon-plus"/></a></h2>
+ <h2> Editer ou supprimer une catégorie </h2>
+ <table class="table table-bordered table-condensed">
+ <thead>
+ <tr class="active">
+ <th class="col-md-2"><b>Nom de la catégorie</b></th>
+ <th class="col-md-10" colspan="2"><b>Description de la catégorie</b></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% if not session["user_logged"] %}
+ <tr>
+ <td class="col-md-12" colspan="3">
+ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+ <span class="sr-only">Attention:</span>
+ Veuillez vous identifier pour visualiser les catégories
+ </td>
+ </tr>
+ {% else %}
+ {% if cat_list|length == 0 %}
+ <tr>
+ <td class="col-md-12" colspan="3">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('cat_editor') }}">Créer une catégorie</a>{% endif %}</td>
+ </tr>
+ {% else %}
+ {% for cat in cat_list %}
+ <tr>
+ <td class="col-md-2">{{ cat.cat_label }}</td>
+ <td class="col-md-8">{{ cat.cat_description}}</td>
+ <td class="col-md-2 text-center">
+ <button class="btn btn-default" id="info_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon glyphicon-plus-sign"/></button>
+ {% if cat.cat_id not in deleted_cat_list %}
+ <a href="{{ url_for('cat_editor', cat_id=cat.cat_id)}}" class="btn btn-default"><span class="glyphicon glyphicon glyphicon-pencil"/></a>
+ <button class="btn btn-default" id="delete_button_{{cat.cat_id}}"><span class="glyphicon glyphicon-trash"/></a>
+ {% endif %}
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div id="properties_{{cat.cat_id}}">
+ <dl class="dl-horizontal">
+ {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
+ {% else %}
+ {% for (predicate, object) in cat.cat_properties %}
+ <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
+ <dd>
+ {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
+ {% for cat in cat_list %}
+ {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
+ {{ cat.cat_label }}
+ {% endif %}
+ {% endfor %}
+ {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
+ <a href="{{ object }}">{{ object }}</a>
+ {% else %}
+ {{ object }}
+ {% endif %}
+ </dd>
+ {% endfor %}
+ {% endif %}
+ </dl>
+ </div>
+ <div id="delete_cat_{{cat.cat_id}}">
+ <form method="POST" action="{{ url_for('cat_modifs', delete_cat_id=cat.cat_id) }}" class="form-inline align-center">
+ <fieldset {% if readonly %}disabled{% endif %}>
+ <div class="input-group">
+ <div class="input-group-addon">
+ Vous devrez soumettre les changements pour appliquer la suppression. Supprimer une catégorie édite automatiquement les autres catégories qui y font référence.
+ </div>
+ <input type="submit" class="btn btn-default" value="Supprimer">
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+ </tbody>
+ </table>
+
+ <h2> Mes modifications </h2>
+ <table class="table table-bordered table-condensed">
+ <thead>
+ <tr class="active">
+ <th class="col-md-2"><b>Nom de la catégorie</b></th>
+ <th class="col-md-10" colspan="2"><b>Description de la catégorie</b></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% if not session["user_logged"] %}
+ <tr>
+ <td class="col-md-12" colspan="3">
+ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+ <span class="sr-only">Attention:</span>
+ Veuillez vous identifier pour visualiser les modifications
+ </td>
+ </tr>
+ {% else %}
+ <tr class="success">
+ <td class="col-md-12" colspan="3">
+ <b> Catégories ajoutées</b>
+ </td>
+ </tr>
+ {% if created_cat_list|length == 0 %}
+ <tr>
+ <td class="col-md-12" colspan="3">Aucune catégorie n'a été ajoutée pour l'instant.</td>
+ </tr>
+ {% else %}
+ {% for cat in created_cat_list %}
+ <tr class="success">
+ <td class="col-md-2">{{ cat.cat_label }}</td>
+ <td class="col-md-9">{{ cat.cat_description}}</td>
+ <td class="col-md-1 text-center">
+ <button class="btn btn-default" id="info_button_created_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button>
+ <button class="btn btn-default" id="remove_modifs_created_{{ cat.cat_id }}"><span class="glyphicon glyphicon-remove-sign"/></button>
+ </td>
+ </tr>
+ <tr class="success">
+ <td colspan="3">
+ <div id="properties_created_{{cat.cat_id}}">
+ <dl class="dl-horizontal">
+ {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
+ {% else %}
+ {% for (predicate, object) in cat.cat_properties %}
+ <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
+ <dd>
+ {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
+ {% for cat in cat_list %}
+ {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
+ {{ cat.cat_label }}
+ {% endif %}
+ {% endfor %}
+ {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
+ <a href="{{ object }}">{{ object }}</a>
+ {% else %}
+ {{ object }}
+ {% endif %}
+ </dd>
+ {% endfor %}
+ {% endif %}
+ </dl>
+ </div>
+ <div id="delete_created_{{cat.cat_id}}">
+ <form method="POST" action="{{ url_for('cat_modifs', delete_modifs_id=cat.cat_id) }}" class="form-inline align-center">
+ <fieldset {% if readonly %}disabled{% endif %}>
+ <div class="input-group">
+ <div class="input-group-addon">
+ Vous allez supprimer les changements faits sur cette catégorie.
+ </div>
+ <input type="submit" class="btn btn-default" value="Supprimer">
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ {% endif %}
+ <tr class="warning">
+ <td class="col-md-12" colspan="3">
+ <b> Catégories modifiées</b>
+ </td>
+ </tr>
+ {% if modified_cat_list|length == 0 %}
+ <tr>
+ <td class="col-md-12" colspan="3">Aucune catégorie n'a été modifiée pour l'instant.</td>
+ </tr>
+ {% else %}
+ {% for cat in modified_cat_list %}
+ <tr class="warning">
+ <td class="col-md-2">{{ cat.cat_label }}</td>
+ <td class="col-md-9">{{ cat.cat_description}}</td>
+ <td class="col-md-1 text-center">
+ <button class="btn btn-default" id="info_button_modified_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button>
+ <button class="btn btn-default" id="remove_modifs_modified_{{ cat.cat_id }}"><span class="glyphicon glyphicon-remove-sign"/></button>
+ </td>
+ </tr>
+ <tr class="warning">
+ <td colspan="3">
+ <div id="properties_modified_{{cat.cat_id}}">
+ <dl class="dl-horizontal">
+ {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
+ {% else %}
+ {% for (predicate, object) in cat.cat_properties %}
+ <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
+ <dd>
+ {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
+ {% for cat in cat_list %}
+ {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
+ {{ cat.cat_label }}
+ {% endif %}
+ {% endfor %}
+ {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
+ <a href="{{ object }}">{{ object }}</a>
+ {% else %}
+ {{ object }}
+ {% endif %}
+ </dd>
+ {% endfor %}
+ {% endif %}
+ </dl>
+ </div>
+ <div id="delete_modified_{{cat.cat_id}}">
+ <form method="POST" action="{{ url_for('cat_modifs', delete_modifs_id=cat.cat_id) }}" class="form-inline align-center">
+ <fieldset {% if readonly %}disabled{% endif %}>
+ <div class="input-group">
+ <div class="input-group-addon">
+ Vous allez supprimer les changements faits sur cette catégorie.
+ </div>
+ <input type="submit" class="btn btn-default" value="Supprimer">
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ {% endif %}
+
+ <tr class="danger">
+ <td class="col-md-12" colspan="3">
+ <b> Catégories supprimées</b>
+ </td>
+ </tr>
+ {% if deleted_cat_list|length == 0 %}
+ <tr>
+ <td class="col-md-12" colspan="3">Aucune catégorie n'a été supprimée pour l'instant.</td>
+ </tr>
+ {% else %}
+ {% for deleted_cat in deleted_cat_list %}
+ {% for existing_cat in cat_list %}
+ {% if existing_cat.cat_id == deleted_cat %}
+ <tr class="danger">
+ <td class="col-md-2">{{ existing_cat.cat_label }}</td>
+ <td class="col-md-9"><i>Cette catégorie va être supprimée quand vous soumettrez vos modifications.</i></td>
+ <td class="col-md-1 text-center">
+ <form method="POST" action="{{ url_for('cat_modifs', delete_cat_id=deleted_cat) }}">
+ <fieldset {% if readonly %}disabled{% endif %}>
+ <input type="submit" class="btn btn-default" value="Restaurer">
+ </fieldset>
+ </form>
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+ </tbody>
+ </table>
+ <h2> Soumettre mes changements </h2>
+ <div class="col-md-12">
+ <form method="POST" action="{{ url_for('cat_modifs')}}">
+ <fieldset {% if readonly %}disabled{% endif %}>
+ {{ commit_form.hidden_tag() }}
+ <div class="form-group">
+ {{ commit_form.commit_message.label }}
+ {{ commit_form.commit_message(size=40, class="form-control", readonly=readonly) }}
+ </div>
+ <button type="submit" class="btn btn-default">Soumettre modifications</button>
+ </fieldset>
+ </form><br>
+ </div>
+ </div>
+ </body>
+</html>
--- a/src/catedit/templates/catrecap.html Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/templates/catrecap.html Mon Dec 29 18:01:09 2014 +0100
@@ -9,7 +9,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title>Liste des catégories</title>
+ <title>Vue d'ensemble</title>
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}" language="Javascript" type="text/javascript"></script>
@@ -20,10 +20,6 @@
$("#info_button_{{cat.cat_id}}").click(function(){
$("#properties_{{cat.cat_id}}").slideToggle();
});
- $("#delete_cat_{{cat.cat_id}}").hide();
- $("#delete_button_{{cat.cat_id}}").click(function(){
- $("#delete_cat_{{cat.cat_id}}").slideToggle();
- });
{% endfor %}
});
</script>
@@ -32,20 +28,24 @@
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
+ <a class="navbar-brand" href="{{ url_for('cat_recap') }}">
+ <img alt="Brand" src="{{ url_for('static', filename='img/catedit_brand.png') }}" class="navbar-img" width="32" height="32">
+ </a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" id="logo" href="{{ url_for('cat_recap') }}">
- <span class="glyphicon glyphicon-picture"/>
- </a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
- <li class="active"><a>Liste des catégories</a></li>
- <li><a href="{{ url_for('cat_editor') }}">Editeur de catégorie: Création</a></li>
+ <li><a href="#">Page d'accueil</a></li>
+ {% if session.get("user_logged", None) %}
+ <li class="active"><a>Vue d'ensemble</a></li>
+ <li><a href="{{ url_for('cat_editor') }}">Créer une catégorie</a></li>
+ <li><a href="{{ url_for('cat_modifs') }}">Mes modifications</a></li>
+ {% endif %}
</ul>
<div class="navbar-text navbar-right">
{% if not session.get("user_logged", None)%}Non authentifié - <a href="{{ url_for('github_login') }}" class="navbar-link">S'authentifier</a>
@@ -66,63 +66,68 @@
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
- <th class="col-md-2"><b>Category Label</b></th>
- <th class="col-md-10" colspan="2"><b>Category Description</b></th>
+ <th class="col-md-2"><b>Nom de la catégorie</b></th>
+ <th class="col-md-10" colspan="2"><b>Description de la catégorie</b></th>
</tr>
</thead>
<tbody>
- {% if cat_list|length == 0 %}
- <tr>
- <td class="col-md-12" colspan="3">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('cat_editor') }}">Créer une catégorie</a>{% endif %}</td>
- </tr>
+ {% if not session["user_logged"] %}
+ <tr>
+ <td class="col-md-12" colspan="3">
+ <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+ <span class="sr-only">Attention:</span>
+ Veuillez vous identifier pour visualiser les catégories
+ </td>
+ </tr>
{% else %}
- {% for cat in cat_list %}
+ {% if cat_list|length == 0 %}
<tr>
- <td class="col-md-2">{{ cat.cat_label }}</td>
- <td class="col-md-8">{{ cat.cat_description}}</td>
- <td class="col-md-2 text-right">
- <button class="btn btn-default" id="info_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus"/></button>
- <a href="{{ url_for('cat_editor', cat_id=cat.cat_id)}}" class="btn btn-default"><span class="glyphicon glyphicon glyphicon-pencil"/></a>
- <button class="btn btn-default" id="delete_button_{{cat.cat_id}}"><span class="glyphicon glyphicon-trash"/></a>
- </td>
+ <td class="col-md-12" colspan="3">Aucune catégorie n'a été créée pour l'instant. {% if not readonly %}<a href="{{ url_for('cat_editor') }}">Créer une catégorie</a>{% endif %}</td>
</tr>
+ {% else %}
+ {% for cat in cat_list %}
<tr>
- <td colspan="3">
- <div id="properties_{{cat.cat_id}}">
- <dl class="dl-horizontal">
- {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
- {% else %}
- {% for (predicate, object) in cat.cat_properties %}
- <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
- <dd>
- {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
- {% for cat in cat_list %}
- {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
- {{ cat.cat_label }}
- {% endif %}
- {% endfor %}
- {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
- <a href="{{ object }}">{{ object }}</a>
- {% else %}
- {{ object }}
- {% endif %}
- </dd>
- {% endfor %}
- {% endif %}
- </dl>
- </div>
- <div id="delete_cat_{{cat.cat_id}}">
- <form method="POST" action="{{ url_for('cat_recap', delete_cat_id=cat.cat_id) }}" class="form-inline align-center">
- <input type="text" style="width: 90%" name="delete_message" class="form-control" placeholder="Message de suppression">
- <input type="submit" class="btn btn-default" value="Supprimer">
- </form>
- </div>
+ <td class="col-md-2">{{ cat.cat_label }}</td>
+ <td class="col-md-9">{{ cat.cat_description}}</td>
+ <td class="col-md-1 text-center">
+ <button class="btn btn-default" id="info_button_{{ cat.cat_id }}"><span class="glyphicon glyphicon-plus-sign"/></button>
</td>
</tr>
- {% endfor %}
+ <tr>
+ <td colspan="3">
+ <div id="properties_{{cat.cat_id}}">
+ <dl class="dl-horizontal">
+ {% if cat.cat_properties|length == 0 %} <dt></dt><dd>Aucune autre propriété</dd>
+ {% else %}
+ {% for (predicate, object) in cat.cat_properties %}
+ <dt>{{ config["PROPERTY_LIST"][predicate]["descriptive_label_fr"] }}</dt>
+ <dd>
+ {% if config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-category" %}
+ {% for cat in cat_list %}
+ {% if object == config["CATEGORY_NAMESPACE"]+cat.cat_id %}
+ {{ cat.cat_label }}
+ {% endif %}
+ {% endfor %}
+ {% elif config["PROPERTY_LIST"][predicate]["object_type"]=="uriref-link" %}
+ <a href="{{ object }}">{{ object }}</a>
+ {% else %}
+ {{ object }}
+ {% endif %}
+ </dd>
+ {% endfor %}
+ {% endif %}
+ </dl>
+ </div>
+ </td>
+ </tr>
+ {% endfor %}
+ {% endif %}
{% endif %}
</tbody>
- </table><br>
+ </table>
+ {% if session.get("user_logged") %}
+ <h3> Consulter l'historique des modifications : <a href="https://github.com/{{config['REPOSITORY_OWNER']}}/{{config['REPOSITORY_NAME']}}/commits/master" class="btn btn-default"><span class="glyphicon glyphicon-list"/></a></h3>
+ {% endif %}
</div>
</body>
</html>
--- a/src/catedit/views.py Thu Dec 18 12:07:03 2014 +0100
+++ b/src/catedit/views.py Mon Dec 29 18:01:09 2014 +0100
@@ -5,6 +5,7 @@
from catedit import app, github
from catedit.models import Category
+import catedit.persistence
from flask import render_template, request, redirect, url_for, session
from flask.ext.github import GitHubError
from flask_wtf import Form
@@ -14,7 +15,7 @@
from rdflib import Graph
from io import StringIO
-LOGGER = app.logger
+logger = app.logger
class NewCategoryMinimalForm(Form):
@@ -30,16 +31,19 @@
"Description de la categorie (obligatoire)",
validators=[DataRequired()]
)
+
+class CommitForm(Form):
+ """
+ Custom form class for commiting changes
+ """
commit_message = StringField(
- "Message de soumission (Obligatoire)",
+ "Message de soumission (obligatoire)",
validators=[DataRequired()]
)
-
-@app.route('/catrecap/delete-<delete_cat_id>', methods=['POST'])
-@app.route('/', defaults={'delete_cat_id': None}, methods=['GET'])
-@app.route('/catrecap', defaults={'delete_cat_id': None}, methods=['GET'])
-def cat_recap(delete_cat_id):
+@app.route('/', methods=['GET'])
+@app.route('/catrecap', methods=['GET'])
+def cat_recap():
"""
View that has a list of all categories available. Template is
catrecap.html, located in src/templates/
@@ -47,40 +51,10 @@
Note: it also handles category deletion from the same page.
"""
cat_api_instance = CategoryAPI()
- if delete_cat_id is None:
- LOGGER.debug("Category to delete is None")
+ serialized_cat_list = []
+ if session.get("user_logged", None) is not None:
serialized_cat_list = cat_api_instance.get()
- # LOGGER.debug(serialized_cat_list)
- cat_list = []
- 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)
-
- cat_list.append({"cat_label": cat.label,
- "cat_description": cat.description,
- "cat_id": cat.cat_id,
- "cat_properties": cat.properties})
- # LOGGER.debug(c.properties)
- return render_template('catrecap.html',
- cat_list=cat_list)
- else:
- LOGGER.debug("Category "+delete_cat_id+" will be deleted.")
- cat_api_instance.delete(delete_cat_id)
- return redirect(url_for('cat_recap'))
-
-
-@app.route('/cateditor', methods=['GET', 'POST'])
-@app.route('/cateditor/<string:cat_id>', methods=['GET', 'POST'])
-def cat_editor(cat_id=None):
- """
- View that handles creation and edition of categories. Template is
- cateditor.html, located in src/templates
- """
- cat_api_instance = CategoryAPI()
-
- serialized_cat_list = cat_api_instance.get()
+ # logger.debug(serialized_cat_list)
cat_list = []
for serialized_cat in serialized_cat_list:
cat_rdf_graph = Graph()
@@ -92,6 +66,36 @@
"cat_description": cat.description,
"cat_id": cat.cat_id,
"cat_properties": cat.properties})
+ # logger.debug(c.properties)
+ return render_template('catrecap.html',
+ cat_list=cat_list)
+
+
+@app.route('/cateditor', methods=['GET', 'POST'])
+@app.route('/cateditor/<string:cat_id>', methods=['GET', 'POST'])
+def cat_editor(cat_id=None):
+ """
+ View that handles creation and edition of categories. Template is
+ cateditor.html, located in src/templates
+ """
+ cat_api_instance = CategoryAPI()
+ cat_list = []
+ deleted_cat_list=[]
+ if (session.get("user_logged",None) is not None):
+ serialized_cat_list = cat_api_instance.get()
+ deleted_cat_list = [element["name"] for \
+ element in session["deleted_categories"]]
+ 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 [element["name"] for element \
+ in session["deleted_categories"]]:
+ cat_list.append({"cat_label": cat.label,
+ "cat_description": cat.description,
+ "cat_id": cat.cat_id,
+ "cat_properties": cat.properties})
if cat_id is not None:
specific_serialized_cat = cat_api_instance.get(cat_id)
@@ -111,7 +115,7 @@
TextAreaField("Description de la categorie",
validators=[DataRequired()],
default=cat.description))
- LOGGER.debug("CatForm fields preset to "
+ logger.debug("CatForm fields preset to "
+ cat.label + " and "
+ cat.description)
@@ -123,18 +127,25 @@
cat_id=cat.cat_id,
cat_properties=cat.properties,
form=cat_form,
- cat_list=cat_list)
+ cat_list=cat_list,
+ deleted_cat_list=deleted_cat_list)
- # PUT + cat_id = Submit edited cat
- if cat_form.validate_on_submit():
- cat_api_instance.put(cat_id)
- return redirect(url_for('cat_recap'))
+ # PUT + cat_id = Submit edited cat, only if cat is not already
+ # in deleted categories
+ if cat_form.validate_on_submit() and \
+ cat.cat_id not in [element["name"] for element \
+ in session["deleted_categories"]]:
+ if (session.get("user_logged", None) is not None and
+ session.get("user_can_edit", False) is not False):
+ cat_api_instance.put(cat_id)
+ return redirect(url_for('cat_modifs'))
else:
return render_template('cateditor.html',
cat_id=cat_id,
cat_properties=cat.properties,
form=cat_form,
- cat_list=cat_list)
+ cat_list=cat_list,
+ deleted_cat_list=deleted_cat_list)
else:
setattr(NewCategoryMinimalForm,
@@ -152,17 +163,104 @@
if request.method == 'GET':
return render_template('cateditor.html',
form=cat_form,
- cat_list=cat_list)
+ cat_list=cat_list,
+ deleted_cat_list=deleted_cat_list)
# POST seul = Submit created cat
if cat_form.validate_on_submit():
- cat_api_instance.post()
- return redirect(url_for('cat_recap'))
+ if (session.get("user_logged", None) is not None and
+ session.get("user_can_edit", False) is not False):
+ cat_api_instance.post()
+ return redirect(url_for('cat_modifs'))
else:
return render_template('cateditor.html',
form=cat_form,
- cat_list=cat_list)
+ cat_list=cat_list,
+ deleted_cat_list=deleted_cat_list)
+
+@app.route('/catrecap/delete-modifs-<delete_modifs_id>',
+ defaults={'delete_cat_id': None},
+ methods=['POST'])
+@app.route('/catrecap/delete-<delete_cat_id>',
+ defaults={'delete_modifs_id': None},
+ methods=['POST'])
+@app.route('/catmodifs',
+ defaults={'delete_cat_id': None, 'delete_modifs_id': None},
+ methods=['GET', 'POST'])
+def cat_modifs(delete_cat_id, delete_modifs_id):
+ cat_list = []
+ modified_cat_list = []
+ deleted_cat_list = []
+ created_cat_list = []
+ cat_api_instance = CategoryAPI()
+ if delete_cat_id is None and delete_modifs_id is None:
+ commit_form = CommitForm(request.form)
+ if request.method=="GET":
+ if session.get("user_logged", None) is not None:
+ serialized_cat_list = cat_api_instance.get()
+ 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)
+ cat_list.append({"cat_label": cat.label,
+ "cat_description": cat.description,
+ "cat_id": cat.cat_id,
+ "cat_properties": cat.properties})
+ for modified_category in session["modified_categories"]:
+ modified_cat_rdf_graph = Graph()
+ modified_cat_rdf_graph.parse(
+ source=StringIO(
+ modified_category["content"]
+ ),
+ format='turtle'
+ )
+ modified_cat = Category(graph=modified_cat_rdf_graph)
+ if modified_cat.cat_id in [existing_cat["cat_id"] \
+ for existing_cat in cat_list]:
+ modified_cat_list.append(
+ {"cat_label": modified_cat.label,
+ "cat_description": modified_cat.description,
+ "cat_id": modified_cat.cat_id,
+ "cat_properties": modified_cat.properties}
+ )
+ else:
+ created_cat_list.append(
+ {"cat_label": modified_cat.label,
+ "cat_description": modified_cat.description,
+ "cat_id": modified_cat.cat_id,
+ "cat_properties": modified_cat.properties}
+ )
+ deleted_cat_list = [element["name"] for element \
+ in session["deleted_categories"]]
+ return render_template('catmodifs.html',
+ cat_list=cat_list,
+ created_cat_list=created_cat_list,
+ modified_cat_list=modified_cat_list,
+ deleted_cat_list=deleted_cat_list,
+ commit_form=commit_form)
+ elif request.method=="POST":
+ logger.debug("Submitting changes")
+ if commit_form.validate_on_submit():
+ logger.debug("Form validates")
+ cat_api_instance.put()
+ return redirect(url_for('cat_modifs'))
+ else:
+ if request.method=="POST":
+ if delete_modifs_id is not None:
+ for element in session["modified_categories"]:
+ if element["name"] == delete_modifs_id:
+ logger.debug("Modification for "
+ + delete_modifs_id
+ + " will be deleted.")
+ session["modified_categories"].remove(element)
+ if delete_cat_id is not None:
+ logger.debug("Category "+delete_cat_id+" will be deleted.")
+ if (session.get("user_logged", None) is not None and
+ session.get("user_can_edit", False) is not False):
+ cat_api_instance.delete(delete_cat_id)
+ return redirect(url_for('cat_modifs'))
@app.route('/catedit-github-login')
def github_login():
@@ -173,6 +271,11 @@
in local files, used for debugging), creates a mock user named
"FileEditUser"
"""
+ if getattr(catedit.persistence,
+ app.config["PERSISTENCE_METHOD"])().session_compliant is True:
+ session["save_user_changes"] = True
+ session["modified_categories"] = []
+ session["deleted_categories"] = []
if app.config["PERSISTENCE_METHOD"] == "PersistenceToGithub":
return github.authorize(scope="repo")
elif app.config["PERSISTENCE_METHOD"] == "PersistenceToFile":
@@ -196,16 +299,16 @@
repo_list = []
repo_list = github.get("user/repos")
for repo in repo_list:
- LOGGER.debug(repo["name"])
+ logger.debug(repo["name"])
session["user_can_edit"] = True
if not any(repo["name"] == app.config["REPOSITORY_NAME"]
for repo in repo_list):
session["user_can_edit"] = False
- LOGGER.debug(session["user_can_edit"])
+ logger.debug(session["user_can_edit"])
except GitHubError:
- LOGGER.debug("error getting repos!")
+ logger.debug("error getting repos!")
- LOGGER.debug(session["user_login"])
+ logger.debug(session["user_login"])
return redirect(url_for('cat_recap'))
@@ -216,7 +319,7 @@
making authenticated requests
"""
if session.get("user_logged", None):
- # LOGGER.debug("I made an authentified request")
+ # logger.debug("I made an authentified request")
return session["user_code"]
@@ -233,4 +336,7 @@
session["user_logged"] = None
session["user_login"] = None
session["user_can_edit"] = None
+ session["save_user_changes"] = None
+ session["modified_categories"] = []
+ session["deleted_categories"] = []
return redirect(url_for('cat_recap'))