# HG changeset patch # User durandn # Date 1467204987 -7200 # Node ID 51257e2701d9b93de46d561f528ab26307542372 # Parent 4fffc78a91b2dcbb4a7a5d75744b7184b4b9dc38 streamlined comment system, replicated post_comment view so form errors redirect on the annotation page and not on a preview page + added revision detail view and links for specific revisions from the comments diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/forms/comments.py --- a/src/iconolab/forms/comments.py Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/forms/comments.py Wed Jun 29 14:56:27 2016 +0200 @@ -1,16 +1,30 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from django_comments_xtd.forms import XtdCommentForm +from django.contrib.contenttypes.models import ContentType +from django.conf import settings +from django.utils.encoding import force_text +from django.utils import timezone from iconolab.models import MetaCategory class IconolabCommentForm(XtdCommentForm): - metacategories = forms.ModelMultipleChoiceField(queryset=MetaCategory.objects.all()) + metacategories = forms.ModelMultipleChoiceField(queryset=MetaCategory.objects.all(), required=False) + email = forms.EmailField(required=False) def __init__(self, *args, **kwargs): super(IconolabCommentForm, self).__init__(*args, **kwargs) self.fields.pop('email') def get_comment_create_data(self): - data = super(IconolabCommentForm, self).get_comment_create_data() - data.update({'metacategories': self.cleaned_data['metacategories']}) - return data \ No newline at end of file + return dict( + content_type=ContentType.objects.get_for_model(self.target_object), + object_pk=force_text(self.target_object._get_pk_val()), + user_name=self.cleaned_data["name"], + user_email='', + user_url=self.cleaned_data["url"], + comment=self.cleaned_data["comment"], + submit_date=timezone.now(), + site_id=settings.SITE_ID, + is_public=True, + is_removed=False, + ) \ No newline at end of file diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/models.py --- a/src/iconolab/models.py Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/models.py Wed Jun 29 14:56:27 2016 +0200 @@ -193,8 +193,8 @@ revision_guid = models.UUIDField(default=uuid.uuid4) annotation = models.ForeignKey('Annotation', related_name='revisions', null=False, blank=False) - parent_revision = models.ForeignKey('AnnotationRevision', related_name='reverse_parent_revision', blank=True, null=True) - merge_parent_revision = models.ForeignKey('AnnotationRevision', related_name='reverse_merge_parent_revision', blank=True, null=True) + parent_revision = models.ForeignKey('AnnotationRevision', related_name='child_revisions', blank=True, null=True) + merge_parent_revision = models.ForeignKey('AnnotationRevision', related_name='child_revisions_merge', blank=True, null=True) author = models.ForeignKey(User, null=True) title = models.CharField(max_length=255) description = models.TextField(null=True) @@ -233,7 +233,26 @@ accuracy = tag_accuracy, relevancy = tag_relevancy ) - + + def get_tags_json(self): + final_list = [] + for tagging_info in self.tagginginfo_set.select_related("tag").all(): + if tagging_info.tag.is_internal(): + final_list.append({ + "tag_label": tagging_info.tag.label, + "tag_link": tagging_info.tag.link, + "accuracy": tagging_info.accuracy, + "relevancy": tagging_info.relevancy + }) + else: + #import label from dbpedia + final_list.append({ + "tag_label": "", + "tag_link": tagging_info.tag.link, + "accuracy": tagging_info.accuracy, + "relevancy": tagging_info.relevancy + }) + return json.dumps(final_list) class IconolabComment(XtdComment): revision = models.ForeignKey('AnnotationRevision', related_name='creation_comment', null=True, blank=True) diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/templates/iconolab/change_annotation.html --- a/src/iconolab/templates/iconolab/change_annotation.html Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/templates/iconolab/change_annotation.html Wed Jun 29 14:56:27 2016 +0200 @@ -101,6 +101,7 @@ + Retour diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/templates/iconolab/detail_annotation.html --- a/src/iconolab/templates/iconolab/detail_annotation.html Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/templates/iconolab/detail_annotation.html Wed Jun 29 14:56:27 2016 +0200 @@ -30,49 +30,60 @@
+

