src/hdalab/views/profile.py
author ymh <ymh.work@gmail.com>
Thu, 12 Apr 2018 01:59:24 +0200
branchdocumentation
changeset 694 46da276fbb1b
parent 693 09e00f38d177
permissions -rw-r--r--
small doc corrections

# -*- coding: utf-8 -*-
'''
Created on Jul 01, 2014

@author: tc
'''

import json
import logging
import uuid
from datetime import datetime

from django.conf import settings
from django.contrib.auth import login as auth_login
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.sites.models import get_current_site
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import (HttpResponse, HttpResponseBadRequest,
                         HttpResponseRedirect)
from django.http.response import Http404
from django.shortcuts import get_object_or_404, redirect, resolve_url
from django.template.response import TemplateResponse
from django.templatetags.static import static
from django.utils.http import is_safe_url
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import TemplateView, View
from django.views.generic.edit import UpdateView
from hdabo.models import Datasheet, Folder, Tag, TaggedSheet
from hdalab.forms import HdalabRenkanFavoriteForm, HdalabRenkanStateForm
from hdalab.models.dataviz import DbpediaFieldsTranslation
from hdalab.models.renkan import HdalabRenkan
from hdalab.services import change_renkan_state
from hdalab.views.ajax import filter_generic
from renkanmanager.models import Renkan
from renkanmanager.utils import (CircleNodePlacer, HorLineNodePlacer,
                                 LineNodePlacer, renkan_copier, renkan_deleter)
from renkanmanager.views import RenkanGetPut

logger = logging.getLogger(__name__)


class BaseRenkanList(TemplateView):
    """
    Classe de vue permettant la gestion de l'affichage d'une liste de Renkan.
    Cette classe fourni une méthode gérant les filtres, le tri et la pagination.
    Cette classe de vue n'est pas directement utilisée mais est dérivé par des classe de vue concrêtes.

    Liste des paramêtres GET:

    +-----------+------------------------------------------------------------+--------+--------+
    | nom       | description                                                | type   | défaut |
    +===========+============================================================+========+========+
    | title     | Le titre du renkan contient la chaine de caractères.       | chaine | vide   |
    +-----------+------------------------------------------------------------+--------+--------+
    | username  | Le nom de l'utilisateur ayant créé le renkan.              | chaine | vide   |
    +-----------+------------------------------------------------------------+--------+--------+
    | state     | Le status du renkan est (c.f. _models/renkan/HdalabRenkan) | entier | (*)    |
    +-----------+------------------------------------------------------------+--------+--------+
    | startdate | La date de modification du renkan est supérieure à.        | epoch  | vide   |
    +-----------+------------------------------------------------------------+--------+--------+
    | enddate   | La date de modification du renkan est inférieure à.        | epoch  | vide   |
    +-----------+------------------------------------------------------------+--------+--------+
    | favorite  | Le renkan est marqué comme favori ou non (0 ou 1)          | 0/1    | (*)    |
    +-----------+------------------------------------------------------------+--------+--------+
    | sort      | Champ sur lequel se fait le tri:                           | chaine | date   |
    |           | - date : date de modification du renkan                    | /énum. |        |
    |           | - title : titre du renkan                                  |        |        |
    |           | - state : status du renkan (c.f _)                         |        |        |
    |           | - user : créateur du renkan                                |        |        |
    |           | - favorite : renkan marqué comme avori                     |        |        |
    +-----------+------------------------------------------------------------+--------+--------+
    | order     | ordre de tri :                                             | chaine | desc   |
    |           | - desc : tri descendant                                    | /énum. |        |
    |           | - asc : tris ascendant                                     |        |        |
    +-----------+------------------------------------------------------------+--------+--------+
    | page      | Le numéro de page pour la pagination                       | entier | 1      |
    |           | La première page est la page 1.                            |        |        |
    +-----------+------------------------------------------------------------+--------+--------+

    (*) : La valeur par défaut de ce paramètre dépend de la vue concrète.

    """

    default_sort_field = "date"
    default_sort_order = "desc"

    def update_context(self, context, renkan_queryset):

        filters = ""
        filter_title = self.request.GET.get("title", "")
        if(len(filter_title)>0):
            renkan_queryset = renkan_queryset.filter(renkan__title__icontains=filter_title)
            filters += "&title=" + filter_title
        filter_username = self.request.GET.get("username", "")
        if(len(filter_username)>0):
            renkan_queryset = renkan_queryset.filter(renkan__owner__username=filter_username)
            filters += "&username=" + filter_username
        filter_state = self.request.GET.get("state", "")
        if(len(filter_state)>0):
            try:
                filter_state = int(filter_state)
            except:
                pass
            renkan_queryset = renkan_queryset.filter(state=filter_state)
            filter_state = str(filter_state)
            filters += "&state=" + filter_state
        filter_startdate = self.request.GET.get("startdate", "")
        if filter_startdate!="":
            renkan_queryset = renkan_queryset.filter(renkan__modification_date__gt=filter_startdate)
            filters += "&startdate=" + filter_startdate
        filter_enddate = self.request.GET.get("enddate", "")
        if filter_enddate!="":
            renkan_queryset = renkan_queryset.filter(renkan__modification_date__lt=filter_enddate + " 23:59:59")
            filters += "&enddate=" + filter_enddate
        filter_favorite = int(self.request.GET.get('favorite', "2"))
        if filter_favorite == 1:
            renkan_queryset = renkan_queryset.filter(favorite=True)
        elif filter_favorite == 0:
            renkan_queryset = renkan_queryset.filter(favorite=False)

        sort_param = self.request.GET.get('sort', self.default_sort_field)
        order_param = self.request.GET.get('order', self.default_sort_order)
        sort = {"date":"renkan__modification_date", "title":"renkan__title", "state":"state", "user":"renkan__owner__username", "favorite":"favorite"}.get(sort_param, None)
        if order_param=="desc":
            order = "-"
            opposite = "asc"
        else:
            order = ""
            opposite = "desc"
        if sort:
            renkan_queryset = renkan_queryset.order_by(order + sort)
        p = Paginator(renkan_queryset, settings.RENKANS_PER_PAGE)
        page_nb = self.request.GET.get('page')
        try:
            page = p.page(page_nb)
        except PageNotAnInteger:
            page = p.page(1)
        except EmptyPage:
            page = p.page(p.num_pages)

        context.update({"page": page, "sort_param":sort_param, "order_param":order_param, "opposite":opposite,
                        "filters":filters, "title": filter_title, "username": filter_username, "state": filter_state,
                        "startdate":filter_startdate, "enddate":filter_enddate, "favorite": filter_favorite})

        return context



