feat: added edit_by_label and closed_by_label in threads response (#32070)

This commit is contained in:
Muhammad Adeel Tajamul
2023-04-19 19:55:10 +05:00
committed by GitHub
parent 9fd2920c4c
commit 2f036e2df6
5 changed files with 164 additions and 2 deletions

View File

@@ -6,7 +6,7 @@ from urllib.parse import urlencode, urlunparse
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models import TextChoices
from django.urls import reverse
from django.utils.html import strip_tags
@@ -83,6 +83,7 @@ def get_context(course, request, thread=None):
"ta_user_ids": ta_user_ids,
"cc_requester": cc_requester,
"has_moderation_privilege": has_moderation_privilege,
"is_global_staff": is_global_staff,
}
@@ -155,6 +156,7 @@ class _ContentSerializer(serializers.Serializer):
anonymous_to_peers = serializers.BooleanField(default=False)
last_edit = serializers.SerializerMethodField(required=False)
edit_reason_code = serializers.CharField(required=False, validators=[validate_edit_reason_code])
edit_by_label = serializers.SerializerMethodField(required=False)
non_updatable_fields = set()
@@ -204,13 +206,25 @@ class _ContentSerializer(serializers.Serializer):
"""
is_staff = user_id in self.context["course_staff_user_ids"] or user_id in self.context["moderator_user_ids"]
is_ta = user_id in self.context["ta_user_ids"]
is_global_staff = self.context["is_global_staff"]
return (
"Staff" if is_staff else
"Staff" if (is_staff or is_global_staff) else
"Community TA" if is_ta else
None
)
def _get_user_label_from_username(self, username):
"""
Returns role label of user from username
Possible Role Labels: Staff, Community TA or None
"""
try:
user = User.objects.get(username=username)
return self._get_user_label(user.id)
except ObjectDoesNotExist:
return None
def get_author_label(self, obj):
"""
Returns the role label for the content author.
@@ -282,6 +296,17 @@ class _ContentSerializer(serializers.Serializer):
last_edit["reason"] = EDIT_REASON_CODES.get(reason_code)
return last_edit
def get_edit_by_label(self, obj):
"""
Returns the role label for the last edit user.
"""
is_user_author = str(obj['user_id']) == str(self.context['request'].user.id)
is_user_privileged = _validate_privileged_access(self.context)
edit_history = obj.get("edit_history")
if (is_user_author or is_user_privileged) and edit_history:
last_edit = edit_history[-1]
return self._get_user_label_from_username(last_edit.get('editor_username'))
class ThreadSerializer(_ContentSerializer):
"""
@@ -316,6 +341,7 @@ class ThreadSerializer(_ContentSerializer):
close_reason_code = serializers.CharField(required=False, validators=[validate_close_reason_code])
close_reason = serializers.SerializerMethodField()
closed_by = serializers.SerializerMethodField()
closed_by_label = serializers.SerializerMethodField(required=False)
non_updatable_fields = NON_UPDATABLE_THREAD_FIELDS
@@ -426,6 +452,14 @@ class ThreadSerializer(_ContentSerializer):
if _validate_privileged_access(self.context) or is_user_author:
return obj.get("closed_by")
def get_closed_by_label(self, obj):
"""
Returns the role label for the user who closed the post.
"""
is_user_author = str(obj['user_id']) == str(self.context['request'].user.id)
if is_user_author or _validate_privileged_access(self.context):
return self._get_user_label_from_username(obj.get("closed_by"))
def create(self, validated_data):
thread = Thread(user_id=self.context["cc_requester"]["id"], **validated_data)
thread.save()

View File

@@ -1472,6 +1472,7 @@ class GetCommentListTest(ForumsEnableMixin, CommentsServiceMockMixin, SharedModu
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
},
{
"id": "test_comment_2",
@@ -1498,6 +1499,7 @@ class GetCommentListTest(ForumsEnableMixin, CommentsServiceMockMixin, SharedModu
"anonymous": True,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
},
]
actual_comments = self.get_comment_list(
@@ -2232,6 +2234,7 @@ class CreateCommentTest(
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
assert actual == expected
expected_url = (
@@ -2328,6 +2331,7 @@ class CreateCommentTest(
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
assert actual == expected
expected_url = (
@@ -3154,6 +3158,7 @@ class UpdateCommentTest(
"child_count": 0,
"can_delete": True,
"last_edit": None,
"edit_by_label": None,
}
assert actual == expected
assert parsed_body(httpretty.last_request()) == {

View File

@@ -188,6 +188,8 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, SharedModuleStoreTe
"pinned": True,
"editable_fields": ["abuse_flagged", "copy_link", "following", "read", "voted"],
"abuse_flagged_count": None,
"edit_by_label": None,
"closed_by_label": None,
})
assert self.serialize(thread) == expected
@@ -242,6 +244,118 @@ class ThreadSerializerSerializationTest(SerializerTestMixin, SharedModuleStoreTe
serialized = self.serialize(thread_data)
assert 'response_count' not in serialized
@ddt.data(
(FORUM_ROLE_MODERATOR, True),
(FORUM_ROLE_STUDENT, False),
("author", True),
)
@ddt.unpack
def test_closed_by_label_field(self, role, visible):
"""
Tests if closed by field is visible to author and priviledged users
"""
moderator = UserFactory()
request_role = FORUM_ROLE_STUDENT if role == "author" else role
author = self.user if role == "author" else self.author
self.create_role(FORUM_ROLE_MODERATOR, [moderator])
self.create_role(request_role, [self.user])
thread = make_minimal_cs_thread({
"id": "test_thread",
"course_id": str(self.course.id),
"commentable_id": "test_topic",
"user_id": str(author.id),
"username": author.username,
"title": "Test Title",
"body": "Test body",
"pinned": True,
"votes": {"up_count": 4},
"comments_count": 5,
"unread_comments_count": 3,
"closed_by": moderator
})
closed_by_label = "Staff" if visible else None
closed_by = moderator if visible else None
can_delete = role != FORUM_ROLE_STUDENT
editable_fields = ["abuse_flagged", "copy_link", "following", "read", "voted"]
if role == "author":
editable_fields.extend(['anonymous', 'raw_body', 'title', 'topic_id', 'type'])
elif role == FORUM_ROLE_MODERATOR:
editable_fields.extend(['close_reason_code', 'closed', 'edit_reason_code', 'pinned',
'raw_body', 'title', 'topic_id', 'type'])
expected = self.expected_thread_data({
"author": author.username,
"can_delete": can_delete,
"vote_count": 4,
"comment_count": 6,
"unread_comment_count": 3,
"pinned": True,
"editable_fields": sorted(editable_fields),
"abuse_flagged_count": None,
"edit_by_label": None,
"closed_by_label": closed_by_label,
"closed_by": closed_by,
})
assert self.serialize(thread) == expected
@ddt.data(
(FORUM_ROLE_MODERATOR, True),
(FORUM_ROLE_STUDENT, False),
("author", True),
)
@ddt.unpack
def test_edit_by_label_field(self, role, visible):
"""
Tests if closed by field is visible to author and priviledged users
"""
moderator = UserFactory()
request_role = FORUM_ROLE_STUDENT if role == "author" else role
author = self.user if role == "author" else self.author
self.create_role(FORUM_ROLE_MODERATOR, [moderator])
self.create_role(request_role, [self.user])
thread = make_minimal_cs_thread({
"id": "test_thread",
"course_id": str(self.course.id),
"commentable_id": "test_topic",
"user_id": str(author.id),
"username": author.username,
"title": "Test Title",
"body": "Test body",
"pinned": True,
"votes": {"up_count": 4},
"edit_history": [{"editor_username": moderator}],
"comments_count": 5,
"unread_comments_count": 3,
"closed_by": None
})
edit_by_label = "Staff" if visible else None
can_delete = role != FORUM_ROLE_STUDENT
last_edit = None if role == FORUM_ROLE_STUDENT else {"editor_username": moderator}
editable_fields = ["abuse_flagged", "copy_link", "following", "read", "voted"]
if role == "author":
editable_fields.extend(['anonymous', 'raw_body', 'title', 'topic_id', 'type'])
elif role == FORUM_ROLE_MODERATOR:
editable_fields.extend(['close_reason_code', 'closed', 'edit_reason_code', 'pinned',
'raw_body', 'title', 'topic_id', 'type'])
expected = self.expected_thread_data({
"author": author.username,
"can_delete": can_delete,
"vote_count": 4,
"comment_count": 6,
"unread_comment_count": 3,
"pinned": True,
"editable_fields": sorted(editable_fields),
"abuse_flagged_count": None,
"last_edit": last_edit,
"edit_by_label": edit_by_label,
"closed_by_label": None,
"closed_by": None,
})
assert self.serialize(thread) == expected
@ddt.ddt
class CommentSerializerTest(SerializerTestMixin, SharedModuleStoreTestCase):
@@ -318,6 +432,7 @@ class CommentSerializerTest(SerializerTestMixin, SharedModuleStoreTestCase):
"child_count": 0,
"can_delete": False,
"last_edit": None,
"edit_by_label": None,
}
assert self.serialize(comment) == expected

View File

@@ -1709,6 +1709,8 @@ class LearnerThreadViewAPITest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"votes": {"up_count": 4},
"comments_count": 5,
"unread_comments_count": 3,
"closed_by_label": None,
"edit_by_label": None,
})],
"page": 1,
"num_pages": 1,
@@ -1968,6 +1970,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
response_data.update(overrides or {})
return response_data
@@ -2392,6 +2395,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
response = self.client.post(
self.url,
@@ -2483,6 +2487,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
response_data.update(overrides or {})
return response_data
@@ -2672,6 +2677,7 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
}
response = self.client.get(self.url)

View File

@@ -499,7 +499,9 @@ class CommentsServiceMockMixin:
"type": "discussion",
"response_count": 0,
"last_edit": None,
"edit_by_label": None,
"closed_by": None,
"closed_by_label": None,
"close_reason": None,
"close_reason_code": None,
}