Skip to content

Commit

Permalink
Merge pull request #6360 from akatsoulas/seg-tags-moderation-tool
Browse files Browse the repository at this point in the history
Expose segmentation tags in moderation
  • Loading branch information
akatsoulas authored Nov 20, 2024
2 parents 247ccc3 + 382b6f8 commit 03b735d
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 108 deletions.
12 changes: 10 additions & 2 deletions kitsune/flagit/jinja2/flagit/content_moderation.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
<hgroup>
<h2 class="sumo-card-heading">{{ _('Flagged {t} (Reason: {r})')|f(t=object.content_type, r=object.get_reason_display()) }}</h2>
{% if object.notes %}
<p class="notes">{{ _('Other reason:') }} {{ object.notes }}</p>
{% if object.content_type.model == 'question' %}
<p class="notes">{{ _('Additional notes:') }} &nbsp;<a target="_blank" href="{{ object.content_object.get_absolute_url() }}">{{ object.notes }}</a></p>
{% else %}
<p class="notes">{{ _('Additional notes:') }} {{ object.notes }}</p>
{% endif %}
{% endif %}
</hgroup>
<div class="wrap">
Expand All @@ -33,4 +37,8 @@ <h3 class="sumo-card-heading"><br>{{ _('Update Status:') }}</h3>
{% else %}
<p>{{ _('There is no content pending moderation.') }}</p>
{% endfor %}
{% endblock %}
{% endblock %}

{# Hide the deactivation log on content moderation #}
{% block deactivation_log %}
{% endblock %}
34 changes: 21 additions & 13 deletions kitsune/flagit/jinja2/flagit/includes/flagged_question.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,28 @@ <h3 class="flagged-content__subheading">{{ _('Flagged:') }}</h3>
{% if object.reason == 'content_moderation' and question_model and user.has_perm('questions.change_question') %}
<h3 class="flagged-content__subheading">{{ _('Take Action:') }}</h3>
<div class="flagged-content__topic-update">
<label> {{ _('Current topic:') }} </label>
<div>
<p id="current-topic-{{ object.content_object.id }}" class="current-topic">{{ object.content_object.topic }}</p>
</div>
<label> {{ _('Current topic:') }} </label>
<p id="current-topic-{{ object.content_object.id }}" class="current-topic">{{ object.content_object.topic }}</p>

<form id="topic-update-form-{{ object.content_object.id }}" method="POST">
{% csrf_token %}
<label for="topic">{{ _('Change Topic:') }}</label>
<select id="topic-dropdown-{{ object.content_object.id }}" class="topic-dropdown" name="topic" data-question-id="{{ object.content_object.id }}">
{% for topic in object.available_topics %}
<option value="{{ topic.id }}" {% if topic.id == object.content_object.topic.id %}selected{% endif %}>{{ topic.title }}</option>
{% endfor %}
</select>

<div class="flagged-content__tag-select">
<label for="tag-select-{{ object.content_object.id }}">{{ _('Assign Tags:') }}</label>
<select id="tag-select-{{ object.content_object.id }}" name="tags" multiple class="tag-select" data-question-id="{{ object.content_object.id }}">
{% for tag in object.available_tags %}
<option value="{{ tag.id }}" {% if tag.get('id') in object.saved_tags %}selected{% endif %}>{{ tag.name }}</option>
{% endfor %}

<form id="topic-update-form-{{ object.content_object.id }}" method="POST">
{% csrf_token %}
<label for="topic">{{ _('Change Topic:') }}</label>
<select id="topic-dropdown-{{ object.content_object.id }}" class="topic-dropdown" name="topic" data-question-id="{{ object.content_object.id }}">
{% for topic in object.available_topics %}
<option value="{{ topic.id }}" {% if topic.id == object.content_object.topic.id %}selected{% endif %}>{{ topic.title }}</option>
{% endfor %}
</select>
</form>
</select>
</div>
</form>
</div>
{% endif %}
</div>
Expand Down
2 changes: 1 addition & 1 deletion kitsune/flagit/jinja2/flagit/queue.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<hgroup>
<h2 class="sumo-card-heading">{{ _('Flagged {t} (Reason: {r})')|f(t=object.content_type, r=object.get_reason_display()) }}</h2>
{% if object.notes %}
<p class="notes">{{ _('Other reason:') }} {{ object.notes }}</p>
<p class="notes">{{ _('Additional notes:') }} {{ object.notes }}</p>
{% endif %}
</hgroup>
<div class="wrap">
Expand Down
5 changes: 4 additions & 1 deletion kitsune/flagit/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from kitsune.questions.models import Answer, Question
from kitsune.sumo.templatetags.jinja_helpers import urlparams
from kitsune.sumo.urlresolvers import reverse
from kitsune.tags.models import SumoTag


def get_flagged_objects(reason=None, exclude_reason=None, content_model=None):
Expand Down Expand Up @@ -118,11 +119,13 @@ def moderate_content(request):
.prefetch_related("content_object__product")
)
objects = set_form_action_for_objects(objects, reason=FlaggedObject.REASON_CONTENT_MODERATION)
available_tags = SumoTag.objects.segmentation_tags().values("id", "name")