class ProfileHome(BaseRenkanList):
    """
    Page de profile d'un utilisateur.

    Cette vue étend la vue de base :class:`.BaseRenkanList`.
    """

    template_name = "profile_home.html"

    def get_context_data(self, **kwargs):
        return self.update_context( super(ProfileHome, self).get_context_data(**kwargs), HdalabRenkan.objects.select_related("renkan").filter(renkan__owner=self.request.user))


class RenkanPublicList(BaseRenkanList):
    """
    Page des renkan publics.

    La colonne de l'auteur du renkan n'est affiché que si l'utilisateur est membre de l'équipe.
    Cette list ne comprend pas les renkan marqués commme favoris. Le paramêtre "favorite" est ignoré.

    Cette vue étend la vue de base :class:`.BaseRenkanList`.
    """

    template_name = "renkan_list.html"

    def get_context_data(self, **kwargs):
        context = super(RenkanPublicList, self).get_context_data(**kwargs)
        #Liste des renkans publics
        renkan_list = HdalabRenkan.objects

        context['hide_favorite'] = True
        context['show_username'] = self.request.user.is_staff
        if self.request.user.is_staff:
            renkan_list = renkan_list.select_related("renkan", "renkan__owner")
        else:
            renkan_list = renkan_list.select_related("renkan")

        return self.update_context(context, renkan_list.filter(state=HdalabRenkan.PUBLISHED, favorite=False) )


class RenkanFavoriteList(BaseRenkanList):
    """
    Page des renkan favoris. Affiche uniquement les renkan marqués comme favoris. Cela implique que le paramêtre "favorite" est ignoré par cette vue.

    La colonne de l'auteur du renkan n'est affiché que si l'utilisateur est membre de l'équipe.

    Cette vue étend la vue de base :class:`.BaseRenkanList`.
    """

    template_name = "renkan_list_favorite.html"

    def get_context_data(self, **kwargs):
        context = super(RenkanFavoriteList, self).get_context_data(**kwargs)
        renkan_list = HdalabRenkan.objects

        context['hide_favorite'] = True
        context['show_username'] = self.request.user.is_staff
        if self.request.user.is_staff:
            renkan_list = renkan_list.select_related("renkan", "renkan__owner")
        else:
            renkan_list = renkan_list.select_related("renkan")

        return self.update_context(context, renkan_list.filter(state=HdalabRenkan.PUBLISHED, favorite=True) )


class RenkanNew(TemplateView):
    """
    Page permettant la création de nouveau Renkan vide.
    Une fois le renkan est créé (POST), l'utilisateur est redirigé vers la page d'édition de Renkan
    """

    template_name="renkan_new_confirm.html"

    def post(self, request):
        rk = Renkan()
        rk_id = unicode(uuid.uuid1())
        rk.rk_id = rk_id
        rk.owner = request.user
        rk.content = '{}'
        rk.title = "Nouveau Renkan"
        rk.schema_version = "2"
        rk.save()
        hr = HdalabRenkan()
        hr.renkan = rk
        hr.state = HdalabRenkan.EDITION
        hr.save()
        return redirect("%s?rk_id=%s" % (reverse('renkan_edit'), rk_id))


