Add an API endpoint for retiring a user from the cs_comments_service

This commit is contained in:
bmedx
2018-03-28 14:50:31 -04:00
parent 4841ff7943
commit ada7ded553
5 changed files with 217 additions and 54 deletions

View File

@@ -15,6 +15,7 @@ from nose.plugins.attrib import attr
from pytz import UTC
from rest_framework.parsers import JSONParser
from rest_framework.test import APIClient
from six import text_type
from common.test.utils import disable_signal
from discussion_api import api
@@ -27,7 +28,9 @@ from discussion_api.tests.utils import (
)
from django_comment_client.tests.utils import ForumsEnableMixin
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_storage
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from openedx.core.lib.token_utils import JwtBuilder
from student.models import get_retired_username_by_username
from student.tests.factories import CourseEnrollmentFactory, UserFactory, SuperuserFactory
from util.testing import PatchMediaTypeMixin, UrlResetMixin
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -77,7 +80,7 @@ class DiscussionAPIViewTestMixin(ForumsEnableMixin, CommentsServiceMockMixin, Ur
"""
cs_thread = make_minimal_cs_thread({
"id": "test_thread",
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"commentable_id": "test_topic",
"username": self.user.username,
"user_id": str(self.user.id),
@@ -95,7 +98,7 @@ class DiscussionAPIViewTestMixin(ForumsEnableMixin, CommentsServiceMockMixin, Ur
"""
cs_comment = make_minimal_cs_comment({
"id": "test_comment",
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_id": "test_thread",
"username": self.user.username,
"user_id": str(self.user.id),
@@ -125,7 +128,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CourseView"""
def setUp(self):
super(CourseViewTest, self).setUp()
self.url = reverse("discussion_course", kwargs={"course_id": unicode(self.course.id)})
self.url = reverse("discussion_course", kwargs={"course_id": text_type(self.course.id)})
def test_404(self):
response = self.client.get(
@@ -143,7 +146,7 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
response,
200,
{
"id": unicode(self.course.id),
"id": text_type(self.course.id),
"blackouts": [],
"thread_list_url": "http://testserver/api/discussion/v1/threads/?course_id=x%2Fy%2Fz",
"following_thread_list_url": (
@@ -154,6 +157,84 @@ class CourseViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
)
@httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class RetireViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""Tests for CourseView"""
def setUp(self):
super(RetireViewTest, self).setUp()
self.superuser = SuperuserFactory()
self.retired_username = get_retired_username_by_username(self.user.username)
self.url = reverse(
"retire_discussion_user",
kwargs={"username": text_type(self.user.username)}
)
def assert_response_correct(self, response, expected_status, expected_content):
"""
Assert that the response has the given status code and content
"""
self.assertEqual(response.status_code, expected_status)
if expected_content:
self.assertEqual(text_type(response.content), expected_content)
def build_jwt_headers(self, user):
"""
Helper function for creating headers for the JWT authentication.
"""
token = JwtBuilder(user).build_token([])
headers = {'HTTP_AUTHORIZATION': 'JWT ' + token}
return headers
def test_basic(self):
"""
Check successful retirement case
"""
self.register_get_user_retire_response(self.user)
headers = self.build_jwt_headers(self.superuser)
response = self.client.post(self.url, {'retired_username': self.retired_username}, **headers)
self.assert_response_correct(response, 204, "")
def test_bad_hash(self):
"""
Check that we fail on a hash mismatch with an appropriate error
"""
headers = self.build_jwt_headers(self.superuser)
response = self.client.post(self.url, {'retired_username': "this will never match"}, **headers)
self.assert_response_correct(response, 500, '"Mismatched hashed_username, bad salt?"')
def test_downstream_forums_error(self):
"""
Check that we bubble up errors from the comments service
"""
self.register_get_user_retire_response(self.user, status=500, body="Server error")
headers = self.build_jwt_headers(self.superuser)
response = self.client.post(self.url, {'retired_username': self.retired_username}, **headers)
self.assert_response_correct(response, 500, '"Server error"')
def test_nonexistent_user(self):
"""
Check that we handle unknown users appropriately
"""
nonexistent_username = "nonexistent user"
self.url = reverse(
"retire_discussion_user",
kwargs={"username": nonexistent_username}
)
self.retired_username = get_retired_username_by_username(nonexistent_username)
headers = self.build_jwt_headers(self.superuser)
response = self.client.post(self.url, {'retired_username': self.retired_username}, **headers)
self.assert_response_correct(response, 404, None)
def test_not_authenticated(self):
"""
Override the parent implementation of this, we auth differently
"""
pass
@ddt.ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
@@ -162,7 +243,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
def setUp(self):
super(CourseTopicsViewTest, self).setUp()
self.url = reverse("course_topics", kwargs={"course_id": unicode(self.course.id)})
self.url = reverse("course_topics", kwargs={"course_id": text_type(self.course.id)})
def create_course(self, modules_count, module_store, topics):
"""
@@ -177,7 +258,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
discussion_topics=topics
)
CourseEnrollmentFactory.create(user=self.user, course_id=course.id)
course_url = reverse("course_topics", kwargs={"course_id": unicode(course.id)})
course_url = reverse("course_topics", kwargs={"course_id": text_type(course.id)})
# add some discussion xblocks
for i in range(modules_count):
ItemFactory.create(
@@ -325,7 +406,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
"""
thread = make_minimal_cs_thread({
"id": "test_thread",
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"commentable_id": "test_topic",
"user_id": str(self.user.id),
"username": self.user.username,
@@ -350,7 +431,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
)
def test_404(self):
response = self.client.get(self.url, {"course_id": unicode("non/existent/course")})
response = self.client.get(self.url, {"course_id": text_type("non/existent/course")})
self.assert_response_correct(
response,
404,
@@ -373,7 +454,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
"editable_fields": ["abuse_flagged", "following", "read", "voted"],
})]
self.register_get_threads_response(source_threads, page=1, num_pages=2)
response = self.client.get(self.url, {"course_id": unicode(self.course.id), "following": ""})
response = self.client.get(self.url, {"course_id": text_type(self.course.id), "following": ""})
expected_response = make_paginated_api_response(
results=expected_threads,
count=1,
@@ -388,8 +469,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
expected_response
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"sort_key": ["activity"],
"page": ["1"],
"per_page": ["10"],
@@ -403,13 +484,13 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"view": query,
}
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"sort_key": ["activity"],
"page": ["1"],
"per_page": ["10"],
@@ -421,7 +502,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.register_get_threads_response([], page=1, num_pages=1)
response = self.client.get(
self.url,
{"course_id": unicode(self.course.id), "page": "18", "page_size": "4"}
{"course_id": text_type(self.course.id), "page": "18", "page_size": "4"}
)
self.assert_response_correct(
response,
@@ -429,8 +510,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
{"developer_message": "Page not found (No results on this page)."}
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"sort_key": ["activity"],
"page": ["18"],
"per_page": ["4"],
@@ -441,7 +522,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.register_get_threads_search_response([], None, num_pages=0)
response = self.client.get(
self.url,
{"course_id": unicode(self.course.id), "text_search": "test search string"}
{"course_id": text_type(self.course.id), "text_search": "test search string"}
)
expected_response = make_paginated_api_response(
@@ -454,8 +535,8 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
expected_response
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"sort_key": ["activity"],
"page": ["1"],
"per_page": ["10"],
@@ -469,7 +550,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
response = self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"following": following,
}
)
@@ -493,7 +574,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
response = self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"following": following,
}
)
@@ -509,7 +590,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
response = self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"following": "invalid-boolean",
}
)
@@ -541,13 +622,13 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"order_by": http_query,
}
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"page": ["1"],
"per_page": ["10"],
"sort_key": [cc_query],
@@ -564,13 +645,13 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.client.get(
self.url,
{
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"order_direction": "desc",
}
)
self.assert_last_query_params({
"user_id": [unicode(self.user.id)],
"course_id": [unicode(self.course.id)],
"user_id": [text_type(self.user.id)],
"course_id": [text_type(self.course.id)],
"sort_key": ["activity"],
"page": ["1"],
"per_page": ["10"],
@@ -583,7 +664,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
self.register_get_user_response(self.user)
self.register_get_threads_search_response([], None, num_pages=0)
response = self.client.get(self.url, {
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"text_search": "test search string",
"topic_id": "topic1, topic2",
})
@@ -616,7 +697,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
response = self.client.get(
self.url,
{"course_id": unicode(self.course.id), "requested_fields": "profile_image"},
{"course_id": text_type(self.course.id), "requested_fields": "profile_image"},
)
self.assertEqual(response.status_code, 200)
response_threads = json.loads(response.content)['results']
@@ -641,7 +722,7 @@ class ThreadViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pro
response = self.client.get(
self.url,
{"course_id": unicode(self.course.id), "requested_fields": "profile_image"},
{"course_id": text_type(self.course.id), "requested_fields": "profile_image"},
)
self.assertEqual(response.status_code, 200)
response_thread = json.loads(response.content)['results'][0]
@@ -667,7 +748,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
})
self.register_post_thread_response(cs_thread)
request_data = {
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"topic_id": "test_topic",
"type": "discussion",
"title": "Test Title",
@@ -684,7 +765,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.assertEqual(
httpretty.last_request().parsed_body,
{
"course_id": [unicode(self.course.id)],
"course_id": [text_type(self.course.id)],
"commentable_id": ["test_topic"],
"thread_type": ["discussion"],
"title": ["Test Title"],
@@ -755,7 +836,7 @@ class ThreadViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTest
self.assertEqual(
httpretty.last_request().parsed_body,
{
"course_id": [unicode(self.course.id)],
"course_id": [text_type(self.course.id)],
"commentable_id": ["test_topic"],
"thread_type": ["discussion"],
"title": ["Test Title"],
@@ -885,7 +966,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.register_get_user_response(self.user)
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"username": self.user.username,
"user_id": str(self.user.id),
})
@@ -943,7 +1024,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
already in overrides.
"""
overrides = overrides.copy() if overrides else {}
overrides.setdefault("course_id", unicode(self.course.id))
overrides.setdefault("course_id", text_type(self.course.id))
return make_minimal_cs_thread(overrides)
def expected_response_comment(self, overrides=None):
@@ -1006,7 +1087,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
})]
self.register_get_thread_response({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_type": "discussion",
"children": source_comments,
"resp_total": 100,
@@ -1043,7 +1124,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
self.register_get_user_response(self.user)
self.register_get_thread_response(make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_type": "discussion",
"resp_total": 10,
}))
@@ -1152,7 +1233,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
})
thread = self.make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_type": "discussion",
"children": [response_1, response_2],
"resp_total": 2,
@@ -1187,7 +1268,7 @@ class CommentViewSetListTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase, Pr
source_comments = [self.create_source_comment()]
self.register_get_thread_response({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_type": "discussion",
"children": source_comments,
"resp_total": 100,
@@ -1296,7 +1377,7 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.register_get_user_response(self.user)
cs_thread = make_minimal_cs_thread({
"id": "test_thread",
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
})
self.register_get_thread_response(cs_thread)
cs_comment = make_minimal_cs_comment({
@@ -1376,7 +1457,7 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
self.assertEqual(
httpretty.last_request().parsed_body,
{
"course_id": [unicode(self.course.id)],
"course_id": [text_type(self.course.id)],
"body": ["Test body"],
"user_id": [str(self.user.id)],
}
@@ -1475,7 +1556,7 @@ class CommentViewSetPartialUpdateTest(DiscussionAPIViewTestMixin, ModuleStoreTes
httpretty.last_request().parsed_body,
{
"body": ["Edited body"],
"course_id": [unicode(self.course.id)],
"course_id": [text_type(self.course.id)],
"user_id": [str(self.user.id)],
"anonymous": ["False"],
"anonymous_to_peers": ["False"],
@@ -1543,7 +1624,7 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase,
self.register_get_user_response(self.user)
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"commentable_id": "test_topic",
"username": self.user.username,
"user_id": str(self.user.id),
@@ -1568,7 +1649,7 @@ class ThreadViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase,
self.register_get_user_response(self.user)
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"username": self.user.username,
"user_id": str(self.user.id),
})
@@ -1598,7 +1679,7 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
return make_minimal_cs_comment({
"id": comment_id,
"parent_id": parent_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"thread_id": self.thread_id,
"thread_type": "discussion",
"username": self.user.username,
@@ -1615,7 +1696,7 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
cs_comment = self.make_comment_data(self.comment_id, None, [cs_comment_child])
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"children": [cs_comment],
})
self.register_get_thread_response(cs_thread)
@@ -1663,7 +1744,7 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
cs_comment = self.make_comment_data(self.comment_id, None, [cs_comment_child])
cs_thread = make_minimal_cs_thread({
"id": self.thread_id,
"course_id": unicode(self.course.id),
"course_id": text_type(self.course.id),
"children": [cs_comment],
})
self.register_get_thread_response(cs_thread)
@@ -1687,7 +1768,7 @@ class CommentViewSetRetrieveTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase
cs_comment = self.make_comment_data(self.comment_id, None, [cs_comment_child])
cs_thread = make_minimal_cs_thread({
'id': self.thread_id,
'course_id': unicode(self.course.id),
'course_id': text_type(self.course.id),
'children': [cs_comment],
})
self.register_get_thread_response(cs_thread)

View File

@@ -216,6 +216,16 @@ class CommentsServiceMockMixin(object):
status=200
)
def register_get_user_retire_response(self, user, status=200, body=""):
"""Register a mock response for GET on the CS user retirement endpoint"""
assert httpretty.is_enabled(), 'httpretty must be enabled to mock calls.'
httpretty.register_uri(
httpretty.POST,
"http://localhost:4567/api/v1/users/{id}/retire".format(id=user.id),
body=body,
status=status
)
def register_subscribed_threads_response(self, user, threads, page, num_pages):
"""Register a mock response for GET on the CS user instance endpoint"""
assert httpretty.is_enabled(), 'httpretty must be enabled to mock calls.'

View File

@@ -5,7 +5,7 @@ from django.conf import settings
from django.conf.urls import include, url
from rest_framework.routers import SimpleRouter
from discussion_api.views import CommentViewSet, CourseTopicsView, CourseView, ThreadViewSet
from discussion_api.views import CommentViewSet, CourseTopicsView, CourseView, ThreadViewSet, RetireUserView
ROUTER = SimpleRouter()
ROUTER.register("threads", ThreadViewSet, base_name="thread")
@@ -17,6 +17,7 @@ urlpatterns = [
CourseView.as_view(),
name="discussion_course"
),
url(r"^v1/users/{}".format(settings.USERNAME_PATTERN), RetireUserView.as_view(), name="retire_discussion_user"),
url(
r"^v1/course_topics/{}".format(settings.COURSE_ID_PATTERN),
CourseTopicsView.as_view(),

View File

@@ -2,13 +2,19 @@
Discussion API views
"""
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from edx_rest_framework_extensions.authentication import JwtAuthentication
from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions
from rest_framework import status
from rest_framework.exceptions import UnsupportedMediaType
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet
from six import text_type
from lms.lib import comment_client
from discussion_api.api import (
create_comment,
create_thread,
@@ -26,6 +32,8 @@ from discussion_api.api import (
from discussion_api.forms import CommentGetForm, CommentListGetForm, ThreadListGetForm
from openedx.core.lib.api.parsers import MergePatchParser
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser
from student.models import get_potentially_retired_user_by_username_and_hash
from xmodule.modulestore.django import modulestore
@@ -512,3 +520,48 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
if request.content_type != MergePatchParser.media_type:
raise UnsupportedMediaType(request.content_type)
return Response(update_comment(request, comment_id, request.data))
class RetireUserView(APIView):
"""
**Use Cases**
A superuser or the user with the settings.RETIREMENT_SERVICE_WORKER_USERNAME
can "retire" the user's data from the comments service, which will remove
personal information and blank all posts / comments the user has made.
**Example Requests**:
POST /api/discussion/v1/retire_user/
{
"retired_username": "old_user_name"
}
**Example Response**:
Empty string
"""
authentication_classes = (JwtAuthentication,)
permission_classes = (permissions.IsAuthenticated, CanRetireUser)
def post(self, request, username):
"""
Implements the retirement endpoint.
"""
user_model = get_user_model()
retired_username = request.data['retired_username']
try:
user = get_potentially_retired_user_by_username_and_hash(username, retired_username)
cc_user = comment_client.User.from_django_user(user)
# We can't count on the LMS username being un-retired at this point,
# so we pass the old username as a parameter to describe which
# user to retire. This will either succeed or throw an error which
# should be good to raise from here.
cc_user.retire(username)
except user_model.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
except Exception as exc: # pylint: disable=broad-except
return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -2,9 +2,7 @@
from six import text_type
import settings
import models
import utils
@@ -169,6 +167,19 @@ class User(models.Model):
raise
self._update_from_response(response)
def retire(self, retired_username):
url = _url_for_retire(self.id)
params = {'retired_username': retired_username}
utils.perform_request(
'post',
url,
params,
raw=True,
metric_action='user.retire',
metric_tags=self._metric_tags
)
def _url_for_vote_comment(comment_id):
return "{prefix}/comments/{comment_id}/votes".format(prefix=settings.PREFIX, comment_id=comment_id)
@@ -195,3 +206,10 @@ def _url_for_read(user_id):
Returns cs_comments_service url endpoint to mark thread as read for given user_id
"""
return "{prefix}/users/{user_id}/read".format(prefix=settings.PREFIX, user_id=user_id)
def _url_for_retire(user_id):
"""
Returns cs_comments_service url endpoint to retire a user (remove all post content, etc.)
"""
return "{prefix}/users/{user_id}/retire".format(prefix=settings.PREFIX, user_id=user_id)