Repository: oppia/oppia

Issue:
[Contributor Dashboard] Consolidate add/remove contribution rights controller handlers
Instead of having separate list/add/remove contribution rights handlers in core/controllers/admin.py, it may make more sense to have one handler "ContributionRightsHandler" which will have get/post/delete methods to handle fetch/add/removal of rights.

See https://github.com/oppia/oppia/pull/11862/files#r571696871 for more context.


Discussion:

Resolving patch:
diff --git a/core/controllers/contributor_dashboard_admin.py b/core/controllers/contributor_dashboard_admin.py
--- a/core/controllers/contributor_dashboard_admin.py
+++ b/core/controllers/contributor_dashboard_admin.py
@@ -41,8 +41,8 @@ def get(self):
         self.render_template('contributor-dashboard-admin-page.mainpage.html')
 
 
-class AddContributionRightsHandler(base.BaseHandler):
-    """Handles adding contribution rights for contributor dashboard page."""
+class ContributionRightsHandler(base.BaseHandler):
+    """Handles contribution rights of a user on contributor dashboard page."""
 
     GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
     URL_PATH_ARGS_SCHEMAS = {
@@ -69,6 +69,22 @@ class AddContributionRightsHandler(base.BaseHandler):
                 },
                 'default_value': None
             }
+        },
+        'DELETE': {
+            'username': {
+                'schema': {
+                    'type': 'basestring'
+                }
+            },
+            'language_code': {
+                'schema': {
+                    'type': 'basestring',
+                    'validators': [{
+                        'id': 'is_supported_audio_language_code'
+                    }]
+                },
+                'default_value': None
+            }
         }
     }
 
@@ -112,47 +128,15 @@ def post(self, category):
                 user_id, category, language_code=language_code)
         self.render_json({})
 
-
-class RemoveContributionRightsHandler(base.BaseHandler):
-    """Handles removing contribution rights for contributor dashboard."""
-
-    GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
-    URL_PATH_ARGS_SCHEMAS = {
-        'category': {
-            'schema': {
-                'type': 'basestring',
-                'choices': constants.CONTRIBUTION_RIGHT_CATEGORIES
-            }
-        }
-    }
-    HANDLER_ARGS_SCHEMAS = {
-        'PUT': {
-            'username': {
-                'schema': {
-                    'type': 'basestring'
-                }
-            },
-            'language_code': {
-                'schema': {
-                    'type': 'basestring',
-                    'validators': [{
-                        'id': 'is_supported_audio_language_code'
-                    }]
-                },
-                'default_value': None
-            }
-        }
-    }
-
     @acl_decorators.can_manage_contributors_role
-    def put(self, category):
-        username = self.normalized_payload.get('username', None)
+    def delete(self, category):
+        username = self.normalized_request.get('username', None)
         user_id = user_services.get_user_id_from_username(username)
         if user_id is None:
             raise self.InvalidInputException(
                 'Invalid username: %s' % username)
 
-        language_code = self.normalized_payload.get('language_code', None)
+        language_code = self.normalized_request.get('language_code', None)
 
         if (category ==
                 constants.CONTRIBUTION_RIGHT_CATEGORY_REVIEW_TRANSLATION):
diff --git a/core/controllers/payload_validator.py b/core/controllers/payload_validator.py
--- a/core/controllers/payload_validator.py
+++ b/core/controllers/payload_validator.py
@@ -86,7 +86,6 @@ def validate(handler_args, handler_args_schemas, allowed_extra_args):
     'BulkEmailWebhookEndpoint',
     'CollectionSummariesHandler',
     'ConsoleErrorPage',