class RenkanEdit(TemplateView):
    """
    Page permettant l'édition de Renkan.
    Le principal rôle de cette page est d'afficher l'éditeur de Renkan.

    Paramêtres GET pris en compte par cette page:

    +-----------+------------------------------------------------------------+--------+--------+
    | nom       | description                                                | type   | défaut |
    +===========+============================================================+========+========+
    | rk_id     | Identifiant d'un renan à éditer.                           | chaine | vide   |
    +-----------+------------------------------------------------------------+--------+--------+
    | shape     | Forme du renkan généré                                     | chaine | circle |
    |           | Ce paramêmtre n'a pas d'effet si le renkan existe déjà     |        |        |
    |           | (utilisation de rk_id) ou bien si il a été sauvegardé.     |        |        |
    +-----------+------------------------------------------------------------+--------+--------+

    """

    template_name="renkan_edit.html"

    def get_context_data(self, **kwargs):
        switch_shape_url= {}
        context = super(RenkanEdit, self).get_context_data(**kwargs)
        # If a renkan id is set
        rk_id = self.request.GET.get("rk_id", "")
        if rk_id!="":
            hr = get_object_or_404(HdalabRenkan.objects.select_related("renkan", "renkan__owner"), renkan__rk_id=rk_id) #.get(=rk_id)
            if hr.renkan.owner!=self.request.user or hr.state!=HdalabRenkan.EDITION:
                raise Exception("You are not allowed to edit this renkan")
        else:
            current_url = self.request.get_full_path()
            switch_shape_url["current"] = current_url
            if "shape=horiz" in current_url:
                switch_shape_url["vert"] = current_url.replace("&shape=horiz", "&shape=vert")
                switch_shape_url["circle"] = current_url.replace("&shape=horiz", "")
            elif "shape=vert" in current_url:
                switch_shape_url["horiz"] = current_url.replace("&shape=vert", "&shape=horiz")
                switch_shape_url["circle"] = current_url.replace("&shape=vert", "")
            else:
                #if we generated the renkan from no filter, the url doesn't have any "?"
                #we add one to create the links to the other representation
                if "?" not in current_url:
                    current_url += "?"
                switch_shape_url["horiz"] = current_url + "&shape=horiz"
                switch_shape_url["vert"] = current_url + "&shape=vert"
            context["switch_shape_url"] = switch_shape_url
        form = AuthenticationForm(self.request)
        context["form"] = form

        context['tutorial_video_urls'] = getattr(settings, 'RENKAN_TUTORIAL_VIDEO_URLS', [])

        return context