Annotation

Title: {{ annotation.current_revision.title }}

Description: {{ annotation.current_revision.description }}

-

Tags: {{ annotation.current_revision.tags }}

+

Tags: {{ tags_data }}

Editer | Proposer une révision
+

Comments

{% get_comment_list for annotation as comment_list %} -
+
+
{% if user.is_authenticated %} - {% get_comment_form for annotation as comment_form %} -
+ {% if not comment_form %} + {% get_comment_form for annotation as comment_form %} + {% endif %} + {% csrf_token %} {{ comment_form.content_type }} {{ comment_form.object_pk }} {{ comment_form.timestamp }} {{ comment_form.security_hash }} - -
- -
-
+ {{ comment_form.reply_to }} + +

Commenting as {{user.username}}

+
+ {% if comment_form.comment.errors %} + + {% endif %}
- +

+ +

{% endif %}
diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/templates/iconolab/detail_revision.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/iconolab/templates/iconolab/detail_revision.html Wed Jun 29 14:56:27 2016 +0200 @@ -0,0 +1,47 @@ +{% extends 'iconolab_base.html' %} + +{% load i18n %} +{% load staticfiles %} +{% load comments %} +{% load comments_xtd %} +{% load thumbnail %} +{% load iconolab_tags %} + +{% block content %} + Revoir l'image +
+ +
+
+
+ {% thumbnail image.media "x300" crop="center" as im %} + + + + + + + + + + + {% endthumbnail %} +
+
+ +
+

Annotation

+

Title: {{ revision.title }}

+

Description: {{ revision.description }}

+

Tags: {{ tags_data }}