for obj in objects:
question = obj.content_object
obj.available_topics = Topic.active.filter(products=question.product, is_archived=False)

obj.available_tags = available_tags
obj.saved_tags = question.tags.values_list("id", flat=True)
return render(
request,
"flagit/content_moderation.html",
Expand Down
51 changes: 42 additions & 9 deletions kitsune/questions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import random
from collections import OrderedDict
from datetime import date, datetime, timedelta
from typing import List, Optional, Tuple, Union

import requests
from django.conf import settings
Expand All @@ -16,6 +17,7 @@
from django.db.models.functions import Now
from django.http import (
Http404,
HttpRequest,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
Expand Down Expand Up @@ -1030,6 +1032,14 @@ def add_tag_async(request, question_id):
If the question already has the tag, do nothing.
"""

if request.content_type == "application/json":
tag_ids = json.loads(request.body).get("tags", [])
question, tags = _add_tag(request, question_id, tag_ids)
if not tags:
return JsonResponse({"error": "Some tags do not exist or are invalid"}, status=400)
return JsonResponse({"message": "Tags updated successfully.", "data": {"tags": tags}})

try:
question, canonical_name = _add_tag(request, question_id)
except SumoTag.DoesNotExist:
Expand Down Expand Up @@ -1079,13 +1089,26 @@ def remove_tag_async(request, question_id):
If question doesn't have that tag, do nothing. Return value is JSON.
"""

question = get_object_or_404(Question, pk=question_id)
if request.content_type == "application/json":
data = json.loads(request.body)
tag_id = data.get("tagId")

try:
tag = SumoTag.objects.get(id=tag_id)
except SumoTag.DoesNotExist:
return JsonResponse({"error": "Tag does not exist."}, status=400)

question.tags.remove(tag)
question.clear_cached_tags()
return JsonResponse({"message": f"Tag '{tag.name}' removed successfully."})

name = request.POST.get("name")
if name:
question = get_object_or_404(Question, pk=question_id)
question.tags.remove(name)
question.clear_cached_tags()
return HttpResponse("{}", content_type="application/json")

return HttpResponseBadRequest(
json.dumps({"error": str(NO_TAG)}), content_type="application/json"
)
Expand Down Expand Up @@ -1424,17 +1447,27 @@ def _answers_data(request, question_id, form=None, watch_form=None, answer_previ
}


def _add_tag(request, question_id):
"""Add a named tag to a question, creating it first if appropriate.
Tag name (case-insensitive) must be in request.POST['tag-name'].
def _add_tag(
request: HttpRequest, question_id: int, tag_ids: Optional[List[int]] = None
) -> Tuple[Optional[Question], Union[List[str], str, None]]:
"""Add tags to a question by tag IDs or tag name.
If no tag name is provided or SumoTag.DoesNotExist is raised, return None.
Otherwise, return the canonicalized tag name.
If tag_ids is provided, adds tags with those IDs to the question.
Otherwise looks for tag name in request.POST['tag-name'].
Returns a tuple of (question, tag_names) if successful.
Returns (None, None) if no valid tags found or SumoTag.DoesNotExist raised.
"""

question = get_object_or_404(Question, pk=question_id)
if tag_ids:
sumo_tags = SumoTag.objects.filter(id__in=tag_ids)
if len(tag_ids) != len(sumo_tags):
return None, None
question.tags.add(*sumo_tags)
return question, list(sumo_tags.values_list("name", flat=True))

if tag_name := request.POST.get("tag-name", "").strip():
question = get_object_or_404(Question, pk=question_id)
# This raises SumoTag.DoesNotExist if the tag doesn't exist.
canonical_name = add_existing_tag(tag_name, question.tags)

Expand Down
Loading

0 comments on commit 03b735d

Please sign in to comment.