class HdalabRenkanGetPut(RenkanGetPut):
    """
    Vue permettant la gestion de la création, de l'édition et de la sauvegarde d'un renkan.
    """

    @csrf_exempt
    def dispatch(self, *args, **kwargs):
        return super(HdalabRenkanGetPut, self).dispatch(*args, **kwargs)

    def get(self, request):
        """
        Méthode qui fournit un renkan au client d'édition. Son origine est fonction des paramêtres fournit.

        Si le paramêtre `rk_id` est fourni, la source du renkan est le contenu d'un objet existant.
        Sinon, le renkan est créé à partir des paramêtres fournis.

        Liste des paramêtres de requête:

        +-------------+------------------------------------------------------------+--------+--------+
        | nom         | description                                                | type   | défaut |
        +=============+============================================================+========+========+
        | rk_id       | Identifiant d'un renan à éditer.                           | chaine | vide   |
        +-------------+------------------------------------------------------------+--------+--------+
        | shape       | Forme du renkan généré (circle, vert, hor)                 | chaine | circle |
        |             | Ce paramêmtre n'a pas d'effet si le renkan existe déjà     |        |        |
        |             | (utilisation de rk_id) ou bien si il a été sauvegardé.     |        |        |
        +-------------+------------------------------------------------------------+--------+--------+
        | lang        | La langue de l'utilisateur. par défaut c'est la langue de  | chaine | vide   |
        |             | la requête.                                                |        |        |
        +-------------+------------------------------------------------------------+--------+--------+
        | notice      | Un identifiant de notice HDA. Si ce paramêtre est fourni,  | chaine | vide   |
        |             | le renkan est généré à partir des tags et notices liées.   |        |        |
        |             | Le centre du renkan est la notice fournie.                 |        |        |
        +-------------+------------------------------------------------------------+--------+--------+
        | folder      | Identifiant d'un dossier de ressource HDA. Si ce paramêtre | chaine | vide   |
        |             | est fourni, le renkan est généré à partir des notices      |        |        |
        |             | contenues dans le dossier, ainsi que les tags liés.        |        |        |
        +-------------+------------------------------------------------------------+--------+--------+
        | label (*)   | Une liste de labels de mots clef, séparés par `,`          | chaine | vide   |
        +-------------+------------------------------------------------------------+--------+--------+
        | country (*) | Une liste d'identifiants de pays, séparés par `,`. Un      | chaine | vide   |
        |             | identifiant de pays est sont URI DBPedia.                  |        |        |
        +-------------+------------------------------------------------------------+--------+--------+
        | period (*)  | Période de recherche.                                      | chaine | vide   |
        |             | Le format est `<année-début>,<année-fin>`.                 |        |        |
        +-------------+------------------------------------------------------------+--------+--------+


        (*) Si les paramêtres `label`, `country`, 'period` sont fournis, ils sont utilisés pour former un filtre sur les fiches HDA en appelant la méthode :func:`hdalab.views.ajax.filter_generic`.
        Le résultat de cet appel est ensuite utilisé avec le paramêtre `shape` pour retourner un nouveau renkan.

        Attention, il faut noter que les nouveau projets renkan créés par cette méthode ne sont pas sauvagardé en base et que donc elle n'a pas d'effet de bord.

        """

        # If a renkan id is set
        rk_id = request.GET.get("rk_id", "")
        if rk_id!="":
            rk = get_object_or_404(Renkan, rk_id=rk_id)
            return HttpResponse(rk.content, content_type="application/json")

        shape = request.GET.get("shape", "")
        no_translate_langs = [ 'fr' ]
        lang = request.GET.get('lang',request.LANGUAGE_CODE)

        # Start dict for renkan json
        now = datetime.now().strftime("%Y-%m-%d %H:%M")

        content = {
          "id": unicode(uuid.uuid1()),
          "schema_version": 2,
          "title": "",
          "description": "(empty description)",
          "created": now,
          "updated": now,
          "nodes": [],
          "edges": [],
          "views": [],
          "users": [],
        }

        # category image dict
        cat_dict = {u"Créateur": static("hdalab/img/category_creator.png"),
                    u"Datation": static("hdalab/img/category_datation.png"),
                    u"Discipline artistique": static("hdalab/img/category_discipline.png"),
                    u"Localisation": static("hdalab/img/category_localisation.png"),
                    u"Ecole/Mouvement": static("hdalab/img/category_movement.png")}

        # category image dict
        shapes = { "tag1": "polygon", "notice": "rectangle", "tag2": "star" }


        # Renkan Project ID
        project_id = unicode(uuid.uuid1())


        # If a notice id is set
        notice_id = request.GET.get("notice", "")
        if notice_id!="":
            notice = get_object_or_404(Datasheet, hda_id=notice_id)
            # We get the ORDERED tags if we display one sheet
            ordered_tags = TaggedSheet.objects.filter(datasheet=notice).select_related("tag", "tag__dbpedia_fields", "tag__category").order_by('order')[:15]
            # Prepare Node placer :
            np = CircleNodePlacer()
            if shape=="horiz":
                np = HorLineNodePlacer()
            elif shape=="vert":
                np = LineNodePlacer()

            np.init({"datasheet": (1, 1), "tags": (2, len(ordered_tags))})
            # Place notice :
            content["nodes"].append({
                "id": unicode(uuid.uuid1()),
                "title": notice.title,
                "description": notice.description,
                "uri": notice.url,
                "position": np.get_place("datasheet"),
                "image": "http://www.histoiredesarts.culture.fr/images/pf/" + notice.hda_id + ".jpg",
                "size": 0,
                "project_id": project_id,
                "style" : {
                    "color": "#FF0033",
                },
                "shape": shapes["notice"]
            })
            notice_id = content["nodes"][0]["id"]

            # Get translated labels
            translations = {}
            if lang not in no_translate_langs:
                transqs = DbpediaFieldsTranslation.objects.filter(master__in = [ot.tag.dbpedia_fields if hasattr(ot.tag, 'dbpedia_fields') and ot.tag.dbpedia_fields else None for ot in ordered_tags], language_code = lang)
                translations = dict([(trans.master_id,trans) for trans in transqs])

            for ot in ordered_tags:
                t = ot.tag
                img_url = t.dbpedia_fields.thumbnail if hasattr(t, 'dbpedia_fields') and t.dbpedia_fields and t.dbpedia_fields.thumbnail else None
                if img_url is None and t.category is not None:
                    img_url = cat_dict[t.category.label]
                translation_obj = translations.get(t.dbpedia_fields.id, None) if t.dbpedia_fields else None
                translation = {
                    'label': translation_obj.label if translation_obj and translation_obj.label else t.label,
                    'abstract': translation_obj.abstract if translation_obj and translation_obj.abstract else '',
                }
                content["nodes"].append({
                  "id": unicode(uuid.uuid1()),
                  "title": translation['label'],
                  "description": translation['abstract'],
                  "uri": t.wikipedia_url,
                  "position": np.get_place("tags"),
                  "image": img_url,
                  "size": 0,
                  "project_id": project_id,
                  "style": {
                    "color": "#00FF33"
                  },
                  "shape": shapes["tag2"]
                })

            # Place edges
            for node in content["nodes"]:
                content["edges"].append({
                    "id": unicode(uuid.uuid1()),
                    "title": "",
                    "description": "",
                    "uri": "",
                    "style": {
                        "color": None,
                    },
                    "from": notice_id,
                    "to": node["id"],
                    "project_id": project_id,
                })

            response = json.dumps(content)
            return HttpResponse(response, content_type="application/json")


        # If a folder id is set
        folder_id = request.GET.get("folder", "")
        if folder_id!="":
            #TODO : optimize to avoid tag request on each notice
            folder = get_object_or_404(Folder.objects.prefetch_related("datasheets", "datasheets__tags"), pk=folder_id)
            notices = folder.datasheets.all()
            n_tags = []
            notice_tag_dict = {}
            tag_uuid_dict = {}
            for n in notices:
                notice_tag_dict[n.pk] = {"uuid":unicode(uuid.uuid1()), "tags":[] }
                for t in n.tags.all()[:5]:
                    if t.pk not in tag_uuid_dict:
                        tag_uuid_dict[t.pk] = unicode(uuid.uuid1())
                    notice_tag_dict[n.pk]["tags"].append(tag_uuid_dict[t.pk])
                    n_tags.append(t)
            n_tags = [t.pk for t in n_tags]
            all_tags = Tag.objects.filter( pk__in=n_tags ).select_related("dbpedia_fields", "category")

            # Get translated labels
            translations = {}
            if lang not in no_translate_langs:
                transqs = DbpediaFieldsTranslation.objects.filter(master__in = [t.dbpedia_fields if hasattr(t, 'dbpedia_fields') and t.dbpedia_fields else None for t in all_tags], language_code = lang)
                translations = dict([(trans.master_id,trans) for trans in transqs])

            # Prepare Node placer :
            np = CircleNodePlacer()
            if shape=="horiz":
                np = HorLineNodePlacer()
            elif shape=="vert":
                np = LineNodePlacer()

            np.init({"datasheet": (1, len(notices)), "tags": (2, len(all_tags))})


            # Place notices
            for n in notices:
                content["nodes"].append({
                  "id": notice_tag_dict[n.pk]["uuid"],
                  "title": n.title,
                  "description": n.description,
                  "uri": n.url,
                  "position": np.get_place("datasheet"),
                  "image": "http://www.histoiredesarts.culture.fr/images/pf/" + n.hda_id + ".jpg",
                  "size": 0,
                  "project_id": project_id,
                  "style": {
                      "color": "#FF0033"
                  },
                  "shape": shapes["notice"]
                })

            # Place tags
            for t in all_tags:
                img_url = t.dbpedia_fields.thumbnail if hasattr(t, 'dbpedia_fields') and t.dbpedia_fields and t.dbpedia_fields.thumbnail else None
                translation_obj = translations.get(t.dbpedia_fields.id, None) if t.dbpedia_fields else None
                translation = {
                    'label': translation_obj.label if translation_obj and translation_obj.label else t.label,
                    'abstract': translation_obj.abstract if translation_obj and translation_obj.abstract else '',
                }

                if img_url is None and t.category is not None:
                    img_url = cat_dict[t.category.label]
                content["nodes"].append({
                  "id": tag_uuid_dict[t.pk],
                  "title": translation['label'],
                  "description": translation['abstract'],
                  "uri": t.wikipedia_url,
                  "position": np.get_place("tags"),
                  "image": img_url,
                  "size": 0,
                  "project_id": project_id,
                  "style": {
                    "color": "#00FF33"
                    },
                    "shape": shapes["tag2"]
                })

            # Place edges
            for n_pk in notice_tag_dict:
                for tag_id in notice_tag_dict[n_pk]["tags"]:
                    content["edges"].append({
                        "id": unicode(uuid.uuid1()),
                        "title": "",
                        "description": "",
                        "uri": "",
                        "style": {
                            "color": None
                        },
                        "from": notice_tag_dict[n_pk]["uuid"],
                        "to": tag_id,
                        "project_id": project_id,
                        #"created_by": "de68xf75y6hs5rgjhgghxbm217xk"
                    })

            response = json.dumps(content)
            return HttpResponse(response, content_type="application/json")


        # Otherwise we build the datas
        # Get tags and countries
        labels = request.GET.get("label", "").split(",")
        countries = request.GET.get("country", "").split(",")
        period = request.GET.get("period", None)
        # Tags arrive with french label, countries with dbpedia uri
        label_list = [t for t in labels if t!=""]
        country_list = [c for c in countries if c!=""]
        all_tags = Tag.objects.filter( Q(label__in=label_list) | Q(dbpedia_uri__in=country_list) ).select_related("dbpedia_fields", "category")

        # Get datasheets from ajax filter search
        temp_fitler = filter_generic(lang, period, ",".join(label_list), ",".join(country_list), content_count=18)
        filter_output = json.loads(temp_fitler)
        filter_output_to_bin = json.loads(temp_fitler)

        #Keep only the 8 first ressources to create the graph
        #the 10 next are sent to the bins
        for i in range(len(filter_output["contents"])):
            if (i < 8):
                del filter_output_to_bin["contents"][0]
            else:
                del filter_output["contents"][8]

        content["bins"] = {
          "type": "ResourceList",
          "title": _("Plus de Ressources"), #TODO: Translate
          "list": filter_output_to_bin["contents"]
        }

        # Prepare other tags
        related_tags = []
        all_labels = [t.label for t in all_tags]
        title = "Recherche " + ", ".join(all_labels)
        if period:
            title += ", " + _("Period") + " " + period
        content["title"] = title
        related_tags_dict = {}
        for c in filter_output["contents"]:
            c["id"] = unicode(uuid.uuid1())
            related_tags_dict[c["id"]] = []
            for t in c["tags"]:
                if t["label"] not in all_labels and t["order"]<6:
                    thumbnail_url = ""
                    for tt in filter_output["tags"]:
                        if tt["label"]==t["label"]:
                            thumbnail_url = tt["thumbnail"]
                    related_tags.append({"label": t["label"], "thumbnail":thumbnail_url, "id":t["id"], "url":t["url"], 'wkpd_url': t['wkpd_url']})
                    all_labels.append(t["label"])
                related_tags_dict[c["id"]].append(t["id"])


        # If possible, we search a dbpedia_fields thumbnail or category thumbnail for related tags
        r_tags = [t["label"] for t in related_tags if t["thumbnail"] is None or t["thumbnail"]=="" ]
        r_tags = Tag.objects.filter( label__in=r_tags ).select_related("dbpedia_fields", "category")
        r_tags_dict = {}
        for t in r_tags:
            img_url = t.dbpedia_fields.thumbnail if hasattr(t, 'dbpedia_fields') and t.dbpedia_fields and t.dbpedia_fields.thumbnail else None
            if img_url is None and t.category is not None:
                img_url = cat_dict[t.category.label]
            if img_url:
                r_tags_dict[t.label] = img_url
        # Populate related_tags with found image urls
        for t in related_tags:
            if (t["thumbnail"] is None or t["thumbnail"]=="") and (t["label"] in r_tags_dict):
                t["thumbnail"] = r_tags_dict[t["label"]]


        # Prepare Node placer :
        np = CircleNodePlacer()
        if shape=="horiz":
            np = HorLineNodePlacer()
        elif shape=="vert":
            np = LineNodePlacer()

        len_tags = len(all_tags)
        if period:
            len_tags += 1
        np.init({"tags": (1, len_tags), "datasheet": (2, len(filter_output["contents"])), "related": (3, len(related_tags)), "northwest":(3 if shape=="circle" else 1, 1)})

        #get tag abstract and label translations

        tags_id = [t.id for t in all_tags] + [t['id'] for t in related_tags]
        translations = {}
        transqs = DbpediaFieldsTranslation.objects.filter(master__tag__in = tags_id, language_code = lang)
        translations = dict([(trans.master.tag.id,trans) for trans in transqs])


        for t in all_tags:
            img_url = t.dbpedia_fields.thumbnail if hasattr(t, 'dbpedia_fields') and t.dbpedia_fields and t.dbpedia_fields.thumbnail else None
            if img_url is None and t.category is not None:
                img_url = cat_dict[t.category.label]
            translation_obj = translations.get(t.id, None)
            translation = {
                'label': filter_output["tagtranslations"][t.label] if t.label in filter_output["tagtranslations"] else t.label,
                'abstract': translation_obj.abstract if translation_obj else ""
            }

            content["nodes"].append({
              "id": unicode(uuid.uuid1()),
              "title": translation['label'],
              "description": translation['abstract'],
              "uri": t.wikipedia_url,
              "position": np.get_place("tags"),
              "image": img_url,
              "size": 0,
              "project_id": project_id,
              "style" : {
                   "color": None,
              },
              "shape": shapes["tag1"]
            })
        if period:
            content["nodes"].append({
              "id": unicode(uuid.uuid1()),
              "title": _("Period") + " " + period,
              "description": "",
              "uri": "",
              "position": np.get_place("tags"),
              "image": cat_dict[u"Datation"],
              "size": 0,
              "project_id": project_id,
              "style": {
                  "color": None
              },
              "shape": shapes["tag1"]
            })

        for c in filter_output["contents"]:
            content["nodes"].append({
              "id": c["id"],
              "title": c["title"],
              "description": c["description"],
              "uri": c["url"],
              "position": np.get_place("datasheet"),
              "image": "http://www.histoiredesarts.culture.fr/images/pf/" + c["hda_id"]+ ".jpg",
              "size": 0,
              "project_id": project_id,
              "style" : {
                  "color": "#FF0033"
              },
              "shape": shapes["notice"]
            })

        for t in related_tags:
            translation_obj = translations.get(t['id'], None)
            translation = {
                'label': filter_output["tagtranslations"][t["label"]],
                'abstract': translation_obj.abstract if translation_obj else ""
            }

            content["nodes"].append({
              "id": t["id"],
              "title": translation['label'],
              "description": translation['abstract'],
              "uri": t["wkpd_url"],
              "position": np.get_place("related"),
              "image": t["thumbnail"],
              "size": 0,
              "project_id": project_id,
              "style": {
                  "color": "#00FF33"
              },
              "shape": shapes["tag2"]
            })

        for c_id in related_tags_dict:
            for tag_id in related_tags_dict[c_id]:
                content["edges"].append({
                    "id": unicode(uuid.uuid1()),
                    "title": "",
                    "description": "",
                    "uri": "",
                    "style": {
                        "color": None,
                    },
                    "from": c_id,
                    "to": tag_id,
                    "project_id": project_id,
                    #"created_by": "de68xf75y6hs5rgjhgghxbm217xk"
                })

        response = json.dumps(content)

        return HttpResponse(response, content_type="application/json")


    def post(self, request):
        """
        Sauvegarde d'un renkan.
        La sauvegarde n'est possible que dans les condition suivantes:
        - si il s'agit d'une création de renkan (pas de paramêtre `rk_id`), il faut être identifié
        - si un renkan est modifié, il faut être identifié et en être le propriétaire.

        Paramêtres de requête:

        +-----------+------------------------------------------------------------+--------+--------+
        | nom       | description                                                | type   | défaut |
        +===========+============================================================+========+========+
        | rk_id     | Identifiant du renkan à sauvegarder                        | chaine | vide   |
        +-----------+------------------------------------------------------------+--------+--------+

        Le contenu du renkan à sauvegarder est dans le corps de la requête.
        Le contenu d'un renkan est décrit sommairement dans la documentation renkan.
        À part pour les champs "id" et "title" qui sont utilisés pour la gestion de l'affichage, le contenu d'un renkan n'est pas modifié par cette meethode et est directement sauvegardé.
        """

        rk_id = request.GET.get("rk_id", "")
        #data = json.loads(request.body)
        #logger.debug(data["edges"])
        #logger.debug(data["nodes"])
        #logger.debug(request.user.is_authenticated())
        #logger.debug(request.user.is_anonymous())
        if rk_id!="":
            rk = get_object_or_404(Renkan, rk_id=rk_id)
            if rk.owner!=request.user:
                return HttpResponseBadRequest("You are not allowed to edit this renkan")
            rk.content = request.body
            data = json.loads(request.body)
            if "title" in data:
                rk.title = data["title"]
            rk.save()
            return HttpResponse("SAVED")
        else:
            # if rk_id was not a get parameter AND if it is set in json data AND if user is authenticated
            # Then we can save the renkan
            data = json.loads(request.body)
            if "id" in data:
                rk_id = data["id"]
                if rk_id != "" and Renkan.objects.filter(rk_id=rk_id).count()==0 and request.user.is_authenticated():
                    rk = Renkan()
                    rk.rk_id = rk_id
                    rk.title = data["title"] if "title" in data else ""
                    rk.content = request.body
                    rk.owner = request.user
                    rk.save()
                    hr = HdalabRenkan()
                    hr.renkan = rk
                    hr.state = HdalabRenkan.EDITION
                    hr.save()
                    return HttpResponse("rk_id=" + rk_id)


        return HttpResponse("NOT SAVED")



