Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements around comments #1220

Merged
merged 10 commits into from
Nov 24, 2019
45 changes: 0 additions & 45 deletions tcms/core/contrib/comments/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-

import django_comments as comments
from django.conf import settings
from django.contrib.auth.decorators import permission_required
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
from django.views.decorators.http import require_POST

Expand All @@ -19,44 +15,3 @@ def post(request):
_form, _target = add_comment(request, data)

return JsonResponse({'rc': 0})


@require_POST
@permission_required("django_comments.can_moderate")
def delete(request):
"""Deletes a comment"""

ajax_response = {'rc': 0, 'response': 'ok'}
comments_s = comments.get_model().objects.filter(
pk__in=request.POST.getlist('comment_id'),
site__pk=settings.SITE_ID,
is_removed=False,
user_id=request.user.id
)

if not comments_s:
if request.is_ajax():
ajax_response = {'rc': 1, 'response': 'Object does not exist.'}
return JsonResponse(ajax_response)

raise ObjectDoesNotExist()

# Flag the comment as deleted instead of actually deleting it.
for comment in comments_s:
if comment.user == request.user:
flag, created = comments.models.CommentFlag.objects.get_or_create(
comment=comment,
user=request.user,
flag=comments.models.CommentFlag.MODERATOR_DELETION
)
comment.is_removed = True
comment.save()
comments.signals.comment_was_flagged.send(
sender=comment.__class__,
comment=comment,
flag=flag,
created=created,
request=request,
)

return JsonResponse(ajax_response)
50 changes: 50 additions & 0 deletions tcms/rpc/api/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.forms import EmailField, ValidationError
from modernrpc.core import REQUEST_KEY, rpc_method

from tcms.core.helpers import comments
from tcms.core.utils import form_errors_to_list
from tcms.management.models import Component, Tag
from tcms.rpc import utils
Expand All @@ -17,6 +18,9 @@
'filter',
'remove',

'add_comment',
'remove_comment',

'add_component',
'get_components',
'remove_component',
Expand Down Expand Up @@ -402,3 +406,49 @@ def add_attachment(case_id, filename, b64content, **kwargs):
kwargs.get(REQUEST_KEY).user,
filename,
b64content)


@permissions_required('django_comments.add_comment')
@rpc_method(name='TestCase.add_comment')
def add_comment(case_id, comment, **kwargs):
"""
.. function:: TestCase.add_comment(case_id, comment)

Add comment to selected test case.

:param case_id: PK of a TestCase object
:param case_id: int
:param comment: The text to add as a comment
:param comment: str
:return: None or JSON string in case of errors
:raises: PermissionDenied if missing *django_comments.add_comment* permission

.. important::

In webUI comments are only shown **only** during test case review!
"""
case = TestCase.objects.get(pk=case_id)
comments.add_comment([case], comment, kwargs.get(REQUEST_KEY).user)


@permissions_required('django_comments.delete_comment')
@rpc_method(name='TestCase.remove_comment')
def remove_comment(case_id, comment_id=None):
"""
.. function:: TestCase.remove_comment(case_id, comment_id)

Remove all or specified comment(s) from selected test case.

:param case_id: PK of a TestCase object
:param case_id: int
:param comment_id: PK of a Comment object or None
:param comment_id: int
:return: None
:raises: PermissionDenied if missing *django_comments.delete_comment* permission
"""
case = TestCase.objects.get(pk=case_id)
to_be_deleted = comments.get_comments(case)
if comment_id:
to_be_deleted = to_be_deleted.filter(pk=comment_id)

to_be_deleted.delete()
26 changes: 26 additions & 0 deletions tcms/rpc/api/testexecution.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
'filter',

'add_comment',
'remove_comment',

'add_link',
'get_links',
'remove_link',
)


@permissions_required('django_comments.add_comment')
@rpc_method(name='TestExecution.add_comment')
def add_comment(execution_id, comment, **kwargs):
"""
Expand All @@ -38,11 +40,35 @@ def add_comment(execution_id, comment, **kwargs):
:param comment: The text to add as a comment
:param comment: str
:return: None or JSON string in case of errors
:raises: PermissionDenied if missing *django_comments.add_comment* permission
"""
execution = TestExecution.objects.get(pk=execution_id)
comments.add_comment([execution], comment, kwargs.get(REQUEST_KEY).user)


