mirror of https://github.com/zulip/zulip.git
realm-icon: Add realm icon feature.
- Add realm icon fields to realm model. - Add migration for new realm model's field. - Add views for icon uploading and deleting. - Add routes for realm icons views. - Add JS widget for realm icon upload setting. - Add realm icon upload to administration organization setting. - Add tests for realm icons. Fixes #3660.
This commit is contained in:
parent
20b655016d
commit
257bb40698
|
@ -58,6 +58,7 @@
|
|||
"viewport": false,
|
||||
"upload_widget": false,
|
||||
"avatar": false,
|
||||
"realm_icon": false,
|
||||
"feature_flags": false,
|
||||
"search_suggestion": false,
|
||||
"referral": false,
|
||||
|
|
|
@ -323,6 +323,8 @@ function _setup_page() {
|
|||
realm_default_language: page_params.realm_default_language,
|
||||
realm_waiting_period_threshold: page_params.realm_waiting_period_threshold,
|
||||
is_admin: page_params.is_admin,
|
||||
realm_icon_source: page_params.realm_icon_source,
|
||||
realm_icon: page_params.realm_icon,
|
||||
};
|
||||
|
||||
var admin_tab = templates.render('admin_tab', options);
|
||||
|
@ -1050,6 +1052,31 @@ function _setup_page() {
|
|||
});
|
||||
});
|
||||
|
||||
function upload_realm_icon(file_input) {
|
||||
var form_data = new FormData();
|
||||
|
||||
form_data.append('csrfmiddlewaretoken', csrf_token);
|
||||
jQuery.each(file_input[0].files, function (i, file) {
|
||||
form_data.append('file-'+i, file);
|
||||
});
|
||||
|
||||
var spinner = $("#upload_icon_spinner").expectOne();
|
||||
loading.make_indicator(spinner, {text: i18n.t("Uploading icon.")});
|
||||
|
||||
channel.put({
|
||||
url: '/json/realm/icon',
|
||||
data: form_data,
|
||||
cache: false,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function () {
|
||||
loading.destroy_indicator($("#upload_icon_spinner"));
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
realm_icon.build_realm_icon_widget(upload_realm_icon);
|
||||
|
||||
}
|
||||
|
||||
exports.launch_page = function (tab) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
var realm_icon = (function () {
|
||||
|
||||
var exports = {};
|
||||
|
||||
exports.build_realm_icon_widget = function (upload_function) {
|
||||
var get_file_input = function () {
|
||||
return $('#realm_icon_file_input').expectOne();
|
||||
};
|
||||
|
||||
if (page_params.realm_icon_source === 'G') {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
}
|
||||
$("#realm_icon_delete_button").on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
channel.del({
|
||||
url: '/json/realm/icon',
|
||||
});
|
||||
});
|
||||
|
||||
return upload_widget.build_direct_upload_widget(
|
||||
get_file_input,
|
||||
$("#realm_icon_file_input_error").expectOne(),
|
||||
$("#realm_icon_upload_button").expectOne(),
|
||||
upload_function
|
||||
);
|
||||
};
|
||||
return exports;
|
||||
|
||||
}());
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = realm_icon;
|
||||
}
|
|
@ -245,6 +245,19 @@ function dispatch_normal_event(event) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case 'realm_change_icon':
|
||||
$("#realm-settings-icon").attr("src", event.url);
|
||||
if (event.source === 'U') {
|
||||
$("#realm_icon_delete_button").show();
|
||||
} else {
|
||||
$("#realm_icon_delete_button").hide();
|
||||
// Need to clear input because of a small edge case
|
||||
// where you try to upload the same image you just deleted.
|
||||
var file_input = $("#realm_icon_file_input");
|
||||
file_input.val('');
|
||||
}
|
||||
page_params.realm_icon = event.url;
|
||||
page_params.icon_source = event.source;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@ label {
|
|||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.new-style .m-t-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.new-style .grid label {
|
||||
width: 200px;
|
||||
}
|
||||
|
@ -58,14 +62,14 @@ label {
|
|||
width: 214px;
|
||||
}
|
||||
|
||||
.user-avatar-section {
|
||||
.user-avatar-section, .realm-icon-section {
|
||||
float: right;
|
||||
background-color: #fff;
|
||||
margin: 20px 0px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.user-avatar-section .inline-block {
|
||||
.user-avatar-section .inline-block, .realm-icon-section .inline-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -213,7 +217,7 @@ input[type=checkbox] + .inline-block {
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#user-settings-avatar {
|
||||
#user-settings-avatar, #realm-icon-section {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
@ -530,7 +534,7 @@ input[type=checkbox].inline-block {
|
|||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#upload_avatar_spinner {
|
||||
#upload_avatar_spinner, #upload_icon_spinner {
|
||||
font-size: 14px;
|
||||
margin: auto;
|
||||
}
|
||||
|
@ -590,6 +594,13 @@ input[type=checkbox].inline-block {
|
|||
height: 200px;
|
||||
}
|
||||
|
||||
#realm-settings-icon {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-left: 25%;
|
||||
}
|
||||
/* -- new settings overlay -- */
|
||||
#settings_overlay_container {
|
||||
pointer-events: none;
|
||||
|
@ -832,20 +843,27 @@ input[type=text]#settings_search {
|
|||
/* -- end new settings overlay -- */
|
||||
|
||||
@media (max-width: 1215px) {
|
||||
.user-avatar-section {
|
||||
.user-avatar-section, .realm-icon-section {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user-avatar-section .inline-block {
|
||||
.user-avatar-section .inline-block, .realm-icon-section .inline-block {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.user-avatar-section .inline-block {
|
||||
margin: 0px 10px;
|
||||
}
|
||||
|
||||
.realm-icon-section .inline-block {
|
||||
margin: 0px 25px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 856px) {
|
||||
.user-avatar-section .inline-block {
|
||||
.user-avatar-section .inline-block, .realm-icon-section .inline-block {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<div class="alert" id="admin-realm-default-language-status"></div>
|
||||
<div class="alert" id="admin-realm-waiting_period_threshold_status"></div>
|
||||
|
||||
<div class="m-10 inline-block grid organization-settings-parent">
|
||||
<div class="input-group admin-realm">
|
||||
<label for="realm_name">{{t "Your organization's name" }}</label>
|
||||
<input type="text" id="id_realm_name" name="realm_name" class="admin-realm-name"
|
||||
|
@ -108,5 +109,21 @@
|
|||
<div class="input-group organization-submission">
|
||||
<input type="submit" class="button" value="{{t 'Save changes' }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="realm-icon-section box-shadow border-radius">
|
||||
<div class="inline-block">
|
||||
<img id="realm-settings-icon" src="{{ realm_icon }}"/>
|
||||
<div id="realm_icon_file_input_error" class="text-error"></div>
|
||||
<input type="file" name="realm_icon_file_input" class="notvisible"
|
||||
id="realm_icon_file_input" value="{{t 'Upload icon' }}"/>
|
||||
<div id="upload_icon_spinner"></div>
|
||||
</div>
|
||||
<div class="inline-block">
|
||||
<button class="button sea-green w-200 m-t-10 block input-size"
|
||||
id="realm_icon_upload_button">{{t 'Upload new ccon' }}</button>
|
||||
<button class="button btn-danger w-200 m-t-10 block input-size"
|
||||
id="realm_icon_delete_button">{{t 'Delete icon' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -30,6 +30,7 @@ target_fully_covered = {path for target in [
|
|||
'zerver/lib/mention.py',
|
||||
'zerver/lib/message.py',
|
||||
'zerver/lib/name_restrictions.py',
|
||||
'zerver/lib/realm_icon.py',
|
||||
'zerver/lib/retention.py',
|
||||
'zerver/lib/streams.py',
|
||||
'zerver/lib/users.py',
|
||||
|
|
|
@ -25,6 +25,7 @@ from zerver.lib.message import (
|
|||
message_to_dict,
|
||||
render_markdown,
|
||||
)
|
||||
from zerver.lib.realm_icon import realm_icon_url
|
||||
from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, RealmAlias, \
|
||||
Subscription, Recipient, Message, Attachment, UserMessage, \
|
||||
Client, DefaultStream, UserPresence, Referral, PushDeviceToken, MAX_SUBJECT_LENGTH, \
|
||||
|
@ -1952,6 +1953,23 @@ def do_change_avatar_fields(user_profile, avatar_source, log=True):
|
|||
person=payload),
|
||||
active_user_ids(user_profile.realm))
|
||||
|
||||
|
||||
def do_change_icon_source(realm, icon_source, log=True):
|
||||
# type: (Realm, Text, bool) -> None
|
||||
realm.icon_source = icon_source
|
||||
realm.icon_version += 1
|
||||
realm.save(update_fields=["icon_source", "icon_version"])
|
||||
|
||||
if log:
|
||||
log_event({'type': 'realm_change_icon',
|
||||
'realm': realm.domain,
|
||||
'icon_source': icon_source})
|
||||
|
||||
send_event(dict(type='realm_change_icon',
|
||||
source=realm.icon_source,
|
||||
url=realm_icon_url(realm)),
|
||||
active_user_ids(realm))
|
||||
|
||||
def _default_stream_permision_check(user_profile, stream):
|
||||
# type: (UserProfile, Optional[Stream]) -> None
|
||||
# Any user can have a None default stream
|
||||
|
|
|
@ -21,6 +21,7 @@ from zerver.lib.alert_words import user_alert_words
|
|||
from zerver.lib.attachments import user_attachments
|
||||
from zerver.lib.avatar import get_avatar_url
|
||||
from zerver.lib.narrow import check_supported_events_narrow_filter
|
||||
from zerver.lib.realm_icon import realm_icon_url
|
||||
from zerver.lib.request import JsonableError
|
||||
from zerver.lib.actions import validate_user_access_to_subscribers_helper, \
|
||||
do_get_streams, get_default_streams_for_realm, \
|
||||
|
@ -156,6 +157,10 @@ def fetch_initial_state_data(user_profile, event_types, queue_id,
|
|||
state['enable_online_push_notifications'] = user_profile.enable_online_push_notifications
|
||||
state['enable_digest_emails'] = user_profile.enable_digest_emails
|
||||
|
||||
if want('realm_change_icon'):
|
||||
state['realm_icon'] = realm_icon_url(user_profile.realm)
|
||||
state['realm_icon_source'] = user_profile.realm.icon_source
|
||||
|
||||
return state
|
||||
|
||||
def apply_events(state, events, user_profile, include_subscribers=True):
|
||||
|
@ -377,6 +382,9 @@ def apply_event(state, event, user_profile, include_subscribers):
|
|||
state['enable_online_push_notifications'] = event['setting']
|
||||
elif event['notification_name'] == "enable_digest_emails":
|
||||
state['enable_digest_emails'] = event['setting']
|
||||
elif event['type'] == "realm_change_icon":
|
||||
state['realm_icon'] = event['url']
|
||||
state['realm_icon_source'] = event['source']
|
||||
else:
|
||||
raise ValueError("Unexpected event type %s" % (event['type'],))
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
from __future__ import absolute_import
|
||||
from django.conf import settings
|
||||
|
||||
from typing import Text
|
||||
|
||||
from zerver.lib.avatar_hash import gravatar_hash, user_avatar_hash
|
||||
from zerver.lib.upload import upload_backend
|
||||
from zerver.models import Realm
|
||||
|
||||
def realm_icon_url(realm):
|
||||
# type: (Realm) -> Text
|
||||
return get_realm_icon_url(realm)
|
||||
|
||||
def get_realm_icon_url(realm):
|
||||
# type: (Realm) -> Text
|
||||
if realm.icon_source == u'U':
|
||||
return upload_backend.get_realm_icon_url(realm.id, realm.icon_version)
|
||||
elif settings.ENABLE_GRAVATAR:
|
||||
hash_key = gravatar_hash(realm.domain)
|
||||
return u"https://secure.gravatar.com/avatar/%s?d=identicon" % (hash_key,)
|
||||
else:
|
||||
return settings.DEFAULT_AVATAR_URI+'?version=0'
|
|
@ -118,6 +118,15 @@ class ZulipUploadBackend(object):
|
|||
# type: (Text) -> None
|
||||
raise NotImplementedError()
|
||||
|
||||
def upload_realm_icon_image(self, icon_file, user_profile):
|
||||
# type: (File, UserProfile) -> None
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_realm_icon_url(self, realm_id, version):
|
||||
# type: (int, int) -> Text
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
### S3
|
||||
|
||||
def get_bucket(conn, bucket_name):
|
||||
|
@ -269,6 +278,38 @@ class S3UploadBackend(ZulipUploadBackend):
|
|||
# ?x=x allows templates to append additional parameters with &s
|
||||
return u"https://%s.s3.amazonaws.com/%s%s?x=x" % (bucket, medium_suffix, hash_key)
|
||||
|
||||
def upload_realm_icon_image(self, icon_file, user_profile):
|
||||
# type: (File, UserProfile) -> None
|
||||
content_type = guess_type(icon_file.name)[0]
|
||||
bucket_name = settings.S3_AVATAR_BUCKET
|
||||
s3_file_name = os.path.join(str(user_profile.realm.id), 'realm', 'icon')
|
||||
|
||||
image_data = icon_file.read()
|
||||
upload_image_to_s3(
|
||||
bucket_name,
|
||||
s3_file_name + ".original",
|
||||
content_type,
|
||||
user_profile,
|
||||
image_data,
|
||||
)
|
||||
|
||||
resized_data = resize_avatar(image_data)
|
||||
upload_image_to_s3(
|
||||
bucket_name,
|
||||
s3_file_name,
|
||||
'image/png',
|
||||
user_profile,
|
||||
resized_data,
|
||||
)
|
||||
# See avatar_url in avatar.py for URL. (That code also handles the case
|
||||
# that users use gravatar.)
|
||||
|
||||
def get_realm_icon_url(self, realm_id, version):
|
||||
# type: (int, int) -> Text
|
||||
bucket = settings.S3_AVATAR_BUCKET
|
||||
# ?x=x allows templates to append additional parameters with &s
|
||||
return u"https://%s.s3.amazonaws.com/%s/realm/icon.png?version=%s" % (bucket, realm_id, version)
|
||||
|
||||
def ensure_medium_avatar_image(self, email):
|
||||
# type: (Text) -> None
|
||||
user_profile = get_user_profile_by_email(email)
|
||||
|
@ -359,6 +400,24 @@ class LocalUploadBackend(ZulipUploadBackend):
|
|||
medium_suffix = "-medium" if medium else ""
|
||||
return u"/user_avatars/%s%s.png?x=x" % (hash_key, medium_suffix)
|
||||
|
||||
def upload_realm_icon_image(self, icon_file, user_profile):
|
||||
# type: (File, UserProfile) -> None
|
||||
upload_path = os.path.join('avatars', str(user_profile.realm.id), 'realm')
|
||||
|
||||
image_data = icon_file.read()
|
||||
write_local_file(
|
||||
upload_path,
|
||||
'icon.original',
|
||||
image_data)
|
||||
|
||||
resized_data = resize_avatar(image_data)
|
||||
write_local_file(upload_path, 'icon.png', resized_data)
|
||||
|
||||
def get_realm_icon_url(self, realm_id, version):
|
||||
# type: (int, int) -> Text
|
||||
# ?x=x allows templates to append additional parameters with &s
|
||||
return u"/user_avatars/%s/realm/icon.png?version=%s" % (realm_id, version)
|
||||
|
||||
def ensure_medium_avatar_image(self, email):
|
||||
# type: (Text) -> None
|
||||
email_hash = user_avatar_hash(email)
|
||||
|
@ -386,6 +445,10 @@ def upload_avatar_image(user_file, user_profile, email):
|
|||
# type: (File, UserProfile, Text) -> None
|
||||
upload_backend.upload_avatar_image(user_file, user_profile, email)
|
||||
|
||||
def upload_icon_image(user_file, user_profile):
|
||||
# type: (File, UserProfile) -> None
|
||||
upload_backend.upload_realm_icon_image(user_file, user_profile)
|
||||
|
||||
def upload_message_image(uploaded_file_name, content_type, file_data, user_profile, target_realm=None):
|
||||
# type: (Text, Optional[Text], binary_type, UserProfile, Optional[Realm]) -> Text
|
||||
return upload_backend.upload_message_image(uploaded_file_name, content_type, file_data,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-15 06:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0053_emailchangestatus'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='realm',
|
||||
name='icon_source',
|
||||
field=models.CharField(
|
||||
choices=[('G', 'Hosted by Gravatar'), ('U', 'Uploaded by administrator')],
|
||||
default='G', max_length=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='realm',
|
||||
name='icon_version',
|
||||
field=models.PositiveSmallIntegerField(default=1),
|
||||
),
|
||||
]
|
|
@ -140,6 +140,16 @@ class Realm(ModelReprMixin, models.Model):
|
|||
default=2**31 - 1) # type: BitHandler
|
||||
waiting_period_threshold = models.PositiveIntegerField(default=0) # type: int
|
||||
|
||||
ICON_FROM_GRAVATAR = u'G'
|
||||
ICON_UPLOADED = u'U'
|
||||
ICON_SOURCES = (
|
||||
(ICON_FROM_GRAVATAR, 'Hosted by Gravatar'),
|
||||
(ICON_UPLOADED, 'Uploaded by administrator'),
|
||||
)
|
||||
icon_source = models.CharField(default=ICON_FROM_GRAVATAR, choices=ICON_SOURCES,
|
||||
max_length=1) # type: Text
|
||||
icon_version = models.PositiveSmallIntegerField(default=1) # type: int
|
||||
|
||||
DEFAULT_NOTIFICATION_STREAM_NAME = u'announce'
|
||||
|
||||
def authentication_methods_dict(self):
|
||||
|
|
|
@ -62,7 +62,7 @@ from zerver.lib.actions import (
|
|||
do_add_realm_alias,
|
||||
do_change_realm_alias,
|
||||
do_remove_realm_alias,
|
||||
)
|
||||
do_change_icon_source)
|
||||
from zerver.lib.events import (
|
||||
apply_events,
|
||||
fetch_initial_state_data,
|
||||
|
@ -959,6 +959,19 @@ class EventsRegisterTest(ZulipTestCase):
|
|||
error = self.realm_bot_schema('avatar_url', check_string)('events[0]', events[0])
|
||||
self.assert_on_error(error)
|
||||
|
||||
def test_change_realm_icon_source(self):
|
||||
# type: () -> None
|
||||
realm = get_realm('zulip')
|
||||
action = lambda: do_change_icon_source(realm, realm.ICON_FROM_GRAVATAR)
|
||||
events = self.do_test(action, state_change_expected=False)
|
||||
schema_checker = check_dict([
|
||||
('type', equals('realm_change_icon')),
|
||||
('source', check_string),
|
||||
('url', check_string)
|
||||
])
|
||||
error = schema_checker('events[0]', events[0])
|
||||
self.assert_on_error(error)
|
||||
|
||||
def test_change_bot_default_all_public_streams(self):
|
||||
# type: () -> None
|
||||
bot = self.create_bot('test-bot@zulip.com')
|
||||
|
|
|
@ -6,6 +6,7 @@ from unittest import skip
|
|||
|
||||
from zerver.lib.avatar import avatar_url
|
||||
from zerver.lib.bugdown import url_filename
|
||||
from zerver.lib.realm_icon import realm_icon_url
|
||||
from zerver.lib.test_classes import ZulipTestCase, UploadSerializeMixin
|
||||
from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file
|
||||
from zerver.lib.test_runner import slow
|
||||
|
@ -13,7 +14,7 @@ from zerver.lib.upload import sanitize_name, S3UploadBackend, \
|
|||
upload_message_image, delete_message_image, LocalUploadBackend
|
||||
import zerver.lib.upload
|
||||
from zerver.models import Attachment, Recipient, get_user_profile_by_email, \
|
||||
get_old_unclaimed_attachments, Message, UserProfile
|
||||
get_old_unclaimed_attachments, Message, UserProfile, Realm, get_realm
|
||||
from zerver.lib.actions import do_delete_old_unclaimed_attachments
|
||||
|
||||
import ujson
|
||||
|
@ -473,6 +474,145 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
|
|||
# type: () -> None
|
||||
destroy_uploads()
|
||||
|
||||
class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
|
||||
|
||||
def test_multiple_upload_failure(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Attempting to upload two files should fail.
|
||||
"""
|
||||
# Log in as admin
|
||||
self.login("iago@zulip.com")
|
||||
with get_test_image_file('img.png') as fp1, \
|
||||
get_test_image_file('img.png') as fp2:
|
||||
result = self.client_put_multipart("/json/realm/icon", {'f1': fp1, 'f2': fp2})
|
||||
self.assert_json_error(result, "You must upload exactly one icon.")
|
||||
|
||||
def test_no_file_upload_failure(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Calling this endpoint with no files should fail.
|
||||
"""
|
||||
self.login("iago@zulip.com")
|
||||
|
||||
result = self.client_put_multipart("/json/realm/icon")
|
||||
self.assert_json_error(result, "You must upload exactly one icon.")
|
||||
|
||||
correct_files = [
|
||||
('img.png', 'png_resized.png'),
|
||||
('img.jpg', None), # jpeg resizing is platform-dependent
|
||||
('img.gif', 'gif_resized.png'),
|
||||
('img.tif', 'tif_resized.png')
|
||||
]
|
||||
corrupt_files = ['text.txt', 'corrupt.png', 'corrupt.gif']
|
||||
|
||||
def test_no_admin_user_upload(self):
|
||||
# type: () -> None
|
||||
self.login("hamlet@zulip.com")
|
||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||
result = self.client_put_multipart("/json/realm/icon", {'file': fp})
|
||||
self.assert_json_error(result, 'Must be a realm administrator')
|
||||
|
||||
def test_get_gravatar_icon(self):
|
||||
# type: () -> None
|
||||
self.login("hamlet@zulip.com")
|
||||
realm = get_realm('zulip')
|
||||
realm.icon_source = Realm.ICON_FROM_GRAVATAR
|
||||
realm.save()
|
||||
with self.settings(ENABLE_GRAVATAR=True):
|
||||
response = self.client_get("/json/realm/icon?foo=bar")
|
||||
redirect_url = response['Location']
|
||||
self.assertEqual(redirect_url, realm_icon_url(realm) + '&foo=bar')
|
||||
|
||||
with self.settings(ENABLE_GRAVATAR=False):
|
||||
response = self.client_get("/json/realm/icon?foo=bar")
|
||||
redirect_url = response['Location']
|
||||
self.assertTrue(redirect_url.endswith(realm_icon_url(realm) + '&foo=bar'))
|
||||
|
||||
def test_get_realm_icon(self):
|
||||
# type: () -> None
|
||||
self.login("hamlet@zulip.com")
|
||||
|
||||
realm = get_realm('zulip')
|
||||
realm.icon_source = Realm.ICON_UPLOADED
|
||||
realm.save()
|
||||
response = self.client_get("/json/realm/icon?foo=bar")
|
||||
redirect_url = response['Location']
|
||||
self.assertTrue(redirect_url.endswith(realm_icon_url(realm) + '&foo=bar'))
|
||||
|
||||
def test_valid_icons(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
A PUT request to /json/realm/icon with a valid file should return a url
|
||||
and actually create an realm icon.
|
||||
"""
|
||||
for fname, rfname in self.correct_files:
|
||||
# TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
|
||||
# with self.subTest(fname=fname):
|
||||
self.login("iago@zulip.com")
|
||||
with get_test_image_file(fname) as fp:
|
||||
result = self.client_put_multipart("/json/realm/icon", {'file': fp})
|
||||
realm = get_realm('zulip')
|
||||
self.assert_json_success(result)
|
||||
json = ujson.loads(result.content)
|
||||
self.assertIn("icon_url", json)
|
||||
url = json["icon_url"]
|
||||
base = '/user_avatars/%s/realm/icon.png' % (realm.id,)
|
||||
self.assertEqual(base, url[:len(base)])
|
||||
|
||||
if rfname is not None:
|
||||
response = self.client_get(url)
|
||||
data = b"".join(response.streaming_content)
|
||||
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
|
||||
|
||||
def test_invalid_icons(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
A PUT request to /json/realm/icon with an invalid file should fail.
|
||||
"""
|
||||
for fname in self.corrupt_files:
|
||||
# with self.subTest(fname=fname):
|
||||
self.login("iago@zulip.com")
|
||||
with get_test_image_file(fname) as fp:
|
||||
result = self.client_put_multipart("/json/realm/icon", {'file': fp})
|
||||
|
||||
self.assert_json_error(result, "Could not decode image; did you upload an image file?")
|
||||
|
||||
def test_delete_icon(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
A DELETE request to /json/realm/icon should delete the realm icon and return gravatar URL
|
||||
"""
|
||||
self.login("iago@zulip.com")
|
||||
realm = get_realm('zulip')
|
||||
realm.icon_source = Realm.ICON_UPLOADED
|
||||
realm.save()
|
||||
|
||||
result = self.client_delete("/json/realm/icon")
|
||||
|
||||
self.assert_json_success(result)
|
||||
json = ujson.loads(result.content)
|
||||
self.assertIn("icon_url", json)
|
||||
realm = get_realm('zulip')
|
||||
self.assertEqual(json["icon_url"], realm_icon_url(realm))
|
||||
self.assertEqual(realm.icon_source, Realm.ICON_FROM_GRAVATAR)
|
||||
|
||||
def test_realm_icon_version(self):
|
||||
# type: () -> None
|
||||
|
||||
self.login("iago@zulip.com")
|
||||
realm = get_realm('zulip')
|
||||
icon_version = realm.icon_version
|
||||
self.assertEqual(icon_version, 1)
|
||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||
self.client_put_multipart("/json/realm/icon", {'file': fp})
|
||||
realm = get_realm('zulip')
|
||||
self.assertEqual(realm.icon_version, icon_version + 1)
|
||||
|
||||
def tearDown(self):
|
||||
# type: () -> None
|
||||
destroy_uploads()
|
||||
|
||||
class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
|
||||
|
||||
def test_file_upload_local(self):
|
||||
|
|
|
@ -1947,6 +1947,8 @@ class HomeTest(ZulipTestCase):
|
|||
"realm_default_streams",
|
||||
"realm_emoji",
|
||||
"realm_filters",
|
||||
"realm_icon",
|
||||
"realm_icon_source",
|
||||
"realm_invite_by_admins_only",
|
||||
"realm_invite_required",
|
||||
"realm_message_content_edit_limit_seconds",
|
||||
|
@ -1991,6 +1993,7 @@ class HomeTest(ZulipTestCase):
|
|||
page_params = self._get_page_params(result)
|
||||
|
||||
actual_keys = sorted([str(k) for k in page_params.keys()])
|
||||
|
||||
self.assertEqual(actual_keys, expected_keys)
|
||||
|
||||
# TODO: Inspect the page_params data further.
|
||||
|
|
|
@ -12,6 +12,7 @@ from six.moves import zip_longest, zip, range
|
|||
from version import ZULIP_VERSION
|
||||
from zerver.decorator import zulip_login_required, process_client
|
||||
from zerver.forms import ToSForm
|
||||
from zerver.lib.realm_icon import realm_icon_url
|
||||
from zerver.models import Message, UserProfile, Stream, Subscription, Huddle, \
|
||||
Recipient, Realm, UserMessage, DefaultStream, RealmEmoji, RealmAlias, \
|
||||
RealmFilter, PreregistrationUser, UserActivity, \
|
||||
|
@ -225,6 +226,8 @@ def home_real(request):
|
|||
realm_restricted_to_domain = register_ret['realm_restricted_to_domain'],
|
||||
realm_default_language = register_ret['realm_default_language'],
|
||||
realm_waiting_period_threshold = register_ret['realm_waiting_period_threshold'],
|
||||
realm_icon = realm_icon_url(user_profile.realm),
|
||||
realm_icon_source = user_profile.realm.icon_source,
|
||||
enter_sends = user_profile.enter_sends,
|
||||
user_id = user_profile.id,
|
||||
left_side_userlist = register_ret['left_side_userlist'],
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.http import HttpResponse, HttpRequest
|
||||
|
||||
from zerver.decorator import require_realm_admin
|
||||
from zerver.lib.actions import do_change_icon_source
|
||||
from zerver.lib.realm_icon import realm_icon_url
|
||||
from zerver.lib.response import json_error, json_success
|
||||
from zerver.lib.upload import upload_icon_image
|
||||
from zerver.models import UserProfile
|
||||
|
||||
|
||||
@require_realm_admin
|
||||
def upload_icon(request, user_profile):
|
||||
# type: (HttpRequest, UserProfile) -> HttpResponse
|
||||
|
||||
if len(request.FILES) != 1:
|
||||
return json_error(_("You must upload exactly one icon."))
|
||||
|
||||
icon_file = list(request.FILES.values())[0]
|
||||
upload_icon_image(icon_file, user_profile)
|
||||
do_change_icon_source(user_profile.realm, user_profile.realm.ICON_UPLOADED)
|
||||
icon_url = realm_icon_url(user_profile.realm)
|
||||
|
||||
json_result = dict(
|
||||
icon_url=icon_url
|
||||
)
|
||||
return json_success(json_result)
|
||||
|
||||
|
||||
@require_realm_admin
|
||||
def delete_icon_backend(request, user_profile):
|
||||
# type: (HttpRequest, UserProfile) -> HttpResponse
|
||||
# We don't actually delete the icon because it might still
|
||||
# be needed if the URL was cached and it is rewrited
|
||||
# in any case after next update.
|
||||
do_change_icon_source(user_profile.realm, user_profile.realm.ICON_FROM_GRAVATAR)
|
||||
gravatar_url = realm_icon_url(user_profile.realm)
|
||||
json_result = dict(
|
||||
icon_url=gravatar_url
|
||||
)
|
||||
return json_success(json_result)
|
||||
|
||||
|
||||
def get_icon_backend(request, user_profile):
|
||||
# type: (HttpRequest, UserProfile) -> HttpResponse
|
||||
url = realm_icon_url(user_profile.realm)
|
||||
|
||||
# We can rely on the url already having query parameters. Because
|
||||
# our templates depend on being able to use the ampersand to
|
||||
# add query parameters to our url, get_icon_url does '?version=version_number'
|
||||
# hacks to prevent us from having to jump through decode/encode hoops.
|
||||
assert '?' in url
|
||||
url += '&' + request.META['QUERY_STRING']
|
||||
return redirect(url)
|
|
@ -859,6 +859,7 @@ JS_SPECS = {
|
|||
'js/templates.js',
|
||||
'js/upload_widget.js',
|
||||
'js/avatar.js',
|
||||
'js/realm_icon.js',
|
||||
'js/settings.js',
|
||||
'js/admin.js',
|
||||
'js/tab_bar.js',
|
||||
|
|
|
@ -185,6 +185,12 @@ v1_api_and_json_patterns = [
|
|||
{'PUT': 'zerver.views.realm_emoji.upload_emoji',
|
||||
'DELETE': 'zerver.views.realm_emoji.delete_emoji'}),
|
||||
|
||||
# realm/icon -> zerver.views.realm_icon
|
||||
url(r'^realm/icon$', rest_dispatch,
|
||||
{'PUT': 'zerver.views.realm_icon.upload_icon',
|
||||
'DELETE': 'zerver.views.realm_icon.delete_icon_backend',
|
||||
'GET': 'zerver.views.realm_icon.get_icon_backend'}),
|
||||
|
||||
# realm/filters -> zerver.views.realm_filters
|
||||
url(r'^realm/filters$', rest_dispatch,
|
||||
{'GET': 'zerver.views.realm_filters.list_filters',
|
||||
|
|
Loading…
Reference in New Issue