-    'ContributorRightsDataHandler',
     'CreatorDashboardHandler',
     'CreatorDashboardPage',
     'DeferredTasksHandler',
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -240,11 +240,8 @@ def ui_access_wrapper(self, *args, **kwargs):
         r'/admintopicscsvdownloadhandler',
         admin.AdminTopicsCsvFileDownloader),
     get_redirect_route(
-        r'/addcontributionrightshandler/<category>',
-        contributor_dashboard_admin.AddContributionRightsHandler),
-    get_redirect_route(
-        r'/removecontributionrightshandler/<category>',
-        contributor_dashboard_admin.RemoveContributionRightsHandler),
+        r'/contributionrightshandler/<category>',
+        contributor_dashboard_admin.ContributionRightsHandler),
     get_redirect_route(
         r'/getcontributorusershandler/<category>',
         contributor_dashboard_admin.ContributorUsersListHandler),


Test patch:
diff --git a/core/controllers/contributor_dashboard_admin_test.py b/core/controllers/contributor_dashboard_admin_test.py
--- a/core/controllers/contributor_dashboard_admin_test.py
+++ b/core/controllers/contributor_dashboard_admin_test.py
@@ -74,8 +74,8 @@ def test_translation_admin_can_access_page(self):
         self.logout()
 
 