@permissions_required('django_comments.delete_comment')
@rpc_method(name='TestExecution.remove_comment')
def remove_comment(execution_id, comment_id=None):
"""
.. function:: TestExecution.remove_comment(execution_id, comment_id)

Remove all or specified comment(s) from selected test execution.

:param execution_id: PK of a TestExecution object
:param execution_id: int
:param comment_id: PK of a Comment object or None
:param comment_id: int
:return: None
:raises: PermissionDenied if missing *django_comments.delete_comment* permission
"""
execution = TestExecution.objects.get(pk=execution_id)
to_be_deleted = comments.get_comments(execution)
if comment_id:
to_be_deleted = to_be_deleted.filter(pk=comment_id)

to_be_deleted.delete()


# todo: this is very similar, if not duplicate to TestRun.add_case IMO
# should we schedule it for removal ?!?
@permissions_required('testruns.add_testexecution')
Expand Down
21 changes: 19 additions & 2 deletions tcms/signals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pylint: disable=unused-argument
"""
Defines custom signals sent throught out Kiwi TCMS. You can connect your own
Defines custom signals sent throughout Kiwi TCMS. You can connect your own
handlers if you'd like to augment some of the default behavior!

If you simply want to connect a signal handler add the following code to your
Expand All @@ -14,7 +14,7 @@
a new Django app and connect your handler function(s) to the desired signals
inside the
`AppConfig.ready
<https://docs.djangoproject.com/en/2.0/ref/applications/#django.apps.AppConfig.ready>`_
<https://docs.djangoproject.com/en/2.2/ref/applications/#django.apps.AppConfig.ready>`_
method. When you are done connect your Django app to the rest of Kiwi TCMS by
altering the following setting::

Expand All @@ -29,6 +29,7 @@

'notify_admins',
'pre_save_clean',
'handle_comments_pre_delete',
'handle_emails_post_case_save',
'handle_emails_pre_case_delete',
'handle_emails_post_plan_save',
Expand Down Expand Up @@ -158,3 +159,19 @@ def handle_emails_post_run_save(sender, *_args, **kwargs):
subject, context = history_email_for(instance, instance.summary)

mailto(template_name, subject, instance.get_notify_addrs(), context)


def handle_comments_pre_delete(sender, **kwargs):
"""
Delete comments attached to object which is about to be
deleted b/c django-comments' object_pk is not a FK relationship
and we can't rely on cascading delete!
"""
from tcms.core.helpers.comments import get_comments

if kwargs.get('raw', False):
return

instance = kwargs['instance']

get_comments(instance).delete()
19 changes: 0 additions & 19 deletions tcms/static/js/tcms_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,25 +282,6 @@ function removeTag(container, params) {
});
}

function removeComment(form, callback) {
var url = form.action;
var method = form.method;
var parameters = Nitrate.Utils.formSerialize(form);

jQ.ajax({
'url': url,
'type': method,
'data': parameters,
'success': function (data, textStatus, jqXHR) {
updateCommentsCount(parameters['object_pk'], false);
callback(jqXHR);
},
'error': function (jqXHR, textStatus, errorThrown) {
json_failure(jqXHR);
}
});
}


function submitComment(container, parameters, callback) {
var complete = function(t) {
Expand Down
21 changes: 6 additions & 15 deletions tcms/static/js/testplan_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,22 +768,13 @@ function bindEventsOnLoadedCases(options) {
if (!window.confirm(default_messages.confirm.remove_comment)) {
return false;
}
var params = Nitrate.Utils.formSerialize(this);
var refresh_case = function(t) {
var returnobj = jQ.parseJSON(t.responseText);
if (returnobj.rc != 0) {
window.alert(returnobj.response);
return false;
}

var td = jQ('<td>', {colspan: 12});
var id = 'id_loading_' + params['object_pk'];
td.append(getAjaxLoading(id));
jQ(content).html(td);
fireEvent(btn, 'click');
fireEvent(btn, 'click');
};
removeComment(this, refresh_case);
const case_id = $(this).find('input[name=object_pk]').val();
const comment_id = $(this).find('input[name=comment_id]').val();
const comment_widget = $(this).parents().find("#comment"+comment_id);
jsonRPC('TestCase.remove_comment', [case_id, comment_id], function(data){
$(comment_widget).hide();
});
};
jQ(content).parent().find('.form_comment').unbind('submit');
jQ(content).parent().find('.form_comment').bind('submit', rc_callback);
Expand Down
10 changes: 5 additions & 5 deletions tcms/static/js/testrun_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,18 @@ Nitrate.TestRuns.Details.on_load = function() {
c_container.parent().find('.update_form')
.unbind('submit').bind('submit', updateCaseRunStatus);

// Observe the delete comment form
var refresh_case = function(t) {
constructCaseRunZone(c_container[0], c[0], case_id);
};

var rc_callback = function(e) {
e.stopPropagation();
e.preventDefault();
if (!window.confirm(default_messages.confirm.remove_comment)) {
return false;
}
removeComment(this, refresh_case);

const comment_id = $(this).find('input[name=comment_id]').val();
jsonRPC('TestExecution.remove_comment', [case_run_id, comment_id], function(data) {
constructCaseRunZone(c_container[0], c[0], case_id);
});
};
c_container.parent().find('.form_comment')
.unbind('submit').bind('submit', rc_callback);
Expand Down
6 changes: 3 additions & 3 deletions tcms/templates/case/get_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ <h4>{% trans "Notes" %}:</h4>
<td colspan="3" style="border:none; background:none">
<h4>{% trans "Comments" %}:</h4>
{% for comment in case_comments %}
<div>
<div id="comment{{ comment.pk }}">
<div>
<span class="strong">#{{ forloop.counter }}</span>
<span class="strong">
Expand All @@ -73,15 +73,15 @@ <h4>{% trans "Comments" %}:</h4>
</div>
<div>
{% if review_mode and comment.user.pk == user.pk %}
<form action="{% url "comments-delete" %}" method="post" class="form_comment">
<form action="#" method="post" class="form_comment">
<input type="hidden" name="comment_id" value="{{ comment.pk }}" />
<input type="hidden" name="object_pk" value="{{ test_case.pk }}" />
<input class='commentdelete sprites' value='Delete Comment' type='submit' />
</form>
{% endif %}
</div>
</div>
{{ comment.comment|urlize|linebreaksbr }}
</div>
{% endfor %}
</td>
</tr>
Expand Down
17 changes: 9 additions & 8 deletions tcms/templates/case/get_details_case_run.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ <h4 style="padding-bottom:3px;">Comments</h4>
</table>
</form>
{% endif %}

<h4 class="borderB">Comments History ({{ comments_count }}):
{% ifequal comments_count 0 %}
<span>[ <a id="showText" href="javascript:void(0);">{% trans "Show All" %}</a> ]</span>
{% else %}
<span>[ <a id="showText" class="js-show-comments" data-param="comment{{ execution.pk }}" href="javascript:void(0);">{% trans "Show All" %}</a> ]</span>
{% endifequal %}
{% if comments_count > 0 %}
<span>[ <a id="showText" class="js-show-comments" data-param="comment{{ execution.pk }}" href="javascript:void(0);">Hide All</a> ]</span>
{% endif %}
</h4>
<ul class="comment" id="comment{{ execution.pk }}" style="display:none;">

{% if comments_count > 0 %}
<ul class="comment" id="comment{{ execution.pk }}">
{% for comment in execution_comments %}
<li>
<span>#{{ forloop.counter }}</span>
Expand All @@ -58,16 +59,16 @@ <h4 class="borderB">Comments History ({{ comments_count }}):
{{ comment.comment|urlize|linebreaksbr }}
<br>
{% if perms.comments.can_moderate and comment.user.pk == request.user.pk %}
<form action="{% url "comments-delete" %}" method="post" class="form_comment" style="display:inline;">
<form action="#" method="post" class="form_comment" style="display:inline;">
<input type="hidden" name="comment_id" value="{{ comment.pk }}" />
<input type="hidden" name="object_pk" value="{{ execution.case_id }}" />
<input class='commentdelete sprites' value='' type='submit' title="Remove Comment" />
</form>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div style="padding: 5px 9px 15px 18px;">
<h4>{% trans "Test Execution Information" %}</h4>
Expand Down
1 change: 1 addition & 0 deletions tcms/testcases/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ def ready(self):
pre_save.connect(signals.pre_save_clean, TestCase)
post_save.connect(signals.handle_emails_post_case_save, TestCase)
pre_delete.connect(signals.handle_emails_pre_case_delete, TestCase)
pre_delete.connect(signals.handle_comments_pre_delete, TestCase)
5 changes: 3 additions & 2 deletions tcms/testruns/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ class AppConfig(DjangoAppConfig):
name = 'tcms.testruns'

def ready(self):
from django.db.models.signals import post_save, pre_save
from .models import TestRun
from django.db.models.signals import post_save, pre_save, pre_delete
from .models import TestExecution, TestRun
from tcms import signals

post_save.connect(signals.handle_emails_post_run_save, sender=TestRun)
pre_save.connect(signals.pre_save_clean, sender=TestRun)
pre_delete.connect(signals.handle_comments_pre_delete, TestExecution)
1 change: 0 additions & 1 deletion tcms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@

# comments
url(r'^comments/post/', comments_views.post, name='comments-post'),
url(r'^comments/delete/', comments_views.delete, name='comments-delete'),

# Account information zone, such as login method
url(r'^accounts/', include(auth_urls)),
Expand Down