Skip to content

Commit

Permalink
Creates voiceover admin page (oppia#19561)
Browse files Browse the repository at this point in the history
* Creates voiceover model

* Adds test for entity voiceover model

* Add voiceover domain and service methods.

* Adds voiceover domain and services tests

* Fixes mypy checks

* Creates voiceover policy model

* Fixes linter issues.

* Address review comments

* Adds regex check for language accent code

* Updates voiceover policy model name.

* Adds voiceover language accent constants

* Adds language accent codes list for voiceovers

* updates docstring

* updates docstring

* Creates voiceover admin page structure.

* Deletes voice policy dir and shifts it into voiceover dir

* Adds helper methods to get & save in VoiceoverAutogenerationPolicyModel

* Adds navbar in the voiceover admin page.

* Creates json files to stores L-a constants.

* Adds test to validate voiceover language accent constants.

* Removes dead code

* Creates voiceover admin backend api service

* Updates code comment

* Updates code comment

* Adds voiceover admin controller tests.

* Adds fronetend tests.

* Updates Readme.

* Fixes lint checks

* Adds voicover admin component methods.

* Adds language codes saving functionality

* Adds frontend tests.

* Adds CSS

* Adds CSS

* Fixes backend and lint checks

* Adds backend test

* Adds frontend tests

* Fixes typescript checks

* Adds confirmation modal and updates saving workflow.

* Updates UI

* Updates CSS

* Updates css

* Updates html alignment

* Updates dropdown css

* Updates test description
  • Loading branch information
Nik-09 authored Jan 31, 2024
1 parent b89a122 commit ac81d84
Show file tree
Hide file tree
Showing 30 changed files with 1,919 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@


# Audio Voiceover project
/core/controllers/voiceover*.py @oppia/lace-backend-reviewers
/core/domain/voiceover*.py @oppia/lace-backend-reviewers
/core/templates/domain/voiceover/ @oppia/lace-frontend-reviewers


# Blog project
Expand Down Expand Up @@ -572,6 +574,7 @@
/core/templates/pages/moderator-page/ @oppia/lace-frontend-reviewers
/core/templates/pages/blog-admin-page/ @oppia/lace-frontend-reviewers
/core/templates/pages/classroom-admin-page/ @oppia/lace-frontend-reviewers
/core/templates/pages/voiceover-admin-page/ @oppia/lace-frontend-reviewers


# Release team.
Expand Down
47 changes: 47 additions & 0 deletions core/controllers/acl_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3780,6 +3780,53 @@ def test_can_access_classroom_admin_page(
return test_can_access_classroom_admin_page


def can_access_voiceover_admin_page(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
"""Decorator to check whether user can access vocieover admin page.
Args:
handler: function. The function to be decorated.
Returns:
function. The newly decorated function that now checks if the user has
permission to access the voiceover admin page.
"""

# Here we use type Any because this method can accept arbitrary number of
# arguments with different types.
@functools.wraps(handler)
def test_can_access_voiceover_admin_page(
self: _SelfBaseHandlerType, **kwargs: Any
) -> _GenericHandlerFunctionReturnType:
"""Checks if the user is logged in and can access voiceover admin page.
Args:
**kwargs: *. Keyword arguments.
Returns:
*. The return value of the decorated function.
Raises:
NotLoggedInException. The user is not logged in.
UnauthorizedUserException. The user does not have credentials to
access the voiceover admin page.
"""
if not self.user_id:
raise base.UserFacingExceptions.NotLoggedInException

if (
role_services.ACTION_ACCESS_VOICEOVER_ADMIN_PAGE in
self.user.actions
):
return handler(self, **kwargs)

raise self.UnauthorizedUserException(
'You do not have credentials to access voiceover admin page.')

return test_can_access_voiceover_admin_page


def can_change_topic_publication_status(
handler: Callable[..., _GenericHandlerFunctionReturnType]
) -> Callable[..., _GenericHandlerFunctionReturnType]:
Expand Down
60 changes: 59 additions & 1 deletion core/controllers/acl_decorators_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7888,7 +7888,7 @@ def test_guest_user_cannot_access_classroom_admin_page(self) -> None:
response['error'],
'You must be logged in to access this resource.')

def test_classroom_admin_can_manage_blog_editors(self) -> None:
def test_classroom_admin_can_access_classroom_admin_page(self) -> None:
self.login(self.CLASSROOM_ADMIN_EMAIL)

with self.swap(self, 'testapp', self.mock_testapp):
Expand All @@ -7898,6 +7898,64 @@ def test_classroom_admin_can_manage_blog_editors(self) -> None:
self.logout()


class CanAccessVoiceoverAdminPageDecoratorTests(test_utils.GenericTestBase):
"""Tests for can_access_voiceover_admin_page decorator."""

username = 'user'
user_email = 'user@example.com'

class MockHandler(base.BaseHandler[Dict[str, str], Dict[str, str]]):
GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
HANDLER_ARGS_SCHEMAS: Dict[str, Dict[str, str]] = {'GET': {}}

@acl_decorators.can_access_voiceover_admin_page
def get(self) -> None:
self.render_json({'success': 1})

def setUp(self) -> None:
super().setUp()
self.signup(self.user_email, self.username)
self.signup(self.VOICEOVER_ADMIN_EMAIL, self.VOICEOVER_ADMIN_USERNAME)

self.add_user_role(
self.VOICEOVER_ADMIN_USERNAME, feconf.ROLE_ID_CURRICULUM_ADMIN)

self.mock_testapp = webtest.TestApp(webapp2.WSGIApplication(
[webapp2.Route('/voiceover-admin', self.MockHandler)],
debug=feconf.DEBUG,
))

def test_normal_user_cannot_access_voiceover_admin_page(self) -> None:
self.login(self.user_email)
with self.swap(self, 'testapp', self.mock_testapp):
response = self.get_json(
'/voiceover-admin', expected_status_int=401)

self.assertEqual(
response['error'],
'You do not have credentials to access voiceover admin page.')
self.logout()

def test_guest_user_cannot_access_voiceover_admin_page(self) -> None:
with self.swap(self, 'testapp', self.mock_testapp):
response = self.get_json(
'/voiceover-admin', expected_status_int=401)

self.assertEqual(
response['error'],
'You must be logged in to access this resource.')

def test_voiceover_admin_can_access_voiceover_admin_page(self) -> None:
self.login(self.VOICEOVER_ADMIN_EMAIL)

with self.swap(self, 'testapp', self.mock_testapp):
response = self.get_json('/voiceover-admin')

self.assertEqual(response['success'], 1)
self.logout()


class IsFromOppiaAndroidBuildDecoratorTests(test_utils.GenericTestBase):
"""Tests for is_from_oppia_android_build decorator."""

Expand Down
124 changes: 124 additions & 0 deletions core/controllers/voiceover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright 2024 The Oppia Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Controllers for the voiceover admin page."""

from __future__ import annotations

from core import feconf
from core.controllers import acl_decorators
from core.controllers import base
from core.domain import voiceover_services

from typing import Dict, TypedDict


class VoiceoverAdminPage(base.BaseHandler[Dict[str, str], Dict[str, str]]):
"""Renders the voiceover admin page."""

URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
HANDLER_ARGS_SCHEMAS: Dict[str, Dict[str, str]] = {'GET': {}}

@acl_decorators.can_access_voiceover_admin_page
def get(self) -> None:
"""Renders the voiceover admin page."""
self.render_template('voiceover-admin-page.mainpage.html')


class VoiceoverAdminDataHandler(
base.BaseHandler[Dict[str, str], Dict[str, str]]
):
"""Fetches relevant data for the voiceover admin page."""

GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
HANDLER_ARGS_SCHEMAS: Dict[str, Dict[str, str]] = {'GET': {}}

@acl_decorators.can_access_voiceover_admin_page
def get(self) -> None:
"""Retrieves relevant data for the voiceover admin page."""

language_accent_master_list: Dict[str, Dict[str, str]] = (
voiceover_services.get_language_accent_master_list())

language_codes_mapping: Dict[str, Dict[str, bool]] = (
voiceover_services.get_all_language_accent_codes_for_voiceovers())
self.values.update({
'language_accent_master_list':
language_accent_master_list,
'language_codes_mapping': language_codes_mapping
})
self.render_json(self.values)


class PutLanguageCodesHandlerNormalizedPayloadDict(TypedDict):
"""Dict representation of VoiceoverLanguageCodesMappingHandler's
normalized_request dictionary.
"""

language_codes_mapping: Dict[str, Dict[str, bool]]


class VoiceoverLanguageCodesMappingHandler(
base.BaseHandler[
PutLanguageCodesHandlerNormalizedPayloadDict,
Dict[str, str]
]
):
"""Updates the language codes mapping field in the backend."""

GET_HANDLER_ERROR_RETURN_TYPE = feconf.HANDLER_TYPE_JSON
URL_PATH_ARGS_SCHEMAS: Dict[str, str] = {}
HANDLER_ARGS_SCHEMAS = {
'PUT': {
'language_codes_mapping': {
'schema': {
'type': 'variable_keys_dict',
'keys': {
'schema': {
'type': 'basestring'
}
},
'values': {
'schema': {
'type': 'variable_keys_dict',
'keys': {
'schema': {
'type': 'basestring'
}
},
'values': {
'schema': {
'type': 'bool'
}
}
}
}
}
}
}
}

@acl_decorators.can_access_voiceover_admin_page
def put(self) -> None:
"""Updates the language codes mapping for the Oppia supported
voiceovers.
"""
assert self.normalized_payload is not None
language_codes_mapping = (
self.normalized_payload['language_codes_mapping'])

voiceover_services.save_language_accent_support(
language_codes_mapping)
self.render_json(self.values)
Loading

0 comments on commit ac81d84

Please sign in to comment.