class HdalabRenkanCopy(View):
    """
    Vue permettant de copier un renkan.
    """

    def post(self, request, rk_id):
        """
        Copie un renkan.
        L'id du renkan à copier est un paramêtre d'URL.
        Le nouveau titre est "ancien titre (copy)".
        """

        rk = renkan_copier(request.user, rk_id)
        hr = HdalabRenkan()
        hr.renkan = rk
        hr.state = HdalabRenkan.EDITION
        hr.save()
        if "next" in request.POST:
            return redirect(request.POST["next"])
        return redirect(reverse('profile_home'))


class HdalabRenkanDelete(View):
    """
    Vue permettant d'effacer un Renkan.
    """

    def post(self, request, rk_id):
        """
        Efface un renkan.
        L'id du renkan à copier est un paramêtre d'URL.
        """
        try:
            hr = HdalabRenkan.objects.get(renkan__rk_id=rk_id)
        except:
            raise Http404('Renkan not found')
        renkan_deleter(request.user, rk_id)
        hr.delete()
        if "next" in request.POST:
            return redirect(request.POST["next"])
        return redirect(reverse('profile_home'))


class HdalabRenkanModerate(View):
    """
    Vue pour modérer un renkan.
    """

    def post(self, request, rk_id):
        """
        Modère un Renkan, i.e. modifie son état.

        :param:rk_id: L'identifiant de renkan, passé comme paramêtre d'URL.

        Paramêtres POST:

        +-----------+------------------------------------------------------------+--------+--------+-------------+
        | nom       | description                                                | type   | défaut | obligatoire |
        +===========+============================================================+========+========+=============+
        | id        | Identifiant de renkan (n'est pas pris en compte)           | chaine |        | oui         |
        +-----------+------------------------------------------------------------+--------+--------+-------------+
        | state     | Le prochain état.                                          | entier |        | oui         |
        |           | c.f.                                                       |        |        |             |
        |           | :class:`hdalab.models.renkan.HdalabRenkanStateTransition`  |        |        |             |
        +-----------+------------------------------------------------------------+--------+--------+-------------+
        | message   | Message optionel de modération.                            | chaine | vide   | non         |
        +-----------+------------------------------------------------------------+--------+--------+-------------+
        | next (*)  | Url de redirection.                                        | chaine | vide   | non         |
        +-----------+------------------------------------------------------------+--------+--------+-------------+

        (*) Si non précisée, redirige vers l'url intitulée `profile_home` (/hdalab/profile/).

        """
        form = HdalabRenkanStateForm(request.POST)
        if form.is_valid():
            logger.debug("FORM DATA %r", form.cleaned_data)
            renkan_hda =  get_object_or_404(HdalabRenkan.objects.select_related(), renkan__rk_id=rk_id)
            change_renkan_state(renkan_hda, form.cleaned_data['state'], form.cleaned_data['message'], request.user)
            next_url = form.cleaned_data.get('next', None)
            if next_url:
                return redirect(next_url)
            else:
                redirect(reverse('profile_home'))
        else:
            logger.debug("FORM INVALID %r : %r", request.POST, form.errors)
            return HttpResponseBadRequest("State form invalid")



