Skip to content

Commit

Permalink
Wrap user-facing strings in gettext; add icon_emoji
Browse files Browse the repository at this point in the history
  • Loading branch information
posita committed Feb 24, 2018
1 parent 6e51e9e commit fc5ae71
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 44 deletions.
24 changes: 9 additions & 15 deletions docs/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,16 @@ To override this, use the ``FERNET_KEYS`` setting. For example:
from os import environ
SECRET_KEY = environ['SECRET_KEY']
# Use only Base64-encoded 32 byte values for keys; don't derive them
# from arbitrary strings
# The keys as a single environment variable separated by semicolons.
# The first entry is the current key (for encrypting and
# decrypting). Any additional entries are older keys for decrypting
# only.
FERNET_KEYS = environ['FERNET_KEY'].split(';')
# If the following is False, use only Base64-encoded 32 byte values
# for keys. Don't derive them from arbitrary strings.
FERNET_USE_HKDF = False
# For supporting any legacy keys that were used when FERNET_USE_HKDF
# was True
from fernet_fields.hkdf import derive_fernet_key
# The keys
FERNET_KEYS = [
# The first entry is the current key (for encrypting and
# decrypting)
environ['FERNET_KEY'],
# Optional additional entries are older keys for decrypting only
# environ['OLD_FERNET_KEY_1'],
# Equivalent to the default key
# derive_fernet_key(SECRET_KEY),
]
See `the docs <http://django-fernet-fields.readthedocs.io/en/latest/#keys>`__ for details.

Expand Down
2 changes: 1 addition & 1 deletion emojiwatch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
updated = filtered._update(values) # pylint: disable=protected-access

if updated == 0:
raise StaleVersionError('{!r}._version {} is stale'.format(self, self._version - 1))
raise StaleVersionError(gettext('{!r}._version {} is stale').format(self, self._version - 1))

assert updated == 1

Expand Down
51 changes: 28 additions & 23 deletions emojiwatch/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import django.views.decorators.http as d_v_d_http
import django.views.generic as d_v_generic

from gettext import gettext

from . import (
LOGGER,
SLACK_VERIFICATION_TOKEN,
Expand All @@ -53,7 +55,8 @@
_EMOJI_URL_MAX_LEN = 1023
_EMOJIS_MAX_LEN = 32
_FIELD_MAX_LEN = 63
_SHRUG = '\u00af\\_(\u30c4)_/\u00af'
_ICON_EMOJI = ':robot_face:'
_SHRUG = str(u'\u00af\\_(\u30c4)_/\u00af')
_SUB_HANDLERS_BY_SUBTYPE = {} # type: typing.Dict[typing.Text, typing.Callable[[SlackWorkspaceEmojiWatcher, typing.Dict], slacker.Response]]
_UNRECOGNIZED_JSON_BODY_ERR = 'unrecognized JSON structure from request body'

Expand All @@ -71,7 +74,7 @@ class RequestPayloadValidationError(Exception):

def __init__( # noqa:F811 # pylint: disable=keyword-arg-before-vararg
self,
message=_UNRECOGNIZED_JSON_BODY_ERR,
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR),
response=d_http.HttpResponseBadRequest(),
*args,
**kw
Expand Down Expand Up @@ -121,7 +124,7 @@ def event_hook_handler(
slack_retry_reason = request.META.get('HTTP_X_SLACK_RETRY_REASON', None)

if slack_retry_num:
LOGGER.info("Slack retry attempt %s ('%s')", slack_retry_num, slack_retry_reason)
LOGGER.info(gettext("Slack retry attempt %s ('%s')"), slack_retry_num, slack_retry_reason)

content_type = request.META.get('HTTP_CONTENT_TYPE', 'application/json')

Expand All @@ -132,7 +135,7 @@ def event_hook_handler(
try:
payload_data = json.loads(request.body.decode('utf-8')) # type: typing.Dict
except (JSONDecodeError, UnicodeDecodeError):
LOGGER.info('unable to parse JSON from request body')
LOGGER.info(gettext('unable to parse JSON from request body'))
truncate_len = 1024
half_truncate_len = truncate_len >> 1

Expand All @@ -148,41 +151,41 @@ def event_hook_handler(
verification_token = payload_data['token']
except (KeyError, TypeError):
verification_token = None
LOGGER.info(_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'token')") # pylint: disable=logging-not-lazy
LOGGER.info(gettext(_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'token')")) # pylint: disable=logging-not-lazy

if not verification_token \
or verification_token != SLACK_VERIFICATION_TOKEN:
raise RequestPayloadValidationError(
message='bad verification token',
message=gettext('bad verification token'),
response=d_http.HttpResponseForbidden(),
)

try:
call_type = payload_data['type']
except KeyError:
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'type')",
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'type')"),
)

if call_type == 'url_verification':
try:
challenge = payload_data['challenge']
except KeyError:
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'challenge')",
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + " (missing 'challenge')"),
)

if not isinstance(challenge, str) \
or len(challenge) > _CHALLENGE_MAX_LEN:
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized challenge)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized challenge)'),
)

return d_http.HttpResponse(challenge, content_type='text/plain')

if call_type != 'event_callback':
raise RequestPayloadValidationError(
message='unrecognized call type',
message=gettext('unrecognized call type'),
)

try:
Expand All @@ -197,7 +200,7 @@ def event_hook_handler(
or len(event_type) > _FIELD_MAX_LEN \
or event_type != 'emoji_changed':
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event type)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event type)'),
)

