Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n support #79

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ Configuration

This would create ``SMSVerification`` table, which is used to store ``phone_number``, ``session_token`` and ``security_code``.

i18n Support
------------

If you would like to internationalize the verification messages, localize ``MESSAGE`` for all the desired locales
that you want to support as per Django internationalization guidelines. Then, from the client, in the call to
the ``phone-register`` API endpoint, specify the desired verification message language by setting the ``language``
POST data to one of the locale codes that you support. ``PhoneVerificationService`` will then try to localize
the message for the specified language code and if found send it to the SMS backend. If a translation for the
specified language is not found, value of ``MESSAGE`` option would be sent.

Usage
-----

Expand Down
3 changes: 2 additions & 1 deletion phone_verify/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def register(self, request):
serializer = PhoneSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
session_token = send_security_code_and_generate_session_token(
str(serializer.validated_data["phone_number"])
str(serializer.validated_data["phone_number"]),
serializer.validated_data['language']
Comment on lines +24 to +25
Copy link
Owner

Choose a reason for hiding this comment

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

I'd suggest to take the language from the value of Accept-Language HTTP header.

)
return response.Ok({"session_token": session_token})

Expand Down
1 change: 1 addition & 0 deletions phone_verify/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

class PhoneSerializer(serializers.Serializer):
phone_number = PhoneNumberField()
language = serializers.CharField(default='')


class SMSVerificationSerializer(serializers.Serializer):
Expand Down
17 changes: 13 additions & 4 deletions phone_verify/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Third Party Stuff
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import override, gettext

# phone_verify stuff
from .backends import get_sms_backend
Expand All @@ -27,10 +28,11 @@ class PhoneVerificationService(object):

verification_message = phone_settings.get("MESSAGE", DEFAULT_MESSAGE)

def __init__(self, phone_number, backend=None):
def __init__(self, phone_number, backend=None, language='en'):
self._check_required_settings()
if backend is None:
self.backend = get_sms_backend(phone_number=phone_number)
self.language = language

def send_verification(self, number, security_code):
"""
Expand All @@ -39,10 +41,17 @@ def send_verification(self, number, security_code):
:param number: the phone number of recipient.
"""
message = self._generate_message(security_code)

self.backend.send_sms(number, message)

def _generate_message(self, security_code):
if self.language:
with override(self.language):
verification_message = gettext(self.verification_message)
return verification_message.format(
app=settings.PHONE_VERIFICATION.get("APP_NAME", DEFAULT_APP_NAME),
security_code=security_code,
)

return self.verification_message.format(
app=settings.PHONE_VERIFICATION.get("APP_NAME", DEFAULT_APP_NAME),
security_code=security_code,
Expand All @@ -67,12 +76,12 @@ def _check_required_settings(self):
)


def send_security_code_and_generate_session_token(phone_number):
def send_security_code_and_generate_session_token(phone_number, language=''):
sms_backend = get_sms_backend(phone_number)
security_code, session_token = sms_backend.create_security_code_and_session_token(
phone_number
)
service = PhoneVerificationService(phone_number=phone_number)
service = PhoneVerificationService(phone_number=phone_number, language=language)
try:
service.send_verification(phone_number, security_code)
except service.backend.exception_class as exc:
Expand Down
28 changes: 28 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,34 @@ def test_phone_registration_sends_message(client, mocker, backend):
)


def test_phone_registration_sends_i18n_message(client, mocker, backend):
with override_settings(PHONE_VERIFICATION=backend):
zh_verification_message = "歡迎使用 {app}! 請使用安全碼 {security_code} 繼續。"
mocker.patch('phone_verify.services.gettext', return_value=zh_verification_message)

backend_service = backend.get("BACKEND")
mock_backend_send_sms = mocker.patch(f"{backend_service}.send_sms")

url = reverse("phone-register")
phone_number = PHONE_NUMBER
data = {"phone_number": phone_number, "language": "zh-hant"}

response = client.post(url, data)

assert response.status_code == 200
assert "session_token" in response.data
SMSVerification = apps.get_model("phone_verify", "SMSVerification")
sms_verification = SMSVerification.objects.get(
session_token=response.data["session_token"], phone_number=phone_number
)
assert sms_verification
actual_message = zh_verification_message.format(
app=backend['APP_NAME'],
security_code=sms_verification.security_code
)
mock_backend_send_sms.assert_called_with("+13478379634", actual_message)


def test_security_code_session_token_verification_api(client, backend):
with override_settings(PHONE_VERIFICATION=backend):
f.create_verification(
Expand Down
13 changes: 13 additions & 0 deletions tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ def test_message_generation_and_sending_service(client, mocker, backend):

assert mock_api.called

def test_i18n_message_generation_and_sending_service(client, mocker, backend):
with override_settings(PHONE_VERIFICATION=backend):
zh_verification_message = "歡迎使用 {app}! 請使用安全碼 {security_code} 繼續。"
mocker.patch('phone_verify.services.gettext', return_value=zh_verification_message)
service = PhoneVerificationService(phone_number="+13478379634", language='zh-hant')
backend_service = backend.get("BACKEND")
mock_api = mocker.patch(f"{backend_service}.send_sms")
service.send_verification("+13478379634", "123456")
actual_message = zh_verification_message.format(
app=backend['APP_NAME'], security_code="123456"
)
mock_api.assert_called_with("+13478379634", actual_message)


def test_exception_is_logged_when_raised(client, mocker, backend):
with override_settings(PHONE_VERIFICATION=backend):
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ deps =
django32: Django>=3.2,<3.2.5
django40: Django>=4.0
commands =
pytest --cov -v --tb=native
pytest --cov -v --tb=native {posargs}