class HdalabRenkanFavorite(View):
    """
    Vue pour marquer un renkan comme favori.
    """

    def post(self, request, rk_id):
        """
        Marque un renkan comme favoris.

        :param:rk_id: L'identifiant de renkan, passé comme paramêtre d'URL.

        Paramêtres POST:

        +-----------+------------------------------------------------------------+---------+--------+-------------+
        | nom       | description                                                | type    | défaut | obligatoire |
        +===========+============================================================+=========+========+=============+
        | favorite  | Indique si le renkan est marqué comme favoris ou pas       | booléen |        | oui         |
        +-----------+------------------------------------------------------------+---------+--------+-------------+

        """
        form = HdalabRenkanFavoriteForm(request.POST)
        if form.is_valid():
            renkan_hda =  get_object_or_404(HdalabRenkan.objects.select_related(), renkan__rk_id=rk_id)
            renkan_hda.favorite = form.cleaned_data['favorite']
            renkan_hda.save()
            return HttpResponse(
                json.dumps({'status': 'ok', 'renkan': {'rk_id': renkan_hda.renkan.rk_id, 'favorite': renkan_hda.favorite}}),
                content_type="application/json"
            )


class UserProfileUpdate(UpdateView):
    """
    Simple classe de vue permettant la gestion des pages de mise à jour des informations de profile de l'utilisateur.
    utilise en particulier le template `hdalab/templates/hdabo/user_update_form.html` comme formulaire de saisie.
    Elle étend la vue `django.views.generic.edit.UpdateView <https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-editing/#django.views.generic.edit.UpdateView>`_ .
    """
    fields = ['email']
    template_name_suffix = '_update_form'

    def get_object(self, queryset=None):
        return self.request.user

    def get_success_url(self):
        return reverse('profile_home')


