src/catedit/resources.py
author ymh <ymh.work@gmail.com>
Wed, 15 Apr 2015 03:34:58 +0200
changeset 105 7bf8b028ccc1
parent 103 ef02353dff20
child 123 1e1c8f98f3a8
permissions -rw-r--r--
do full cache clear

"""
resources.py:
contains the api that links the views (views.py) to the model (models.py) and
its persistence method (persistence.py). As it only trades rdf graphs
serializations strings, it isn't bound to one specific view
"""


from catedit import app, cache, github
from catedit.models import Category, CategoryManager
import catedit.persistence
from catedit.tasks import submit_changes
from catedit.utils import get_property_list
from io import StringIO

from flask import session
from rdflib import Graph, URIRef, Literal

from flask.ext.restful import Resource, reqparse


logger = app.logger


class CategoryAPI(Resource):
    """
        The API to create and edit categories, returns rdf graph serializations
        when successful
    """
    def get(self, repository, cat_id=""):
        """
            API to get the category of id cat_id, or if cat_id is None,
            get the list of category

            The result of this API function goes into the Flask Cache.
        """

        # Cache key for this function and parameters
        cache_key = "categoryapi_get_" + repository + "_" + cat_id

        # Cache key for category key registry for this repo
        registry_key = "categories_"+repository+"_keys"
        if (cache.get(registry_key) is None or cache_key not in cache.get(registry_key)) and cache.get(cache_key) is None:
            rv = None
            cat_manager_instance = CategoryManager(
                getattr(
                    catedit.persistence,
                    app.config["PERSISTENCE_CONFIG"]["METHOD"]
                )(repository=repository),
            )
            if cat_id != "":
                cat = cat_manager_instance.load_category(cat_id)
                if cat is not None:
                    rv = cat.cat_graph.serialize(
                        format='turtle'
                    ).decode("utf-8"), 200
                else:
                    rv = 404
            else:
                response = []
                for cat in cat_manager_instance.list_categories():
                    response.append(
                        cat.cat_graph.serialize(format='turtle').decode("utf-8")
                    )
                rv = response, 200
            # Cache operations
            # Setting key for this function
            cache.set(cache_key, rv, timeout=3600)
            # Setting key in the registry
            if cache.get(registry_key) is None:
                cache.set(registry_key, [cache_key])
            else:
                if cache_key not in cache.get(registry_key):
                    key_list = cache.get(registry_key)
                    key_list.append(cache_key)
                    cache.set(registry_key, key_list)
            return rv
        else:
            return cache.get(cache_key)

    # update category cat_id
    def put(self, repository, cat_id=None, cat_data=None, message=""):
        """
            API to edit an existing category

            Args are:
            * repository: the repository where the category cat_id is stored
            * cat_id : the id of the category to edit
            * cat_data : the new data of the category, dict of the form:
            {
                "label": edited_cat_label,
                "description": edited_cat_description,
                "properties": [(predicate1, object1), (predicate2, object2)]
            }
            List of predicate is available in config.py, key PROPERTY_LIST

            Note: If cat_id is None and persistence support change sets, will
            submit all changes to category list
        """
        cat_manager_instance = CategoryManager(
            getattr(
                catedit.persistence,
                app.config["PERSISTENCE_CONFIG"]["METHOD"]
            )(repository=repository),
        )
        if cat_id is None:
            if cat_manager_instance.persistence.session_compliant is True:
                task=submit_changes.apply_async(
                    kwargs={
                        "deleted_categories" : session.setdefault(
                            "deleted_categories", {}
                        ).setdefault(repository, {}),
                        "modified_categories" : session.setdefault(
                            "modified_categories", {}
                        ).setdefault(repository, {}),
                        "message" : message,
                        "repository" : repository,
                        "token" : session.get("user_code")
                    },
                    queue="repo_"+repository,
                    routing_key="task_for_"+repository
                )

                session.setdefault("tasks", {}).setdefault(repository, []).append(task.id)
                session.setdefault("deleted_categories", {})[repository] = {}
                session.setdefault("modified_categories", {})[repository] = {}
                return 204
        else:
            # is the edition occuring on an already modified category?
            if (cat_id in session.setdefault(
                    "modified_categories", {}
            ).setdefault(repository, {}).keys()):
                cat_graph = Graph()
                cat_graph.parse(
                    source=StringIO(
                        session["modified_categories"][repository][cat_id]
                    ),
                    format="turtle"
                )
                cat = Category(graph=cat_graph)
            else:
                cat = cat_manager_instance.load_category(cat_id)

            new_property_list=[]
            for (predicate, obj) in cat_data["properties"]:
                if get_property_list()[repository].get(predicate, {}).get("object_type") == "uriref-category":
                    new_property_list.append(
                        (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), app.config["CATEGORY_NAMESPACE"][obj])
                    )
                    new_property_list.append(
                        (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), URIRef(obj))
                    )
                else:
                    new_property_list.append(
                        (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), Literal(obj))
                    )

            cat.edit_category(new_description=cat_data["description"],
                              new_label=cat_data["label"],
                              new_other_properties=new_property_list)

            session.setdefault("modified_categories", {}).setdefault(repository, {})[cat.cat_id] = str(
                cat.cat_graph.serialize(format="turtle"), "utf-8"
            )

            # Now we must clean the deleted categories list in case the
            # modified category was deleted before being edited
            session.setdefault("deleted_categories", {})[repository] = {
                cat_name: cat_content for cat_name, cat_content
                in session.setdefault("deleted_categories", {}).setdefault(repository, {}).items()
                if cat_name != cat.cat_id
            }

        return 204

    def post(self, repository, cat_data):
        """
            API to create a new category

            Args are:
            * repository: the repository where the category cat_id is stored
            * cat_data : the new data of the category, dict of the form:
            {
                "label": edited_cat_label,
                "description": edited_cat_description,
                "properties": [(predicate1, object1), (predicate2, object2)]
            }
            List of predicate is available in config.py, key PROPERTY_LIST
        """
        property_list = []
        for (predicate, obj) in cat_data["properties"]:
            if get_property_list()[repository].get(predicate, {}).get("object_type", "") == "uriref-category": # faire des get
                property_list.append(
                    (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), app.config["CATEGORY_NAMESPACE"][obj])
                )
            elif get_property_list()[repository].get(predicate, {}).get("object_type", "") == "uriref-link":
                property_list.append(
                    (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), URIRef(obj))
                )
            else:
                property_list.append(
                    (URIRef(get_property_list()[repository][predicate]["rdflib_class"]), Literal(obj))
                )

        cat = Category(
            label=cat_data["label"],
            description=cat_data["description"],
            other_properties=property_list
        )



        if cat.cat_id not in session.setdefault("modified_categories", {}).setdefault(repository, {}):
            session.setdefault("modified_categories", {}).setdefault(repository, {})[cat.cat_id] = str(
                cat.cat_graph.serialize(format="turtle"), "utf-8"
            )

        return cat.cat_id, \
               cat.cat_graph.serialize(format='turtle').decode("utf-8"), \
               201

    def delete(self, repository, deleted_cat_id):
        """
            API to delete the category of id cat_id or restore it from the
            deletion list
        """
        # if cat_id is already in deleted categories, we restore it
        if deleted_cat_id in session.setdefault("deleted_categories", {}).setdefault(repository, {}):
            session.setdefault("deleted_categories", {})[repository] = {
                cat_name: cat_content for cat_name, cat_content
                in session["deleted_categories"][repository].items()
                if cat_name != deleted_cat_id
            }
            # warning, not safe if 2 files share the same name (or category id)
            # but that shouldn't happen
        else:
            session.setdefault("deleted_categories", {}).setdefault(repository, {})[deleted_cat_id] = "deleted"
            # Maybe we can store something more useful in there, such as
            # category content when deleted to restore last edit?
            # It would be nice to have symetry between modified and
            # deleted dicts

            # now we must clean the modified categories list in case the
            # deleted category was modified before
            session.setdefault("modified_categories", {})[repository] = {
                cat_name: cat_content for cat_name, cat_content
                in session["modified_categories"][repository].items()
                if cat_name != deleted_cat_id
            }
            # Now we also have to clean up categories that reference the
            # deleted category
            cat_manager_instance = CategoryManager(
                getattr(
                    catedit.persistence,
                    app.config["PERSISTENCE_CONFIG"]["METHOD"]
                )(repository=repository),
            )
            cat_list = cat_manager_instance.list_categories()
            # first we edit what was already modified before the deletion
            if (
                    deleted_cat_id in session.setdefault("deleted_categories", {}).setdefault(repository, {})
            ):
                for element in session.setdefault("modified_categories", {}) \
                                      .setdefault(repository, {}):
                    modified_cat_graph = Graph()
                    modified_cat_graph.parse(
                        source=StringIO(
                            session["modified_categories"][repository][element]
                        ),
                        format="turtle"
                    )
                    modified_cat = Category(graph=modified_cat_graph)
                    if (modified_cat.cat_id != app.config["CATEGORY_NAMESPACE"][deleted_cat_id]):
                        new_property_list = []
                        for (predicate, obj) in modified_cat.properties:
                            if not (
                                    get_property_list()[repository]
                                                       [predicate.toPython()]
                                                       ["object_type"]
                                    == "uriref-category"
                                    and (obj == app.config["CATEGORY_NAMESPACE"][deleted_cat_id])
                            ):
                                new_property_list.append((predicate, obj))

                        if new_property_list != modified_cat.properties:
                            logger.debug("Modifying modified category")
                            modified_cat.edit_category(
                                new_other_properties=new_property_list
                            )
                            session["modified_categories"] \
                                   [repository] \
                                   [modified_cat.cat_id] = str(
                                modified_cat.cat_graph
                                            .serialize(format="turtle"),
                                "utf-8"
                            )

                # now we check if an unmodified category reference the deleted
                # category
                for cat in cat_list:
                    if (cat.cat_id not in (
                            list(session.setdefault("modified_categories", {}).setdefault(repository, {}).keys())
                            + list(session.setdefault("deleted_categories", {}).setdefault(repository, {}).keys())
                    )):
                        new_property_list = []
                        for (predicate, obj) in cat.properties:
                            if not (
                                    get_property_list()[repository]
                                                       [predicate.toPython()]
                                                       ["object_type"]
                                    == "uriref-category"
                                    and (obj == app.config["CATEGORY_NAMESPACE"][deleted_cat_id])
                            ):
                                new_property_list.append((predicate, obj))

                        if new_property_list != cat.properties:
                            logger.debug("Modifying untouched category")
                            cat.edit_category(
                                new_other_properties=new_property_list
                            )
                            session.setdefault("modified_categories", {}).setdefault(repository, {})[cat.cat_id] = str(
                                cat.cat_graph.serialize(format="turtle"),
                                "utf-8"
                            )

        return 204