-class AddContributionRightsHandlerTest(test_utils.GenericTestBase):
-    """Tests related to add reviewers for contributor's
+class ContributionRightsHandlerTest(test_utils.GenericTestBase):
+    """Tests related to reviewers for contributor's
     suggestion/application.
     """
 
@@ -85,7 +85,7 @@ class AddContributionRightsHandlerTest(test_utils.GenericTestBase):
     QUESTION_ADMIN_EMAIL = 'questionadmin@example.com'
 
     def setUp(self):
-        super(AddContributionRightsHandlerTest, self).setUp()
+        super(ContributionRightsHandlerTest, self).setUp()
         self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
         self.signup(self.QUESTION_REVIEWER_EMAIL, 'question')
         self.signup(self.TRANSLATION_REVIEWER_EMAIL, 'translator')
@@ -109,7 +109,7 @@ def test_add_reviewer_with_invalid_username_raise_error(self):
 
         csrf_token = self.get_new_csrf_token()
         response = self.post_json(
-            '/addcontributionrightshandler/translation', {
+            '/contributionrightshandler/translation', {
                 'username': 'invalid',
                 'language_code': 'en'
             }, csrf_token=csrf_token, expected_status_int=400)
@@ -126,7 +126,7 @@ def test_add_translation_reviewer(self):
 
         csrf_token = self.get_new_csrf_token()
         self.post_json(
-            '/addcontributionrightshandler/translation', {
+            '/contributionrightshandler/translation', {
                 'username': 'translator',
                 'language_code': 'hi'
             }, csrf_token=csrf_token)
@@ -141,7 +141,7 @@ def test_assigning_same_language_for_translation_review_raise_error(self):
                 self.translation_reviewer_id, language_code='hi'))
         csrf_token = self.get_new_csrf_token()
         self.post_json(
-            '/addcontributionrightshandler/translation', {
+            '/contributionrightshandler/translation', {
                 'username': 'translator',
                 'language_code': 'hi'
             }, csrf_token=csrf_token)
@@ -149,7 +149,7 @@ def test_assigning_same_language_for_translation_review_raise_error(self):
             user_services.can_review_translation_suggestions(
                 self.translation_reviewer_id, language_code='hi'))
         response = self.post_json(
-            '/addcontributionrightshandler/translation', {
+            '/contributionrightshandler/translation', {
                 'username': 'translator',
                 'language_code': 'hi'
             }, csrf_token=csrf_token, expected_status_int=400)
@@ -167,7 +167,7 @@ def test_add_question_reviewer(self):
 
         csrf_token = self.get_new_csrf_token()
         self.post_json(
-            '/addcontributionrightshandler/question', {
+            '/contributionrightshandler/question', {
                 'username': 'question'
             }, csrf_token=csrf_token)
 
@@ -181,14 +181,14 @@ def test_assigning_same_user_as_question_reviewer_raise_error(self):
         self.login(self.QUESTION_ADMIN_EMAIL)
         csrf_token = self.get_new_csrf_token()
         response = self.post_json(
-            '/addcontributionrightshandler/question', {
+            '/contributionrightshandler/question', {
                 'username': 'question'
             }, csrf_token=csrf_token)
         self.assertTrue(user_services.can_review_question_suggestions(
             self.question_reviewer_id))
 
         response = self.post_json(
-            '/addcontributionrightshandler/question', {
+            '/contributionrightshandler/question', {
                 'username': 'question'
             }, csrf_token=csrf_token, expected_status_int=400)
 
@@ -204,7 +204,7 @@ def test_add_question_submitter(self):
 
         csrf_token = self.get_new_csrf_token()
         self.post_json(
-            '/addcontributionrightshandler/submit_question', {
+            '/contributionrightshandler/submit_question', {
                 'username': 'question'
             }, csrf_token=csrf_token)
 
@@ -218,14 +218,14 @@ def test_assigning_same_user_as_question_submitter_raise_error(self):
         self.login(self.QUESTION_ADMIN_EMAIL)
         csrf_token = self.get_new_csrf_token()
         response = self.post_json(
-            '/addcontributionrightshandler/submit_question', {
+            '/contributionrightshandler/submit_question', {
                 'username': 'question'
             }, csrf_token=csrf_token)
         self.assertTrue(user_services.can_submit_question_suggestions(
             self.question_reviewer_id))
 
         response = self.post_json(
-            '/addcontributionrightshandler/submit_question', {
+            '/contributionrightshandler/submit_question', {
                 'username': 'question'
             }, csrf_token=csrf_token, expected_status_int=400)
 
@@ -238,7 +238,7 @@ def test_add_reviewer_for_invalid_category_raise_error(self):
 
         csrf_token = self.get_new_csrf_token()
         response = self.post_json(
-            '/addcontributionrightshandler/invalid', {
+            '/contributionrightshandler/invalid', {
                 'username': 'question'
             }, csrf_token=csrf_token, expected_status_int=400)
 
@@ -248,42 +248,13 @@ def test_add_reviewer_for_invalid_category_raise_error(self):
             'is not in the allowed range of choices: %s' % (
                 constants.CONTRIBUTION_RIGHT_CATEGORIES))
 
-
-class RemoveContributionRightsHandlerTest(test_utils.GenericTestBase):
-    """Tests related to remove reviewers from contributor dashboard page."""
-
-    TRANSLATION_REVIEWER_EMAIL = 'translationreviewer@example.com'
-    QUESTION_REVIEWER_EMAIL = 'questionreviewer@example.com'
-    TRANSLATION_ADMIN_EMAIL = 'translationadmin@example.com'
-    QUESTION_ADMIN_EMAIL = 'questionadmin@example.com'
-
-    def setUp(self):
-        super(RemoveContributionRightsHandlerTest, self).setUp()
-        self.signup(self.ADMIN_EMAIL, self.ADMIN_USERNAME)
-        self.signup(self.TRANSLATION_REVIEWER_EMAIL, 'translator')
-        self.signup(self.QUESTION_REVIEWER_EMAIL, 'question')
-        self.signup(self.TRANSLATION_ADMIN_EMAIL, 'translationAdmen')
-        self.signup(self.QUESTION_ADMIN_EMAIL, 'questionAdmen')
-
-        self.translation_reviewer_id = self.get_user_id_from_email(
-            self.TRANSLATION_REVIEWER_EMAIL)
-        self.question_reviewer_id = self.get_user_id_from_email(
-            self.QUESTION_REVIEWER_EMAIL)
-        user_services.update_user_role(
-            self.get_user_id_from_email(self.TRANSLATION_ADMIN_EMAIL),
-            feconf.ROLE_ID_TRANSLATION_ADMIN)
-        user_services.update_user_role(
-            self.get_user_id_from_email(self.QUESTION_ADMIN_EMAIL),
-            feconf.ROLE_ID_QUESTION_ADMIN)
-
-    def test_add_reviewer_with_invalid_username_raise_error(self):
+    def test_remove_reviewer_with_invalid_username_raise_error(self):
         self.login(self.QUESTION_ADMIN_EMAIL)
 
-        csrf_token = self.get_new_csrf_token()
-        response = self.put_json(
-            '/removecontributionrightshandler/question', {
+        response = self.delete_json(
+            '/contributionrightshandler/question', params={
                 'username': 'invalid'
-            }, csrf_token=csrf_token, expected_status_int=400)
+            }, expected_status_int=400)
 
         self.assertEqual(
             response['error'], 'Invalid username: invalid')
@@ -297,12 +268,11 @@ def test_remove_translation_reviewer(self):
 
         self.login(self.TRANSLATION_ADMIN_EMAIL)
 
-        csrf_token = self.get_new_csrf_token()
-        self.put_json(
-            '/removecontributionrightshandler/translation', {
+        self.delete_json(
+            '/contributionrightshandler/translation', params={
                 'username': 'translator',
                 'language_code': 'hi'
-            }, csrf_token=csrf_token)
+            })
 
         self.assertFalse(user_services.can_review_translation_suggestions(
             self.translation_reviewer_id, language_code='hi'))
@@ -312,12 +282,11 @@ def test_remove_unassigned_translation_reviewer_raise_error(self):
             user_services.can_review_translation_suggestions(
                 self.translation_reviewer_id, language_code='hi'))
         self.login(self.TRANSLATION_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        response = self.put_json(
-            '/removecontributionrightshandler/translation', {
+        response = self.delete_json(
+            '/contributionrightshandler/translation', params={
                 'username': 'translator',
                 'language_code': 'hi'
-            }, csrf_token=csrf_token, expected_status_int=400)
+            }, expected_status_int=400)
 
         self.assertEqual(
             response['error'],
@@ -330,11 +299,10 @@ def test_remove_question_reviewer(self):
             self.question_reviewer_id))
 
         self.login(self.QUESTION_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        self.put_json(
-            '/removecontributionrightshandler/question', {
+        self.delete_json(
+            '/contributionrightshandler/question', params={
                 'username': 'question',
-            }, csrf_token=csrf_token)
+            })
 
         self.assertFalse(user_services.can_review_question_suggestions(
             self.question_reviewer_id))
@@ -344,11 +312,10 @@ def test_removing_unassigned_question_reviewer_raise_error(self):
             self.question_reviewer_id))
 
         self.login(self.QUESTION_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        response = self.put_json(
-            '/removecontributionrightshandler/question', {
+        response = self.delete_json(
+            '/contributionrightshandler/question', params={
                 'username': 'question'
-            }, csrf_token=csrf_token, expected_status_int=400)
+            }, expected_status_int=400)
 
         self.assertEqual(
             response['error'],
@@ -360,11 +327,10 @@ def test_remove_question_submitter(self):
             self.question_reviewer_id))
 
         self.login(self.QUESTION_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        self.put_json(
-            '/removecontributionrightshandler/submit_question', {
+        self.delete_json(
+            '/contributionrightshandler/submit_question', params={
                 'username': 'question'
-            }, csrf_token=csrf_token)
+            })
 
         self.assertFalse(user_services.can_submit_question_suggestions(
             self.question_reviewer_id))
@@ -374,11 +340,10 @@ def test_removing_unassigned_question_submitter_raise_error(self):
             self.question_reviewer_id))
 
         self.login(self.QUESTION_ADMIN_EMAIL)
-        csrf_token = self.get_new_csrf_token()
-        response = self.put_json(
-            '/removecontributionrightshandler/submit_question', {
+        response = self.delete_json(
+            '/contributionrightshandler/submit_question', params={
                 'username': 'question'
-            }, csrf_token=csrf_token, expected_status_int=400)
+            }, expected_status_int=400)
 
         self.assertEqual(
             response['error'],