+ + Retour sur l'annotation + {% if revision.parent_revision %} | Voir révision précédente{% endif %} +
+
+{% endblock %} diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/urls.py --- a/src/iconolab/urls.py Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/urls.py Wed Jun 29 14:56:27 2016 +0200 @@ -30,11 +30,13 @@ url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/create$', login_required(views.CreateAnnotationView.as_view()), name='annotation_create'), url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/detail$', views.ShowAnnotationView.as_view(), name='annotation_detail'), url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/edit$', login_required(views.EditAnnotationView.as_view()), name='annotation_edit'), - url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/revisions/(?P[0-9]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/revisions/(?P[^/]+)/detail', login_required(views.ShowRevisionView.as_view()), name='revision_detail'), + url(r'^collections/(?P[a-z]+)/images/(?P[^/]+)/annotations/(?P[^/]+)/revisions/(?P[^/]+)/merge$', login_required(views.MergeProposalView.as_view()), name='annotation_merge'), url(r'errors/404', views.NotFoundErrorView.as_view(), name="404error"), url(r'^rest', include('restapi.urls')), url(r'^account/', include('iconolab.auth.urls', namespace='account')), - url(r'^comments/', include('django_comments_xtd.urls')) + url(r'^comments/', include('django_comments_xtd.urls')), + url(r'^comments/annotation/post', views.post_comment_iconolab, name="post_comment") ] urlpatterns += staticfiles_urlpatterns() diff -r 4fffc78a91b2 -r 51257e2701d9 src/iconolab/views.py --- a/src/iconolab/views.py Wed Jun 29 14:54:16 2016 +0200 +++ b/src/iconolab/views.py Wed Jun 29 14:56:27 2016 +0200 @@ -1,35 +1,23 @@ +from django.apps import apps from django.shortcuts import HttpResponse, get_object_or_404, render from django.http import Http404 from django.contrib.auth.decorators import login_required from django.views.generic import View, RedirectView from django.views.generic.base import ContextMixin +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.http import require_POST from django.core.urlresolvers import reverse +from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.conf import settings -from iconolab.models import Annotation, Collection, Image, IconolabComment +from iconolab.models import Annotation, AnnotationRevision, Collection, Image, IconolabComment from iconolab.forms.annotations import AnnotationRevisionForm -import json, datetime +import datetime +import django_comments +from django_comments import signals +from django_comments.views.utils import next_redirect, confirmation_view -def make_tags_json(annotation_revision): - final_list = [] - for tagging_info in annotation_revision.tagginginfo_set.select_related("tag").all(): - if tagging_info.tag.is_internal(): - final_list.append({ - "tag_label": tagging_info.tag.label, - "tag_link": tagging_info.tag.link, - "accuracy": tagging_info.accuracy, - "relevancy": tagging_info.relevancy - }) - else: - #import label from dbpedia - final_list.append({ - "tag_label": "", - "tag_link": tagging_info.tag.link, - "accuracy": tagging_info.accuracy, - "relevancy": tagging_info.relevancy - }) - return json.dumps(final_list) class GlobalHomepageView(View): def get(self, request, *args, **kwargs): @@ -65,7 +53,7 @@ context["collection"] = collection context["image"] = image return render(request, 'iconolab/detail_image.html', context); - + class CreateAnnotationView(View, ContextMixin): def get_context_data(self, **kwargs): @@ -105,6 +93,17 @@ fragment = annotation_form.cleaned_data["fragment"] tags_json = annotation_form.cleaned_data["tags"] new_annotation = Annotation.objects.create_annotation(author, image, title=title, description=description, fragment=fragment, tags_json=tags_json) + revision_comment = annotation_form.cleaned_data["comment"] + IconolabComment.objects.create( + comment = revision_comment, + revision = new_annotation.current_revision, + content_type = ContentType.objects.get(app_label="iconolab", model="annotation"), + content_object = new_annotation, + site = Site.objects.get(id=settings.SITE_ID), + object_pk = new_annotation.id, + user = request.user, + user_name = request.user.username + ) return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': new_annotation.annotation_guid}))(request) @@ -137,8 +136,9 @@ self.check_kwargs(kwargs) context = self.get_context_data(**kwargs) context["collection"] = collection - context["image"] = collection + context["image"] = image context["annotation"] = annotation + context["tags_data"] = annotation.current_revision.get_tags_json() return render(request, 'iconolab/detail_annotation.html', context) @@ -173,7 +173,7 @@ context["image"] = image context["annotation"] = annotation context["form"] = annotation_form - context["tags_data"] = make_tags_json(annotation.current_revision) + context["tags_data"] = annotation.current_revision.get_tags_json() return render(request, 'iconolab/change_annotation.html', context) def post(self, request, *args, **kwargs): @@ -190,7 +190,7 @@ revision_tags_json = annotation_form.cleaned_data["tags"] new_revision = annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, revision_tags_json) revision_comment = annotation_form.cleaned_data["comment"] - IconolabComment.objects.create( + comment = IconolabComment.objects.create( comment = revision_comment, revision = new_revision, content_type = ContentType.objects.get(app_label="iconolab", model="annotation"), @@ -200,9 +200,55 @@ user = request.user, user_name = request.user.username ) + print(comment.revision) + print(new_revision.creation_comment.first()) return RedirectView.as_view(url=reverse("annotation_detail", kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': annotation_guid}))(request) print(annotation_form.errors) return HttpResponse("wow errors") + + +class ShowRevisionView(View, ContextMixin): + + def get_context_data(self, **kwargs): + context = super(ShowRevisionView, self).get_context_data(**kwargs) + context["collection_name"] = self.kwargs.get("collection_name", "") + context["image_guid"] = self.kwargs.get("image_guid", "") + context["annotation_guid"] = self.kwargs.get("annotation_guid", "") + context["revision_guid"] = self.kwargs.get("revision_guid", "") + return context + + def check_kwargs(self, kwargs): + try: + collection = Collection.objects.get(name=kwargs.get("collection_name", "")) + except Collection.DoesNotExist: + return RedirectView.as_view(url=reverse("404error")) + try: + image = Image.objects.get(image_guid=kwargs.get("image_guid", "")) + except Image.DoesNotExist: + return RedirectView.as_view(url=reverse("404error")) + try: + annotation = Annotation.objects.select_related("current_revision").get(annotation_guid=kwargs.get("annotation_guid", "")) + except Annotation.DoesNotExist: + return RedirectView.as_view(url=reverse("404error")) + try: + revision = AnnotationRevision.objects.select_related("parent_revision").get(revision_guid=kwargs.get("revision_guid", "")) + except AnnotationRevision.DoesNotExist: + return RedirectView.as_view(url=reverse("404error")) + return collection, image, annotation, revision + + def get(self, request, *args, **kwargs): + collection, image, annotation, revision = self.check_kwargs(kwargs) + self.check_kwargs(kwargs) + context = self.get_context_data(**kwargs) + context["collection"] = collection + context["image"] = image + context["annotation"] = annotation + context["revision"] = revision + context["tags_data"] = revision.get_tags_json() + print(revision.creation_comment) + context["comment"] = revision.creation_comment.first() + return render(request, 'iconolab/detail_revision.html', context) + class MergeProposalView(View): @@ -218,4 +264,96 @@ class NotFoundErrorView(View): def get(self, request, *args, **kwargs): # Handle image display here - pass \ No newline at end of file + pass + +@csrf_protect +@require_POST +def post_comment_iconolab(request, next=None, using=None): + """ + Post a comment. + HTTP POST is required. If ``POST['submit'] == "preview"`` or if there are + errors a preview template, ``comments/preview.html``, will be rendered. + """ + # Fill out some initial data fields from an authenticated user, if present + data = request.POST.copy() + if request.user.is_authenticated(): + if not data.get('name', ''): + data["name"] = request.user.get_full_name() or request.user.get_username() + if not data.get('email', ''): + data["email"] = request.user.email + + # Look up the object we're trying to comment about + ctype = data.get("content_type") + object_pk = data.get("object_pk") + if ctype is None or object_pk is None: + return CommentPostBadRequest("Missing content_type or object_pk field.") + try: + model = apps.get_model(*ctype.split(".", 1)) + target = model._default_manager.using(using).get(pk=object_pk) + except TypeError: + return CommentPostBadRequest( + "Invalid content_type value: %r" % escape(ctype)) + except AttributeError: + return CommentPostBadRequest( + "The given content-type %r does not resolve to a valid model." % escape(ctype)) + except ObjectDoesNotExist: + return CommentPostBadRequest( + "No object matching content-type %r and object PK %r exists." % ( + escape(ctype), escape(object_pk))) + except (ValueError, ValidationError) as e: + return CommentPostBadRequest( + "Attempting go get content-type %r and object PK %r exists raised %s" % ( + escape(ctype), escape(object_pk), e.__class__.__name__)) + + # Do we want to preview the comment? + preview = "preview" in data + + # Construct the comment form + form = django_comments.get_form()(target, data=data) + + # Check security information + if form.security_errors(): + return CommentPostBadRequest( + "The comment form failed security verification: %s" % escape(str(form.security_errors()))) + + # If there are errors or if we requested a preview show the comment + if form.errors: + return render(request, "iconolab/detail_annotation.html", { + "comment_form": form, + "next": data.get("next", next), + "annotation": target, + "annotation_guid": target.annotation_guid, + "image_guid": target.image.image_guid, + "collection_name": target.image.item.collection.name, + "tags_data": target.current_revision.get_tags_json() + }, + ) + + # Otherwise create the comment + comment = form.get_comment_object() + comment.ip_address = request.META.get("REMOTE_ADDR", None) + if request.user.is_authenticated(): + comment.user = request.user + + # Signal that the comment is about to be saved + responses = signals.comment_will_be_posted.send( + sender=comment.__class__, + comment=comment, + request=request + ) + + for (receiver, response) in responses: + if response is False: + return CommentPostBadRequest( + "comment_will_be_posted receiver %r killed the comment" % receiver.__name__) + + # Save the comment and signal that it was saved + comment.save() + signals.comment_was_posted.send( + sender=comment.__class__, + comment=comment, + request=request + ) + + return next_redirect(request, fallback=next or 'comments-comment-done', + c=comment._get_pk_val()) \ No newline at end of file