Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { availableLanguages, currentLanguage, sortLanguages } from '../i18n';

import client from 'shared/client';

export default {
Expand Down
27 changes: 24 additions & 3 deletions contentcuration/contentcuration/frontend/shared/views/AppBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,30 @@
</VList>
</Menu>
</template>
<VBtn v-else href="/accounts" flat>
{{ $tr('logIn') }}
</VBtn>
<template v-else>
<Menu>
<template #activator="{ on }">
<VBtn flat style="text-transform: none;" v-on="on">
<Icon>person</Icon>
<Icon>arrow_drop_down</Icon>
</VBtn>
</template>
<VList>
<VListTile :href="'/accounts/'">
<VListTileAction>
<Icon>exit_to_app</Icon>
</VListTileAction>
<VListTileTitle v-text="$tr('logIn')" />
</VListTile>
<VListTile @click="showLanguageModal = true">
<VListTileAction>
<Icon>language</Icon>
</VListTileAction>
<VListTileTitle v-text="$tr('changeLanguage')" />
</VListTile>
</VList>
</Menu>
</template>
</VToolbarItems>

<template v-if="$slots.tabs" #extension>
Expand Down
2 changes: 2 additions & 0 deletions contentcuration/contentcuration/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,5 @@ def gettext(s):


DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

LANGUAGE_COOKIE_AGE = 3600 * 24 * 14
150 changes: 150 additions & 0 deletions contentcuration/contentcuration/tests/test_setlanguage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import json

from django.conf import settings
from django.http import HttpResponseNotAllowed
from django.test import override_settings
from django.test import TestCase
from django.urls import reverse
from django.urls import translate_url
from django.utils.translation import get_language
from django.utils.translation import LANGUAGE_SESSION_KEY


@override_settings(LANGUAGE_CODE="en")
class I18NTests(TestCase):
"""
Tests set_language view in kolibri/core/views.py
Copied from https://github.com/django/django/blob/stable/1.11.x/tests/view_tests/tests/test_i18n.py
"""

def set_post_data(self, lang_code, next_url=""):
post_data = {
"language": lang_code,
"next": next_url,
}
return json.dumps(post_data)

def _get_inactive_language_code(self):
"""Return language code for a language which is not activated."""
current_language = get_language()
return [
code for code, name in settings.LANGUAGES if not code == current_language
][0]

def test_setlang(self):
"""
The set_language view can be used to change the session language.
"""
lang_code = self._get_inactive_language_code()
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)

def test_setlang_next_valid(self):
"""
The set_language view can be used to change the session language.
The user is redirected to the "next" argument.
"""
lang_code = self._get_inactive_language_code()
next_url = reverse("channels")
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code, next_url), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("channels"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)

def test_setlang_next_invalid(self):
"""
The set_language view can be used to change the session language.
The user is redirected to base redirect if the "next" argument is invalid.
"""
lang_code = self._get_inactive_language_code()
next_url = "/not/a/real/url"
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code, next_url), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)

def test_setlang_null(self):
"""
Test language code set to null which shoul direct to default language "en"
"""
lang_code = self._get_inactive_language_code()
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
lang_code = None
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), "en"),
)
self.assertFalse(LANGUAGE_SESSION_KEY in self.client.session)

def test_setlang_null_next_valid(self):
"""
The set_language view can be used to change the session language.
The user is redirected to the "next" argument.
"""
lang_code = self._get_inactive_language_code()
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
next_url = reverse("channels")
lang_code = None
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code, next_url), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("channels"), "en"),
)
self.assertFalse(LANGUAGE_SESSION_KEY in self.client.session)

def test_setlang_null_next_invalid(self):
"""
The set_language view can be used to change the session language.
The user is redirected to user redirect if the "next" argument is invalid.
"""
lang_code = self._get_inactive_language_code()
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), lang_code),
)
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
next_url = "/not/a/real/url"
lang_code = None
response = self.client.post(reverse("set_language"), self.set_post_data(lang_code, next_url), content_type='application/json')
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
translate_url(reverse("base"), "en"),
)
self.assertFalse(LANGUAGE_SESSION_KEY in self.client.session)

def test_setlang_get(self):
"""
The set_language view is forbidden to be accessed via GET
"""
lang_code = self._get_inactive_language_code()
response = self.client.get(reverse("set_language"), params=self.set_post_data(lang_code), content_type='application/json')
self.assertEqual(type(response), HttpResponseNotAllowed)
91 changes: 50 additions & 41 deletions contentcuration/contentcuration/views/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import json
from builtins import str
from urllib.parse import urlsplit
from urllib.parse import urlunsplit