class CategoryChangesAPI(Resource):
    """
        API for getting and deleting category changes, returns a dict when
        succesful if category is a modified one, returns only the cat_id if it
        is a deleted one

        All changes and deletions are saved in
        session["modified_categories"][repository] and
        session["deleted_categories"][repository] under the format:
        {changed_cat_id: content_of_changed_cat}
    """
    def get(self, repository, modified_cat_id=None):
        """
            API to get the pending changes for category cat_id
        """

        if modified_cat_id is None:
            return {
                "modified_categories": session.setdefault("modified_categories", {})
                                              .setdefault(repository, {}),
                "deleted_categories": session.setdefault("deleted_categories", {})
                                             .setdefault(repository, {})
            }, 200
        else:
            if modified_cat_id in session.setdefault("modified_categories", {}) \
                                         .setdefault(repository, {}):
                return {
                    "type": "modified",
                    "category": {
                        modified_cat_id: session["modified_categories"]
                                                [repository]
                                                [modified_cat_id]
                    }
                }, 200
            if modified_cat_id in session.setdefault("deleted_categories", {}) \
                                         .setdefault(repository, {}):
                return {
                    "type": "deleted",
                    "category": {
                        modified_cat_id: session["deleted_categories"]
                                                [repository]
                                                [modified_cat_id]
                    }
                }, 200
            return {
                "type": "untouched",
                "category": modified_cat_id
            }, 200

    def delete(self, repository, modified_cat_id=None):
        """
            API to delete the category cat_id from the changelist or if cat_id
            is None, delete the whole changelist
        """
        if modified_cat_id is not None:
            session.setdefault("modified_categories", {})[repository] = {
                cat_name: cat_content for cat_name, cat_content
                in session.setdefault("modified_categories", {}).setdefault(repository, {}).items()
                if cat_name != modified_cat_id
            }
            session.setdefault("deleted_categories", {})[repository] = {
                cat_name: cat_content for cat_name, cat_content
                in session.setdefault("deleted_categories", {}).setdefault(repository, {}).items()
                if cat_name != modified_cat_id
            }
        else:
            session.setdefault("modified_categories", {})[repository] = {}
            session.setdefault("deleted_categories", {})[repository] = {}
        return 204