From 242d3ffaf4e682d15e02325f61b5f4f4feb5b0fc Mon Sep 17 00:00:00 2001 From: Umair Khan Date: Tue, 14 Mar 2017 14:53:09 +0500 Subject: [PATCH] Add timezone field in UserProfile. Implements backend of #1506. --- zerver/lib/actions.py | 12 ++++++++++ zerver/lib/timezone.py | 9 ++++++++ .../migrations/0061_userprofile_timezone.py | 20 ++++++++++++++++ zerver/models.py | 3 +++ zerver/tests/test_home.py | 1 + zerver/tests/test_settings.py | 23 +++++++++++++++++++ zerver/views/home.py | 1 + zerver/views/user_settings.py | 17 +++++++++++--- 8 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 zerver/lib/timezone.py create mode 100644 zerver/migrations/0061_userprofile_timezone.py diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 90077550cc..dc3d4585f3 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -2359,6 +2359,18 @@ def do_change_default_language(user_profile, setting_value, log=True): log_event(event) send_event(event, [user_profile.id]) +def do_change_timezone(user_profile, setting_value, log=True): + # type: (UserProfile, Text, bool) -> None + user_profile.timezone = setting_value + user_profile.save(update_fields=['timezone']) + event = {'type': 'update_display_settings', + 'user': user_profile.email, + 'setting_name': 'timezone', + 'setting': setting_value} + if log: + log_event(event) + send_event(event, [user_profile.id]) + def set_default_streams(realm, stream_dict): # type: (Realm, Dict[Text, Dict[Text, Any]]) -> None DefaultStream.objects.filter(realm=realm).delete() diff --git a/zerver/lib/timezone.py b/zerver/lib/timezone.py new file mode 100644 index 0000000000..84a919ddb3 --- /dev/null +++ b/zerver/lib/timezone.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import + +from typing import Text, List + +import pytz + +def get_all_timezones(): + # type: () -> List[Text] + return sorted(pytz.all_timezones) diff --git a/zerver/migrations/0061_userprofile_timezone.py b/zerver/migrations/0061_userprofile_timezone.py new file mode 100644 index 0000000000..b33ddeaab3 --- /dev/null +++ b/zerver/migrations/0061_userprofile_timezone.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-03-15 11:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0060_move_avatars_to_be_uid_based'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='timezone', + field=models.CharField(default='UTC', max_length=40), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 6e64f72a35..02e1d5b8a0 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -609,6 +609,9 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin): DEFAULT_UPLOADS_QUOTA = 1024*1024*1024 quota = models.IntegerField(default=DEFAULT_UPLOADS_QUOTA) # type: int + # The maximum length of a timezone in pytz.all_timezones is 32. + # Setting max_length=40 is a safe choice. + timezone = models.CharField(max_length=40, default='UTC') # type: Text def can_admin_user(self, target_user): # type: (UserProfile) -> bool diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 643e939c38..67c1e094a6 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -124,6 +124,7 @@ class HomeTest(ZulipTestCase): "stream_sounds_enabled", "subbed_info", "test_suite", + "timezone", "twenty_four_hour_time", "unread_count", "unsubbed_info", diff --git a/zerver/tests/test_settings.py b/zerver/tests/test_settings.py index 5da09aa733..93af888183 100644 --- a/zerver/tests/test_settings.py +++ b/zerver/tests/test_settings.py @@ -216,6 +216,29 @@ class ChangeSettingsTest(ZulipTestCase): user_profile = get_user_profile_by_email(email) self.assertNotEqual(user_profile.default_language, invalid_lang) + def test_change_timezone(self): + # type: () -> None + """ + Test changing the timezone of the user. + """ + email = "hamlet@zulip.com" + self.login(email) + usa_pacific = 'US/Pacific' + data = dict(timezone=ujson.dumps(usa_pacific)) + result = self.client_patch("/json/settings/display", data) + self.assert_json_success(result) + user_profile = get_user_profile_by_email(email) + self.assertEqual(user_profile.timezone, usa_pacific) + + # Test to make sure invalid timezones are not accepted + # and saved in the db. + invalid_timezone = "invalid_timezone" + data = dict(timezone=ujson.dumps(invalid_timezone)) + result = self.client_patch("/json/settings/display", data) + self.assert_json_error(result, "Invalid timezone '%s'" % (invalid_timezone,)) + user_profile = get_user_profile_by_email(email) + self.assertNotEqual(user_profile.timezone, invalid_timezone) + class UserChangesTest(ZulipTestCase): def test_update_api_key(self): # type: () -> None diff --git a/zerver/views/home.py b/zerver/views/home.py index 5924cf4cc8..cadacbdbca 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -233,6 +233,7 @@ def home_real(request): avatar_url = avatar_url(user_profile), avatar_url_medium = avatar_url(user_profile, medium=True), avatar_source = user_profile.avatar_source, + timezone = user_profile.timezone, # Stream message notification settings: stream_desktop_notifications_enabled = user_profile.enable_stream_desktop_notifications, diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index f3936f74ab..2dc4636144 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -23,7 +23,7 @@ from zerver.lib.actions import do_change_password, \ do_regenerate_api_key, do_change_avatar_fields, do_change_twenty_four_hour_time, \ do_change_left_side_userlist, do_change_emoji_alt_code, do_change_default_language, \ do_change_pm_content_in_desktop_notifications, validate_email, \ - do_change_user_email, do_start_email_change_process + do_change_user_email, do_start_email_change_process, do_change_timezone from zerver.lib.avatar import avatar_url from zerver.lib.i18n import get_available_language_codes from zerver.lib.response import json_success, json_error @@ -31,6 +31,7 @@ from zerver.lib.upload import upload_avatar_image from zerver.lib.validator import check_bool, check_string from zerver.lib.request import JsonableError from zerver.lib.users import check_change_full_name +from zerver.lib.timezone import get_all_timezones from zerver.models import UserProfile, Realm, name_changes_disabled, \ EmailChangeStatus from confirmation.models import EmailChangeConfirmation @@ -158,12 +159,17 @@ def update_display_settings_backend(request, user_profile, twenty_four_hour_time=REQ(validator=check_bool, default=None), default_language=REQ(validator=check_string, default=None), left_side_userlist=REQ(validator=check_bool, default=None), - emoji_alt_code=REQ(validator=check_bool, default=None)): - # type: (HttpRequest, UserProfile, Optional[bool], Optional[str], Optional[bool], Optional[bool]) -> HttpResponse + emoji_alt_code=REQ(validator=check_bool, default=None), + timezone=REQ(validator=check_string, default=None)): + # type: (HttpRequest, UserProfile, Optional[bool], Optional[str], Optional[bool], Optional[bool], Optional[Text]) -> HttpResponse if (default_language is not None and default_language not in get_available_language_codes()): raise JsonableError(_("Invalid language '%s'" % (default_language,))) + if (timezone is not None and + timezone not in get_all_timezones()): + raise JsonableError(_("Invalid timezone '%s'" % (timezone,))) + result = {} # type: Dict[str, Any] if (default_language is not None and user_profile.default_language != default_language): @@ -185,6 +191,11 @@ def update_display_settings_backend(request, user_profile, do_change_emoji_alt_code(user_profile, emoji_alt_code) result['emoji_alt_code'] = emoji_alt_code + elif (timezone is not None and + user_profile.timezone != timezone): + do_change_timezone(user_profile, timezone) + result['timezone'] = timezone + return json_success(result) @has_request_variables