from django.conf import settings
from django.contrib.auth.decorators import login_required
Expand All @@ -22,6 +20,8 @@
from django.urls import reverse
from django.urls import reverse_lazy
from django.urls import translate_url
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import check_for_language
from django.utils.translation import get_language
from django.utils.translation import LANGUAGE_SESSION_KEY
from django.views.decorators.http import require_POST
Expand Down Expand Up @@ -51,7 +51,6 @@
from contentcuration.models import License
from contentcuration.models import TaskResult
from contentcuration.serializers import SimplifiedChannelProbeCheckSerializer
from contentcuration.utils.i18n import SUPPORTED_LANGUAGES
from contentcuration.utils.messages import get_messages
from contentcuration.viewsets.channelset import PublicChannelSetSerializer

Expand Down Expand Up @@ -342,9 +341,8 @@ def activate_channel_endpoint(request):
return HttpResponse(json.dumps({"success": True}))


# Taken from kolibri.core.views which was
# modified from django.views.i18n
@require_POST
# flake8: noqa: C901
def set_language(request):
"""
Since this view changes how the user will see the rest of the site, it must
Expand All @@ -353,42 +351,53 @@ def set_language(request):
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add/replace the doc string here noting where this code came from and what you changed?

payload = json.loads(request.body)
lang_code = payload.get(LANGUAGE_QUERY_PARAMETER)
next_url = urlsplit(payload.get("next")) if payload.get("next") else None
if lang_code and lang_code in SUPPORTED_LANGUAGES:
if next_url and is_valid_path(next_url.path):
# If it is a recognized path, then translate it to the new language and return it.
next_path = urlunsplit(
(
next_url[0],
next_url[1],
translate_url(next_url[2], lang_code),
next_url[3],
next_url[4],
)
)
else:
# Just redirect to the base URL w/ the lang_code
next_path = translate_url(reverse('base'), lang_code)
response = HttpResponse(next_path)
if hasattr(request, "session"):
request.session[LANGUAGE_SESSION_KEY] = lang_code
else:
lang_code = get_language()
if next_url and is_valid_path(next_url.path):
# If it is a recognized path, then translate it using the default language code for this device
next_path = urlunsplit(
(
next_url[0],
next_url[1],
translate_url(next_url[2], lang_code),
next_url[3],
next_url[4],
)
next_url = payload.get("next")

if (
(next_url or request.accepts('text/html')) and
not url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts={request.get_host()},
require_https=request.is_secure(),
)
):
next_url = request.META.get('HTTP_REFERER')
if not url_has_allowed_host_and_scheme(
url=next_url,
allowed_hosts={request.get_host()},
require_https=request.is_secure(),
):
next_url = translate_url(reverse('base'), lang_code)
if next_url and not is_valid_path(next_url):
next_url = translate_url(reverse('base'), lang_code)
response = HttpResponse(next_url) if next_url else HttpResponse(status=204)
if request.method == 'POST':
if lang_code and check_for_language(lang_code):
if next_url:
next_trans = translate_url(next_url, lang_code)
if next_trans != next_url:
response = HttpResponse(next_trans)
if hasattr(request, 'session'):
# Storing the language in the session is deprecated.
# (RemovedInDjango40Warning)
request.session[LANGUAGE_SESSION_KEY] = lang_code
response.set_cookie(
settings.LANGUAGE_COOKIE_NAME, lang_code,
max_age=settings.LANGUAGE_COOKIE_AGE,
path=settings.LANGUAGE_COOKIE_PATH,
domain=settings.LANGUAGE_COOKIE_DOMAIN,
secure=settings.LANGUAGE_COOKIE_SECURE,
httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
samesite=settings.LANGUAGE_COOKIE_SAMESITE,
)
else:
# Just redirect to the base URL w/ the lang_code, likely the default language
next_path = translate_url(reverse('base'), lang_code)
response = HttpResponse(next_path)
if hasattr(request, "session"):
request.session.pop(LANGUAGE_SESSION_KEY, "")
lang_code = get_language()
if lang_code and check_for_language(lang_code):
if next_url:
next_trans = translate_url(next_url, lang_code)
if next_trans != next_url:
response = HttpResponse(next_trans)
if hasattr(request, "session"):
request.session.pop(LANGUAGE_SESSION_KEY, "")

return response