From 4d0f7c7ea4477d346123e40fe5c2784c5f094042 Mon Sep 17 00:00:00 2001 From: Kate Buckner Date: Wed, 19 Aug 2015 13:35:46 -0700 Subject: [PATCH] Add a user-visible setting for 24-hour time display. (imported from commit d934824fd6b72e64a455aac9ff4585b262145f02) --- static/js/feature_flags.js | 5 - static/js/message_list_view.js | 2 +- static/js/server_events.js | 3 + static/js/settings.js | 36 +++ static/styles/zulip.css | 18 +- static/templates/settings_tab.handlebars | 22 ++ zerver/lib/actions.py | 15 + ...field_userprofile_twenty_four_hour_time.py | 275 ++++++++++++++++++ zerver/models.py | 3 + zerver/test_events.py | 17 ++ zerver/views/__init__.py | 15 +- zproject/urls.py | 1 + 12 files changed, 402 insertions(+), 10 deletions(-) create mode 100644 zerver/migrations/0081_auto__add_field_userprofile_twenty_four_hour_time.py diff --git a/static/js/feature_flags.js b/static/js/feature_flags.js index 0e02312394..9da780eb5f 100644 --- a/static/js/feature_flags.js +++ b/static/js/feature_flags.js @@ -3,12 +3,8 @@ var feature_flags = (function () { var exports = {}; // Helpers -var special_24_hour_people= _.contains([], - page_params.email); var og_zuliper_emails = []; -var iceland = page_params.domain === 'customer8.invalid'; - var customer4_realms = [ 'customer4.invalid', 'users.customer4.invalid' @@ -25,7 +21,6 @@ exports.collect_send_times = false; // Permanent realm-specific stuff: exports.disable_message_editing = _.contains(['mit.edu'], page_params.domain); exports.is_og_zulip_user = _.contains(og_zuliper_emails, page_params.email); -exports.twenty_four_hour_time = special_24_hour_people || iceland; exports.left_side_userlist = _.contains(['customer7.invalid'], page_params.domain); exports.enable_new_user_app_alerts = ! _.contains(['employees.customer16.invalid'], page_params.domain); diff --git a/static/js/message_list_view.js b/static/js/message_list_view.js index 631c5b292e..48d91d497b 100644 --- a/static/js/message_list_view.js +++ b/static/js/message_list_view.js @@ -17,7 +17,7 @@ function MessageListView(list, table_name, collapse_messages) { (function () { function stringify_time(time) { - if (feature_flags.twenty_four_hour_time) { + if (page_params.twenty_four_hour_time) { return time.toString('HH:mm'); } return time.toString('h:mm TT'); diff --git a/static/js/server_events.js b/static/js/server_events.js index 4504bb9ec2..9fd1ea056e 100644 --- a/static/js/server_events.js +++ b/static/js/server_events.js @@ -180,6 +180,9 @@ function get_events_success(events) { notifications.handle_global_notification_updates(event.notification_name, event.setting); break; + case 'update_display_settings': + page_params.twenty_four_hour_time = event.twenty_four_hour_time; + break; } }; diff --git a/static/js/settings.js b/static/js/settings.js index 615303c155..f256ba2e38 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -151,6 +151,7 @@ exports.setup_page = function () { $("#settings").html(settings_tab); $("#settings-status").hide(); $("#notify-settings-status").hide(); + $("#display-settings-status").hide(); $("#ui-settings-status").hide(); alert_words_ui.set_up_alert_words(); @@ -383,6 +384,41 @@ exports.setup_page = function () { update_audible_notification_setting); }); + $("#twenty_four_hour_time").change(function () { + var time_checkbox = $("#twenty_four_hour_time").is(":checked"); + var data = {}; + data.twenty_four_hour_time = JSON.stringify(time_checkbox); + + channel.patch({ + url: '/json/time_setting', + data: data, + success: function (resp, statusText, xhr, form) { + var message = "Updated display settings!"; + var result = $.parseJSON(xhr.responseText); + var display_settings_status = $('#display-settings-status').expectOne(); + + display_settings_status.removeClass(status_classes) + .addClass('alert-success') + .text(message).stop(true).fadeTo(0,1); + }, + error: function (xhr, error_type, xhn) { + var response = "Error updating display settings"; + var display_settings_status = $('#display-settings-status').expectOne(); + + if (xhr.status.toString().charAt(0) === "4") { + // Only display the error response for 4XX, where we've crafted + // a nice response. + response += ": " + $.parseJSON(xhr.responseText).msg; + } + display_settings_status.removeClass(status_classes) + .addClass('alert-error') + .text(response).stop(true).fadeTo(0,1); + + } + }); + }); + + $("#get_api_key_box").hide(); $("#show_api_key_box").hide(); $("#get_api_key_box form").ajaxForm({ diff --git a/static/styles/zulip.css b/static/styles/zulip.css index 528386b496..f1dda28bf2 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -3299,7 +3299,7 @@ div.edit_bot { position: relative; } -#settings #notify-settings-status,#ui-settings-status { +#settings #notify-settings-status,#ui-settings-status,#display-settings-status { width: 80%; text-align: center; margin: auto; @@ -3324,7 +3324,7 @@ div.edit_bot { left: 20px; } -#settings .settings-section .notification-settings-form,.ui-settings-form { +#settings .settings-section .notification-settings-form,.ui-settings-form,.display-settings-form { width: 400px; margin: auto; } @@ -3343,6 +3343,7 @@ div.edit_bot { } #settings .settings-section .notification-settings-form .controls, +#settings .settings-section .display-settings-form .controls, #settings .settings-section .ui-settings-form .controls { display: inline-block; vertical-align: middle; @@ -3354,7 +3355,12 @@ div.edit_bot { margin-right: 12px; } +#settings .settings-section .display-settings-form .controls { + margin-left: 0; +} + #settings .settings-section .notification-settings-form .controls input[type='checkbox'], +#settings .settings-section .display-settings-form .controls input[type='checkbox'], #settings .settings-section .ui-settings-form .controls input[type='checkbox'] { margin: 0px; padding: 0px; @@ -3364,7 +3370,8 @@ div.edit_bot { } #settings .settings-section .notification-settings-form .control-label, -#settings .settings-section .ui-settings-form .control-label { +#settings .settings-section .ui-settings-form .control-label, +#settings .settings-section .display-settings-form .control-label { float: none; display: inline-block; vertical-align: middle; @@ -3373,6 +3380,10 @@ div.edit_bot { width: 240px; } +#settings .settings-section .display-settings-form .control-label { + width: 360px; +} + #settings .settings-section .notification-settings-form .notification-submission, #settings .settings-section .ui-settings-form .ui-submission { margin-left: 140px; @@ -3477,6 +3488,7 @@ div.edit_bot { #settings .settings-section .account-settings-form, #settings .settings-section .new-bot-form, #settings .settings-section .notification-settings-form, +#settings .settings-section .display-settings-form, #settings .settings-section .edit-bot-form-box { width: 100%; } diff --git a/static/templates/settings_tab.handlebars b/static/templates/settings_tab.handlebars index 8018665cdc..90b2835f9e 100644 --- a/static/templates/settings_tab.handlebars +++ b/static/templates/settings_tab.handlebars @@ -86,6 +86,28 @@ +
+
+
+ Display Settings
+
+
+
+
+ +
+ +
+
+
+
+
Notifications
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 7ab87e549a..9615ef0888 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -1717,6 +1717,16 @@ def do_change_default_desktop_notifications(user_profile, default_desktop_notifi user_profile.default_desktop_notifications = default_desktop_notifications user_profile.save(update_fields=["default_desktop_notifications"]) +def do_change_twenty_four_hour_time(user_profile, twenty_four_hour_time, log=True): + user_profile.twenty_four_hour_time = twenty_four_hour_time + user_profile.save(update_fields=["twenty_four_hour_time"]) + event = {'type': 'update_display_settings', + 'user': user_profile.email, + 'setting': twenty_four_hour_time} + if log: + log_event(event) + send_event(event, [user_profile.id]) + def set_default_streams(realm, stream_names): DefaultStream.objects.filter(realm=realm).delete() for stream_name in stream_names: @@ -2359,6 +2369,9 @@ def fetch_initial_state_data(user_profile, event_types, queue_id): if want('stream'): state['streams'] = do_get_streams(user_profile) + if want('update_display_settings'): + state['twenty_four_hour_time'] = user_profile.twenty_four_hour_time + return state def apply_events(state, events, user_profile): @@ -2489,6 +2502,8 @@ def apply_events(state, events, user_profile): state['muted_topics'] = event["muted_topics"] elif event['type'] == "realm_filters": state['realm_filters'] = event["realm_filters"] + elif event['type'] == "update_display_settings": + state['twenty_four_hour_time'] == event["twenty_four_hour_time"] else: raise ValueError("Unexpected event type %s" % (event['type'],)) diff --git a/zerver/migrations/0081_auto__add_field_userprofile_twenty_four_hour_time.py b/zerver/migrations/0081_auto__add_field_userprofile_twenty_four_hour_time.py new file mode 100644 index 0000000000..ef7edc3618 --- /dev/null +++ b/zerver/migrations/0081_auto__add_field_userprofile_twenty_four_hour_time.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models +from django.conf import settings + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'UserProfile.twenty_four_hour_time' + db.add_column(u'zerver_userprofile', 'twenty_four_hour_time', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=True) + if settings.DEPLOYED and not settings.ENTERPRISE: + db.execute("UPDATE zerver_userprofile SET twenty_four_hour_time = TRUE WHERE realm_id IN " + + "(SELECT id FROM zerver_realm WHERE domain='customer8.invalid')") + db.execute("UPDATE zerver_userprofile SET twenty_four_hour_time = TRUE WHERE email IN ()") + + def backwards(self, orm): + # Deleting field 'UserProfile.twenty_four_hour_time' + db.delete_column(u'zerver_userprofile', 'twenty_four_hour_time') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'zerver.appledevicetoken': { + 'Meta': {'object_name': 'AppleDeviceToken'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.client': { + 'Meta': {'object_name': 'Client'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30', 'db_index': 'True'}) + }, + u'zerver.defaultstream': { + 'Meta': {'unique_together': "(('realm', 'stream'),)", 'object_name': 'DefaultStream'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Stream']"}) + }, + u'zerver.huddle': { + 'Meta': {'object_name': 'Huddle'}, + 'huddle_hash': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + u'zerver.message': { + 'Meta': {'object_name': 'Message'}, + 'content': ('django.db.models.fields.TextField', [], {}), + 'edit_history': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'has_attachment': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'has_image': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'has_link': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'pub_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Recipient']"}), + 'rendered_content': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'rendered_content_version': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}), + 'sending_client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '60', 'db_index': 'True'}) + }, + u'zerver.mituser': { + 'Meta': {'object_name': 'MitUser'}, + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + u'zerver.preregistrationuser': { + 'Meta': {'object_name': 'PreregistrationUser'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invited_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']", 'null': 'True'}), + 'referred_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']", 'null': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'streams': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['zerver.Stream']", 'null': 'True', 'symmetrical': 'False'}) + }, + u'zerver.pushdevicetoken': { + 'Meta': {'object_name': 'PushDeviceToken'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ios_app_id': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'kind': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '4096'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.realm': { + 'Meta': {'object_name': 'Realm'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'deactivated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_by_admins_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'invite_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'mandatory_topics': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True'}), + 'name_changes_disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'notifications_stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': u"orm['zerver.Stream']"}), + 'restricted_to_domain': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'show_digest_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + u'zerver.realmalias': { + 'Meta': {'object_name': 'RealmAlias'}, + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80', 'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']", 'null': 'True'}) + }, + u'zerver.realmemoji': { + 'Meta': {'unique_together': "(('realm', 'name'),)", 'object_name': 'RealmEmoji'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'img_url': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.TextField', [], {}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}) + }, + u'zerver.realmfilter': { + 'Meta': {'unique_together': "(('realm', 'pattern'),)", 'object_name': 'RealmFilter'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pattern': ('django.db.models.fields.TextField', [], {}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}), + 'url_format_string': ('django.db.models.fields.TextField', [], {}) + }, + u'zerver.recipient': { + 'Meta': {'unique_together': "(('type', 'type_id'),)", 'object_name': 'Recipient'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'db_index': 'True'}), + 'type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + u'zerver.referral': { + 'Meta': {'object_name': 'Referral'}, + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.scheduledjob': { + 'Meta': {'object_name': 'ScheduledJob'}, + 'data': ('django.db.models.fields.TextField', [], {}), + 'filter_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), + 'filter_string': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'scheduled_timestamp': ('django.db.models.fields.DateTimeField', [], {}), + 'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {}) + }, + u'zerver.stream': { + 'Meta': {'unique_together': "(('name', 'realm'),)", 'object_name': 'Stream'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'deactivated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'description': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}), + 'email_token': ('django.db.models.fields.CharField', [], {'default': "'db559172cf87493869af805bd51da2d4'", 'max_length': '32'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invite_only': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '60', 'db_index': 'True'}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}) + }, + u'zerver.streamcolor': { + 'Meta': {'object_name': 'StreamColor'}, + 'color': ('django.db.models.fields.CharField', [], {'max_length': '10'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Subscription']"}) + }, + u'zerver.subscription': { + 'Meta': {'unique_together': "(('user_profile', 'recipient'),)", 'object_name': 'Subscription'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'audible_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'color': ('django.db.models.fields.CharField', [], {'default': "'#c2c2c2'", 'max_length': '10'}), + 'desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'in_home_view': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}), + 'notifications': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'recipient': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Recipient']"}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.useractivity': { + 'Meta': {'unique_together': "(('user_profile', 'client', 'query'),)", 'object_name': 'UserActivity'}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}), + 'count': ('django.db.models.fields.IntegerField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_visit': ('django.db.models.fields.DateTimeField', [], {}), + 'query': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.useractivityinterval': { + 'Meta': {'object_name': 'UserActivityInterval'}, + 'end': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'start': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.usermessage': { + 'Meta': {'unique_together': "(('user_profile', 'message'),)", 'object_name': 'UserMessage'}, + 'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Message']"}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.userpresence': { + 'Meta': {'unique_together': "(('user_profile', 'client'),)", 'object_name': 'UserPresence'}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Client']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {}), + 'user_profile': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']"}) + }, + u'zerver.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'alert_words': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'autoscroll_forever': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'avatar_source': ('django.db.models.fields.CharField', [], {'default': "'G'", 'max_length': '1'}), + 'bot_owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.UserProfile']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_all_public_streams': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'default_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'default_events_register_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': u"orm['zerver.Stream']"}), + 'default_sending_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'to': u"orm['zerver.Stream']"}), + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'db_index': 'True'}), + 'enable_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_digest_emails': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_offline_email_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_offline_push_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_sounds': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_stream_desktop_notifications': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enable_stream_sounds': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'enter_sends': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}), + 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invites_granted': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'invites_used': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_bot': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_mirror_dummy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_pointer_updater': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'last_reminder': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}), + 'muted_topics': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'onboarding_steps': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'pointer': ('django.db.models.fields.IntegerField', [], {}), + 'rate_limits': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}), + 'realm': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['zerver.Realm']"}), + 'short_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'tutorial_status': ('django.db.models.fields.CharField', [], {'default': "'W'", 'max_length': '1'}), + 'twenty_four_hour_time': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}) + } + } + + complete_apps = ['zerver'] diff --git a/zerver/models.py b/zerver/models.py index 605862b47c..af8d698090 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -328,6 +328,9 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): enter_sends = models.NullBooleanField(default=True) autoscroll_forever = models.BooleanField(default=False) + # display settings + twenty_four_hour_time = models.BooleanField(default=False) + # Hours to wait before sending another email to a user EMAIL_REMINDER_WAITPERIOD = 24 # Minutes to wait before warning a bot owner that her bot sent a message diff --git a/zerver/test_events.py b/zerver/test_events.py index 5a4982d5d7..cc35047a3b 100644 --- a/zerver/test_events.py +++ b/zerver/test_events.py @@ -34,6 +34,7 @@ from zerver.lib.actions import ( do_set_realm_invite_by_admins_only, do_update_message, do_update_pointer, + do_change_twenty_four_hour_time, fetch_initial_state_data, ) @@ -434,6 +435,21 @@ class EventsRegisterTest(AuthedTestCase): error = schema_checker('events[0]', events[0]) self.assert_on_error(error) + def test_change_twenty_four_hour_time(self): + schema_checker = check_dict([ + ('type', equals('update_display_settings')), + ('op', equals('update')), + ('person', check_dict([ + ('email', check_string), + ('twenty_four_hour_time', check_bool), + ])), + ]) + # The first False is probably a noop, then we get transitions in both directions. + for twenty_four_hour_time in [False, True, False]: + events = self.do_test(lambda: do_change_twenty_four_hour_time(self.user_profile, twenty_four_hour_time)) + error = schema_checker('events[0]', events[0]) + self.assert_on_error(error) + def test_realm_emoji_events(self): schema_checker = check_dict([ ('type', equals('realm_emoji')), @@ -463,6 +479,7 @@ class EventsRegisterTest(AuthedTestCase): error = schema_checker('events[0]', events[0]) self.assert_on_error(error) + def test_create_bot(self): bot_created_checker = check_dict([ ('type', equals('realm_bot')), diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py index c3ed332737..0c9e41d62a 100644 --- a/zerver/views/__init__.py +++ b/zerver/views/__init__.py @@ -46,7 +46,7 @@ from zerver.lib.actions import bulk_remove_subscriptions, do_change_password, \ do_change_enable_stream_desktop_notifications, do_change_enable_stream_sounds, \ do_change_stream_description, do_get_streams, do_make_stream_private, \ do_regenerate_api_key, do_remove_default_stream, do_update_pointer, \ - do_change_avatar_source + do_change_avatar_source, do_change_twenty_four_hour_time from zerver.lib.create_user import random_api_key from zerver.lib.push_notifications import num_push_devices_for_user @@ -1071,6 +1071,7 @@ def home(request): user_profile.enable_offline_email_notifications, enable_offline_push_notifications = user_profile.enable_offline_push_notifications, + twenty_four_hour_time = register_ret['twenty_four_hour_time'], enable_digest_emails = user_profile.enable_digest_emails, event_queue_id = register_ret['queue_id'], @@ -1710,6 +1711,18 @@ def json_change_settings(request, user_profile, return json_success(result) +@authenticated_json_post_view +@has_request_variables +def json_time_setting(request, user_profile, twenty_four_hour_time=REQ(validator=check_bool,default=None)): + result = {} + if twenty_four_hour_time is not None and \ + user_profile.twenty_four_hour_time != twenty_four_hour_time: + do_change_twenty_four_hour_time(user_profile, twenty_four_hour_time) + + result['twenty_four_hour_time'] = twenty_four_hour_time + + return json_success(result) + @authenticated_json_post_view @has_request_variables def json_change_notify_settings(request, user_profile, diff --git a/zproject/urls.py b/zproject/urls.py index 46adfd6aca..52bb270206 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -145,6 +145,7 @@ urlpatterns += patterns('zerver.views', url(r'^json/set_alert_words$', 'json_set_alert_words'), url(r'^json/set_muted_topics$', 'json_set_muted_topics'), url(r'^json/set_avatar$', 'json_set_avatar'), + url(r'^json/time_setting$', 'json_time_setting'), # This json format view is used by the LEGACY pre-REST API. It # requires an API key.