# Function copied from django.contrib.auth.views to simplify ajax login
@sensitive_post_parameters()
@csrf_protect
@never_cache
def ajax_login(request, template_name='ajax_identification/ajax_login.html',
          redirect_field_name=REDIRECT_FIELD_NAME,
          authentication_form=AuthenticationForm,
          current_app=None, extra_context=None):
    """
    Affiche le formulaire de connection et gère l'action de connexion.
    Cette fonction a été copiée de :func:`django.contrib.auth.views.login` pour faciliter la connexion `ajax`.
    """
    redirect_to = request.REQUEST.get(redirect_field_name, '')

    if request.method == "POST":
        form = authentication_form(request, data=request.POST)
        if form.is_valid():

            # Ensure the user-originating redirection url is safe.
            if not is_safe_url(url=redirect_to, host=request.get_host()):
                redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)

            # Okay, security check complete. Log the user in.
            auth_login(request, form.get_user())

            return HttpResponseRedirect(redirect_to)
    else:
        form = authentication_form(request)

    current_site = get_current_site(request)

    context = {
        'form': form,
        redirect_field_name: redirect_to,
        'site': current_site,
        'site_name': current_site.name,
    }
    if extra_context is not None:
        context.update(extra_context)
    return TemplateResponse(request, template_name, context,
                            current_app=current_app)