try:
Expand All @@ -208,21 +211,21 @@ def event_hook_handler(
subhandler = _SUB_HANDLERS_BY_SUBTYPE[event_subtype]
except (KeyError, ValueError):
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event subtype)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event subtype)'),
)

if not isinstance(team_id, str) \
or len(team_id) > TEAM_ID_MAX_LEN \
or not re.search(TEAM_ID_RE, team_id):
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized team_id)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized team_id)'),
)

team = SlackWorkspaceEmojiWatcher.objects.filter(team_id=team_id).first()

if team is None:
raise RequestPayloadValidationError(
message='no such team ({})'.format(team_id),
message=gettext('no such team ({})').format(team_id),
)

try:
Expand All @@ -231,13 +234,13 @@ def event_hook_handler(
except slacker.Error as exc:
if exc.args == ('invalid_auth',):
raise RequestPayloadValidationError(
message='call to Slack API failed auth',
message=gettext('call to Slack API failed auth'),
response=d_http.HttpResponseForbidden(),
)

# Log, but otherwise ignore errors from our callbacks to Slack's
# API
LOGGER.info('falled call to Slack')
LOGGER.info(gettext('falled call to Slack'))
LOGGER.debug(_SHRUG, exc_info=True)

except RequestPayloadValidationError as exc:
Expand All @@ -262,13 +265,13 @@ def _handle_add(
or not emoji_name \
or len(emoji_name) > _EMOJI_NAME_MAX_LEN:
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event name)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event name)'),
)

if not isinstance(emoji_url, str) \
or len(emoji_url) > _EMOJI_URL_MAX_LEN:
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event value)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event value)'),
)

if emoji_url:
Expand All @@ -277,14 +280,15 @@ def _handle_add(
'fallback': '<{}>'.format(emoji_url),
'image_url': emoji_url,
},
] # type: typing.Optional[typing.List[typing.Dict]]
] # type: typing.Optional[typing.List[typing.Dict[typing.Text, typing.Text]]]
else:
attachments = None

return team.slack.chat.post_message(
team.channel_id,
html.escape('added `:{}:`').format(emoji_name),
html.escape(gettext('added `:{}:`').format(emoji_name)),
attachments=attachments,
icon_emoji=_ICON_EMOJI,
)

# ========================================================================
Expand All @@ -296,7 +300,7 @@ def _handle_remove(

if not isinstance(emoji_names, list):
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event names)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event names)'),
)

if not emoji_names:
Expand All @@ -308,12 +312,13 @@ def _handle_remove(
if any((not isinstance(emoji_name, str) for emoji_name in emoji_names[:_EMOJIS_MAX_LEN])) \
or any((len(emoji_name) > _EMOJI_NAME_MAX_LEN for emoji_name in emoji_names)):
raise RequestPayloadValidationError(
message=_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event names)',
message=gettext(_UNRECOGNIZED_JSON_BODY_ERR + ' (unrecognized event names)'),
)

return team.slack.chat.post_message(
team.channel_id,
html.escape('removed {}{}').format(', '.join('`:{}:`'.format(name) for name in emoji_names[:_EMOJIS_MAX_LEN]), '...' if too_many else ''),
html.escape(gettext('removed {}{}').format(', '.join('`:{}:`'.format(name) for name in emoji_names[:_EMOJIS_MAX_LEN]), '...' if too_many else '')),
icon_emoji=_ICON_EMOJI,
)

# ---- Initialization ----------------------------------------------------
Expand Down
12 changes: 7 additions & 5 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ def _post_good_add_event(
attachments=[{
'fallback': '<{}>'.format(emoji_url),
'image_url': emoji_url,
}]
}],
icon_emoji=':robot_face:',
)

return res
Expand All @@ -502,7 +503,7 @@ def test_event_remove_emoji(
mocked_post_message, # type: mock.MagicMock
):
# type: (...) -> None
res = self._post_good_add_event(self.good_remove_event, mocked_post_message)
res = self._post_good_remove_event(self.good_remove_event, mocked_post_message)
self.assertEqual(res.status_code, 200)

@mock.patch.object(slacker.Chat, 'post_message')
Expand All @@ -512,7 +513,7 @@ def test_event_remove_emoji_invalid_auth(
):
# type: (...) -> None
mocked_post_message.side_effect = slacker.Error('invalid_auth')
res = self._post_good_add_event(self.good_remove_event, mocked_post_message)
res = self._post_good_remove_event(self.good_remove_event, mocked_post_message)
self.assertEqual(res.status_code, 403)

@mock.patch.object(slacker.Chat, 'post_message')
Expand All @@ -522,10 +523,10 @@ def test_event_remove_emoji_slacker_errors_ignored(
):
# type: (...) -> None
mocked_post_message.side_effect = slacker.Error()
res = self._post_good_add_event(self.good_remove_event, mocked_post_message)
res = self._post_good_remove_event(self.good_remove_event, mocked_post_message)
self.assertEqual(res.status_code, 200)

def _post_good_add_event(
def _post_good_remove_event(
self,
good_remove_event, # type: typing.Dict
mocked_post_message, # type: mock.MagicMock
Expand All @@ -536,6 +537,7 @@ def _post_good_add_event(
mocked_post_message.assert_called_with(
self.CLIENT_ID,
'removed `:{}:`'.format(':`, `:'.join(emoji_names)),
icon_emoji=':robot_face:',
)

return res
Expand Down

0 comments on commit fc5ae71

Please sign in to comment.