mirror of https://github.com/zulip/zulip.git
settings: Add support for uploading logo for night mode.
This adds a new field named realm_night_logo which is used for displaying the organization logo when the user is in night mode. Fixes #11176.
This commit is contained in:
parent
e67cf30dfd
commit
7157edf4af
|
@ -121,6 +121,7 @@
|
||||||
"reactions": false,
|
"reactions": false,
|
||||||
"realm_icon": false,
|
"realm_icon": false,
|
||||||
"realm_logo": false,
|
"realm_logo": false,
|
||||||
|
"realm_night_logo": false,
|
||||||
"recent_senders": false,
|
"recent_senders": false,
|
||||||
"reload": false,
|
"reload": false,
|
||||||
"reload_state": false,
|
"reload_state": false,
|
||||||
|
|
|
@ -296,6 +296,16 @@ var event_fixtures = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
realm__update_dict__night_logo: {
|
||||||
|
type: 'realm',
|
||||||
|
op: 'update_dict',
|
||||||
|
property: 'night_logo',
|
||||||
|
data: {
|
||||||
|
night_logo_url: 'night_logo.png',
|
||||||
|
night_logo_source: 'U',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
realm__deactivated: {
|
realm__deactivated: {
|
||||||
type: 'realm',
|
type: 'realm',
|
||||||
op: 'deactivated',
|
op: 'deactivated',
|
||||||
|
@ -940,6 +950,12 @@ with_overrides(function (override) {
|
||||||
assert_same(page_params.realm_logo_url, 'logo.png');
|
assert_same(page_params.realm_logo_url, 'logo.png');
|
||||||
assert_same(page_params.realm_logo_source, 'U');
|
assert_same(page_params.realm_logo_source, 'U');
|
||||||
|
|
||||||
|
event = event_fixtures.realm__update_dict__night_logo;
|
||||||
|
override('realm_logo.rerender', noop);
|
||||||
|
dispatch(event);
|
||||||
|
assert_same(page_params.realm_night_logo_url, 'night_logo.png');
|
||||||
|
assert_same(page_params.realm_night_logo_source, 'U');
|
||||||
|
|
||||||
event = event_fixtures.realm__deactivated;
|
event = event_fixtures.realm__deactivated;
|
||||||
window.location = {};
|
window.location = {};
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
|
@ -1336,6 +1352,7 @@ with_overrides(function (override) {
|
||||||
event = event_fixtures.update_display_settings__night_mode;
|
event = event_fixtures.update_display_settings__night_mode;
|
||||||
page_params.night_mode = false;
|
page_params.night_mode = false;
|
||||||
override('night_mode.enable', stub.f); // automatically checks if called
|
override('night_mode.enable', stub.f); // automatically checks if called
|
||||||
|
override('realm_logo.rerender', noop);
|
||||||
dispatch(event);
|
dispatch(event);
|
||||||
assert_same(page_params.night_mode, true);
|
assert_same(page_params.night_mode, true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -57,6 +57,7 @@ const _ui_report = {
|
||||||
|
|
||||||
const _realm_logo = {
|
const _realm_logo = {
|
||||||
build_realm_logo_widget: noop,
|
build_realm_logo_widget: noop,
|
||||||
|
build_realm_night_logo_widget: noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
set_global('channel', _channel);
|
set_global('channel', _channel);
|
||||||
|
|
|
@ -53,6 +53,8 @@ exports.build_page = function () {
|
||||||
realm_icon_url: page_params.realm_icon_url,
|
realm_icon_url: page_params.realm_icon_url,
|
||||||
realm_logo_source: page_params.realm_logo_source,
|
realm_logo_source: page_params.realm_logo_source,
|
||||||
realm_logo_url: page_params.realm_logo_url,
|
realm_logo_url: page_params.realm_logo_url,
|
||||||
|
realm_night_logo_source: page_params.realm_night_logo_source,
|
||||||
|
realm_night_logo_url: page_params.realm_night_logo_url,
|
||||||
realm_mandatory_topics: page_params.realm_mandatory_topics,
|
realm_mandatory_topics: page_params.realm_mandatory_topics,
|
||||||
realm_send_welcome_emails: page_params.realm_send_welcome_emails,
|
realm_send_welcome_emails: page_params.realm_send_welcome_emails,
|
||||||
realm_message_content_allowed_in_email_notifications:
|
realm_message_content_allowed_in_email_notifications:
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
var realm_logo = (function () {
|
var realm_logo = (function () {
|
||||||
|
|
||||||
var exports = {};
|
var exports = {};
|
||||||
|
|
||||||
exports.build_realm_logo_widget = function (upload_function) {
|
exports.build_realm_logo_widget = function (upload_function) {
|
||||||
var get_file_input = function () {
|
var get_file_input = function () {
|
||||||
return $('#realm_logo_file_input').expectOne();
|
return $('#realm_logo_file_input').expectOne();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (page_params.realm_logo_source === 'D') {
|
if (page_params.realm_logo_source === 'D') {
|
||||||
$("#realm_logo_delete_button").hide();
|
$("#realm_logo_delete_button").hide();
|
||||||
} else {
|
} else {
|
||||||
$("#realm_logo_delete_button").show();
|
$("#realm_logo_delete_button").show();
|
||||||
}
|
}
|
||||||
|
var data = {night: JSON.stringify(false)};
|
||||||
$("#realm_logo_delete_button").on('click', function (e) {
|
$("#realm_logo_delete_button").on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
channel.del({
|
channel.del({
|
||||||
url: '/json/realm/logo',
|
url: '/json/realm/logo',
|
||||||
|
data: data,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,18 +30,61 @@ var realm_logo = (function () {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.build_realm_night_logo_widget = function (upload_function) {
|
||||||
|
var get_file_input = function () {
|
||||||
|
return $('#realm_night_logo_file_input').expectOne();
|
||||||
|
};
|
||||||
|
if (page_params.realm_night_logo_source === 'D') {
|
||||||
|
$("#realm_night_logo_delete_button").hide();
|
||||||
|
} else {
|
||||||
|
$("#realm_night_logo_delete_button").show();
|
||||||
|
}
|
||||||
|
var data = {night: JSON.stringify(true)};
|
||||||
|
$("#realm_night_logo_delete_button").on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
channel.del({
|
||||||
|
url: '/json/realm/logo',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return upload_widget.build_direct_upload_widget(
|
||||||
|
get_file_input,
|
||||||
|
$("#realm_night_logo_file_input_error").expectOne(),
|
||||||
|
$("#realm_night_logo_upload_button").expectOne(),
|
||||||
|
upload_function,
|
||||||
|
page_params.max_logo_file_size
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
exports.rerender = function () {
|
exports.rerender = function () {
|
||||||
|
var file_input = $("#realm_logo_file_input");
|
||||||
|
var night_file_input = $("#realm_night_logo_file_input");
|
||||||
$("#realm-settings-logo").attr("src", page_params.realm_logo_url);
|
$("#realm-settings-logo").attr("src", page_params.realm_logo_url);
|
||||||
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
$("#realm-settings-night-logo").attr("src", page_params.realm_night_logo_url);
|
||||||
|
if (page_params.night_mode) {
|
||||||
|
$("#realm-logo").attr("src", page_params.realm_night_logo_url);
|
||||||
|
} else {
|
||||||
|
$("#realm-logo").attr("src", page_params.realm_logo_url);
|
||||||
|
}
|
||||||
if (page_params.realm_logo_source === 'U') {
|
if (page_params.realm_logo_source === 'U') {
|
||||||
$("#realm_logo_delete_button").show();
|
$("#realm_logo_delete_button").show();
|
||||||
} else {
|
} else {
|
||||||
$("#realm_logo_delete_button").hide();
|
$("#realm_logo_delete_button").hide();
|
||||||
// Need to clear input because of a small edge case
|
// Need to clear input because of a small edge case
|
||||||
// where you try to upload the same image you just deleted.
|
// where you try to upload the same image you just deleted.
|
||||||
var file_input = $("#realm_logo_file_input");
|
|
||||||
file_input.val('');
|
file_input.val('');
|
||||||
}
|
}
|
||||||
|
if (page_params.realm_night_logo_source === 'U') {
|
||||||
|
$("#realm_night_logo_delete_button").show();
|
||||||
|
} else {
|
||||||
|
$("#realm_night_logo_delete_button").hide();
|
||||||
|
// Need to clear input because of a small edge case
|
||||||
|
// where you try to upload the same image you just deleted.
|
||||||
|
night_file_input.val('');
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return exports;
|
return exports;
|
||||||
|
|
|
@ -165,6 +165,10 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
page_params.realm_logo_url = event.data.logo_url;
|
page_params.realm_logo_url = event.data.logo_url;
|
||||||
page_params.realm_logo_source = event.data.logo_source;
|
page_params.realm_logo_source = event.data.logo_source;
|
||||||
realm_logo.rerender();
|
realm_logo.rerender();
|
||||||
|
} else if (event.op === 'update_dict' && event.property === 'night_logo') {
|
||||||
|
page_params.realm_night_logo_url = event.data.night_logo_url;
|
||||||
|
page_params.realm_night_logo_source = event.data.night_logo_source;
|
||||||
|
realm_logo.rerender();
|
||||||
} else if (event.op === 'deactivated') {
|
} else if (event.op === 'deactivated') {
|
||||||
window.location.href = "/accounts/deactivated/";
|
window.location.href = "/accounts/deactivated/";
|
||||||
}
|
}
|
||||||
|
@ -392,8 +396,10 @@ exports.dispatch_normal_event = function dispatch_normal_event(event) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
if (event.setting === true) {
|
if (event.setting === true) {
|
||||||
night_mode.enable();
|
night_mode.enable();
|
||||||
|
realm_logo.rerender();
|
||||||
} else {
|
} else {
|
||||||
night_mode.disable();
|
night_mode.disable();
|
||||||
|
realm_logo.rerender();
|
||||||
}
|
}
|
||||||
$("body").fadeIn(300);
|
$("body").fadeIn(300);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
|
@ -1116,20 +1116,30 @@ exports.build_page = function () {
|
||||||
}
|
}
|
||||||
realm_icon.build_realm_icon_widget(upload_realm_icon);
|
realm_icon.build_realm_icon_widget(upload_realm_icon);
|
||||||
|
|
||||||
function upload_realm_logo(file_input) {
|
function upload_realm_logo(file_input, night) {
|
||||||
var form_data = new FormData();
|
var form_data = new FormData();
|
||||||
|
var spinner;
|
||||||
|
var error_field;
|
||||||
|
var button_text;
|
||||||
|
|
||||||
form_data.append('csrfmiddlewaretoken', csrf_token);
|
form_data.append('csrfmiddlewaretoken', csrf_token);
|
||||||
jQuery.each(file_input[0].files, function (i, file) {
|
jQuery.each(file_input[0].files, function (i, file) {
|
||||||
form_data.append('file-' + i, file);
|
form_data.append('file-' + i, file);
|
||||||
});
|
});
|
||||||
|
if (night) {
|
||||||
var error_field = $("#realm_logo_file_input_error");
|
error_field = $("#realm_night_logo_file_input_error");
|
||||||
|
spinner = $("#upload_night_logo_spinner");
|
||||||
|
button_text = $("#upload_night_logo_button_text");
|
||||||
|
} else {
|
||||||
|
error_field = $("#realm_logo_file_input_error");
|
||||||
|
spinner = $("#upload_logo_spinner");
|
||||||
|
button_text = $("#upload_logo_button_text");
|
||||||
|
}
|
||||||
|
spinner.expectOne();
|
||||||
error_field.hide();
|
error_field.hide();
|
||||||
var spinner = $("#upload_logo_spinner").expectOne();
|
button_text.expectOne().hide();
|
||||||
loading.make_indicator(spinner, {text: i18n.t("Uploading logo.")});
|
loading.make_indicator(spinner, {text: i18n.t("Uploading logo.")});
|
||||||
$("#upload_logo_button_text").expectOne().hide();
|
form_data.append('night', JSON.stringify(night));
|
||||||
|
|
||||||
channel.post({
|
channel.post({
|
||||||
url: '/json/realm/logo',
|
url: '/json/realm/logo',
|
||||||
data: form_data,
|
data: form_data,
|
||||||
|
@ -1137,17 +1147,18 @@ exports.build_page = function () {
|
||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
success: function () {
|
success: function () {
|
||||||
loading.destroy_indicator($("#upload_logo_spinner"));
|
loading.destroy_indicator(spinner);
|
||||||
$("#upload_logo_button_text").expectOne().show();
|
button_text.expectOne().show();
|
||||||
},
|
},
|
||||||
error: function (xhr) {
|
error: function (xhr) {
|
||||||
loading.destroy_indicator($("#upload_logo_spinner"));
|
loading.destroy_indicator(spinner);
|
||||||
$("#upload_logo_button_text").expectOne().show();
|
button_text.expectOne().show();
|
||||||
ui_report.error("", xhr, error_field);
|
ui_report.error("", xhr, error_field);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
realm_logo.build_realm_night_logo_widget(upload_realm_logo);
|
||||||
realm_logo.build_realm_logo_widget(upload_realm_logo);
|
realm_logo.build_realm_logo_widget(upload_realm_logo);
|
||||||
|
|
||||||
$('#deactivate_realm_button').on('click', function (e) {
|
$('#deactivate_realm_button').on('click', function (e) {
|
||||||
|
|
|
@ -116,10 +116,15 @@ var upload_widget = (function () {
|
||||||
) {
|
) {
|
||||||
// default value of max upladed file size
|
// default value of max upladed file size
|
||||||
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
max_file_upload_size = max_file_upload_size || default_max_file_size;
|
||||||
|
|
||||||
function accept() {
|
function accept() {
|
||||||
input_error.hide();
|
input_error.hide();
|
||||||
upload_function(get_file_input());
|
if (upload_button[0].id === "realm_night_logo_upload_button") {
|
||||||
|
upload_function(get_file_input(), true);
|
||||||
|
} else if (upload_button[0].id === "realm_logo_upload_button") {
|
||||||
|
upload_function(get_file_input(), false);
|
||||||
|
} else {
|
||||||
|
upload_function(get_file_input());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
|
|
|
@ -970,7 +970,8 @@ input[type=checkbox].inline-block {
|
||||||
|
|
||||||
#upload_avatar_spinner,
|
#upload_avatar_spinner,
|
||||||
#upload_logo_spinner,
|
#upload_logo_spinner,
|
||||||
#upload_icon_spinner {
|
#upload_icon_spinner,
|
||||||
|
#upload_night_logo_spinner {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
@ -1113,7 +1114,8 @@ input[type=checkbox].inline-block {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#realm-settings-logo {
|
#realm-settings-logo,
|
||||||
|
#realm-settings-night-logo {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: 0px 0px 10px hsla(0, 0%, 0%, 0.2);
|
box-shadow: 0px 0px 10px hsla(0, 0%, 0%, 0.2);
|
||||||
/* We allow actual images up to 800x100 in the main display, but the
|
/* We allow actual images up to 800x100 in the main display, but the
|
||||||
|
@ -1690,7 +1692,8 @@ input[type=text]#settings_search {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1023px) {
|
@media (max-width: 1023px) {
|
||||||
#realm-settings-logo {
|
#realm-settings-logo,
|
||||||
|
#realm-settings-night-logo {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
height: 75px;
|
height: 75px;
|
||||||
}
|
}
|
||||||
|
@ -1716,7 +1719,8 @@ input[type=text]#settings_search {
|
||||||
margin: 5px 0 0 0;
|
margin: 5px 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#realm-settings-logo {
|
#realm-settings-logo,
|
||||||
|
#realm-settings-night-logo {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>{{t "Organization logo for night mode" }}</h3>
|
||||||
|
<p>{{t "Like Organization Logo, but for the night theme." }}</p>
|
||||||
|
|
||||||
|
<div class="realm-logo-section realm-night-logo-section">
|
||||||
|
<div class="block realm-logo-block">
|
||||||
|
<img id="realm-settings-night-logo" src="{{ realm_night_logo_url }}">
|
||||||
|
<input type="file" name="realm_night_logo_file_input" class="notvisible"
|
||||||
|
id="realm_night_logo_file_input" value="{{t 'Upload logo' }}"/>
|
||||||
|
</div>
|
||||||
|
<div class="block avatar-controls">
|
||||||
|
<div id="realm_night_logo_file_input_error" class="alert text-error"></div>
|
||||||
|
<button class="button rounded sea-green w-200 block input-size"
|
||||||
|
id="realm_night_logo_upload_button">
|
||||||
|
<span id="upload_night_logo_button_text">{{t 'Upload new logo' }}</span>
|
||||||
|
<span id="upload_night_logo_spinner"></span>
|
||||||
|
</button>
|
||||||
|
<button class="button rounded btn-danger w-200 m-t-10 block input-size"
|
||||||
|
id="realm_night_logo_delete_button">{{t 'Delete logo' }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="light">{{t "Deactivate organization" }}</h3>
|
<h3 class="light">{{t "Deactivate organization" }}</h3>
|
||||||
<div class="deactivate-realm-section">
|
<div class="deactivate-realm-section">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|
|
@ -56,6 +56,9 @@ curl {{ api_url }}/v1/server_settings \
|
||||||
* `realm_logo`: the URI of the organization's logo as a horizontal
|
* `realm_logo`: the URI of the organization's logo as a horizontal
|
||||||
format image (displayed in the top-left corner of the logged-in
|
format image (displayed in the top-left corner of the logged-in
|
||||||
webapp).
|
webapp).
|
||||||
|
* `realm_night_logo`: the URI of the organization's logo in the night mode as a
|
||||||
|
horizontal format image (dispalyed in the top-left corner of the logged-in
|
||||||
|
webapp).
|
||||||
* `realm_description`: HTML description of the organization, as configured by
|
* `realm_description`: HTML description of the organization, as configured by
|
||||||
the [organization profile](/help/create-your-organization-profile).
|
the [organization profile](/help/create-your-organization-profile).
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<nav class="header-main rightside-userlist" id="top_navbar">
|
<nav class="header-main rightside-userlist" id="top_navbar">
|
||||||
<div class="column-left">
|
<div class="column-left">
|
||||||
<a class="brand no-style" href="#">
|
<a class="brand no-style" href="#">
|
||||||
<img id="realm-logo" src="{{ realm_logo }}" alt="" class="nav-logo no-drag">
|
<img id="realm-logo" src="{% if night_mode %} {{ realm_night_logo }}{% else %} {{ realm_logo }} {% endif %}" alt="" class="nav-logo no-drag"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="column-middle" id="navbar-middle">
|
<div class="column-middle" id="navbar-middle">
|
||||||
|
|
|
@ -55,6 +55,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
|
||||||
realm_name = None
|
realm_name = None
|
||||||
realm_icon = None
|
realm_icon = None
|
||||||
realm_logo = None
|
realm_logo = None
|
||||||
|
realm_night_logo = None
|
||||||
realm_description = None
|
realm_description = None
|
||||||
realm_invite_required = False
|
realm_invite_required = False
|
||||||
realm_plan_type = 0
|
realm_plan_type = 0
|
||||||
|
@ -62,7 +63,8 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
|
||||||
realm_uri = realm.uri
|
realm_uri = realm.uri
|
||||||
realm_name = realm.name
|
realm_name = realm.name
|
||||||
realm_icon = get_realm_icon_url(realm)
|
realm_icon = get_realm_icon_url(realm)
|
||||||
realm_logo = get_realm_logo_url(realm)
|
realm_logo = get_realm_logo_url(realm, night = False)
|
||||||
|
realm_night_logo = get_realm_logo_url(realm, night = True)
|
||||||
realm_description_raw = realm.description or "The coolest place in the universe."
|
realm_description_raw = realm.description or "The coolest place in the universe."
|
||||||
realm_description = bugdown_convert(realm_description_raw, message_realm=realm)
|
realm_description = bugdown_convert(realm_description_raw, message_realm=realm)
|
||||||
realm_invite_required = realm.invite_required
|
realm_invite_required = realm.invite_required
|
||||||
|
@ -118,6 +120,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
|
||||||
'realm_name': realm_name,
|
'realm_name': realm_name,
|
||||||
'realm_icon': realm_icon,
|
'realm_icon': realm_icon,
|
||||||
'realm_logo': realm_logo,
|
'realm_logo': realm_logo,
|
||||||
|
'realm_night_logo': realm_night_logo,
|
||||||
'realm_description': realm_description,
|
'realm_description': realm_description,
|
||||||
'realm_plan_type': realm_plan_type,
|
'realm_plan_type': realm_plan_type,
|
||||||
'root_domain_uri': settings.ROOT_DOMAIN_URI,
|
'root_domain_uri': settings.ROOT_DOMAIN_URI,
|
||||||
|
|
|
@ -3266,21 +3266,38 @@ def do_change_icon_source(realm: Realm, icon_source: str, log: bool=True) -> Non
|
||||||
icon_url=realm_icon_url(realm))),
|
icon_url=realm_icon_url(realm))),
|
||||||
active_user_ids(realm.id))
|
active_user_ids(realm.id))
|
||||||
|
|
||||||
def do_change_logo_source(realm: Realm, logo_source: str) -> None:
|
def do_change_logo_source(realm: Realm, logo_source: str, night: bool) -> None:
|
||||||
realm.logo_source = logo_source
|
if not night:
|
||||||
realm.logo_version += 1
|
realm.logo_source = logo_source
|
||||||
realm.save(update_fields=["logo_source", "logo_version"])
|
realm.logo_version += 1
|
||||||
|
realm.save(update_fields=["logo_source", "logo_version"])
|
||||||
|
|
||||||
|
else:
|
||||||
|
realm.night_logo_source = logo_source
|
||||||
|
realm.night_logo_version += 1
|
||||||
|
realm.save(update_fields=["night_logo_source", "night_logo_version"])
|
||||||
|
|
||||||
RealmAuditLog.objects.create(event_type=RealmAuditLog.REALM_LOGO_CHANGED,
|
RealmAuditLog.objects.create(event_type=RealmAuditLog.REALM_LOGO_CHANGED,
|
||||||
realm=realm, event_time=timezone_now())
|
realm=realm, event_time=timezone_now())
|
||||||
|
|
||||||
send_event(realm,
|
if not night:
|
||||||
dict(type='realm',
|
send_event(realm,
|
||||||
op='update_dict',
|
dict(type='realm',
|
||||||
property="logo",
|
op='update_dict',
|
||||||
data=dict(logo_source=realm.logo_source,
|
property="logo",
|
||||||
logo_url=realm_logo_url(realm))),
|
data=dict(logo_source=realm.logo_source,
|
||||||
active_user_ids(realm.id))
|
logo_url=realm_logo_url(realm, night))),
|
||||||
|
active_user_ids(realm.id))
|
||||||
|
|
||||||
|
else:
|
||||||
|
send_event(realm,
|
||||||
|
dict(type='realm',
|
||||||
|
op='update_dict',
|
||||||
|
property="night_logo",
|
||||||
|
data=dict(night_logo_source=realm.night_logo_source,
|
||||||
|
night_logo_url=realm_logo_url(realm, night))),
|
||||||
|
active_user_ids(realm.id))
|
||||||
|
|
||||||
|
|
||||||
def do_change_plan_type(realm: Realm, plan_type: int) -> None:
|
def do_change_plan_type(realm: Realm, plan_type: int) -> None:
|
||||||
old_value = realm.plan_type
|
old_value = realm.plan_type
|
||||||
|
|
|
@ -180,8 +180,10 @@ def fetch_initial_state_data(user_profile: UserProfile,
|
||||||
state['realm_icon_url'] = realm_icon_url(realm)
|
state['realm_icon_url'] = realm_icon_url(realm)
|
||||||
state['realm_icon_source'] = realm.icon_source
|
state['realm_icon_source'] = realm.icon_source
|
||||||
state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE
|
state['max_icon_file_size'] = settings.MAX_ICON_FILE_SIZE
|
||||||
state['realm_logo_url'] = realm_logo_url(realm)
|
state['realm_logo_url'] = realm_logo_url(realm, night = False)
|
||||||
state['realm_logo_source'] = realm.logo_source
|
state['realm_logo_source'] = realm.logo_source
|
||||||
|
state['realm_night_logo_url'] = realm_logo_url(realm, night = True)
|
||||||
|
state['realm_night_logo_source'] = realm.night_logo_source
|
||||||
state['max_logo_file_size'] = settings.MAX_LOGO_FILE_SIZE
|
state['max_logo_file_size'] = settings.MAX_LOGO_FILE_SIZE
|
||||||
state['realm_bot_domain'] = realm.get_bot_domain()
|
state['realm_bot_domain'] = realm.get_bot_domain()
|
||||||
state['realm_uri'] = realm.uri
|
state['realm_uri'] = realm.uri
|
||||||
|
|
|
@ -3,11 +3,17 @@ from django.conf import settings
|
||||||
from zerver.lib.upload import upload_backend
|
from zerver.lib.upload import upload_backend
|
||||||
from zerver.models import Realm
|
from zerver.models import Realm
|
||||||
|
|
||||||
def realm_logo_url(realm: Realm) -> str:
|
def realm_logo_url(realm: Realm, night: bool) -> str:
|
||||||
return get_realm_logo_url(realm)
|
return get_realm_logo_url(realm, night)
|
||||||
|
|
||||||
def get_realm_logo_url(realm: Realm) -> str:
|
def get_realm_logo_url(realm: Realm, night: bool) -> str:
|
||||||
if realm.logo_source == 'U':
|
if not night:
|
||||||
return upload_backend.get_realm_logo_url(realm.id, realm.logo_version)
|
if realm.logo_source == 'U':
|
||||||
|
return upload_backend.get_realm_logo_url(realm.id, realm.logo_version, night)
|
||||||
|
else:
|
||||||
|
return settings.DEFAULT_LOGO_URI+'?version=0'
|
||||||
else:
|
else:
|
||||||
return settings.DEFAULT_LOGO_URI+'?version=0'
|
if realm.night_logo_source == 'U':
|
||||||
|
return upload_backend.get_realm_logo_url(realm.id, realm.night_logo_version, night)
|
||||||
|
else:
|
||||||
|
return settings.DEFAULT_LOGO_URI+'?version=0'
|
||||||
|
|
|
@ -225,10 +225,11 @@ class ZulipUploadBackend:
|
||||||
def get_realm_icon_url(self, realm_id: int, version: int) -> str:
|
def get_realm_icon_url(self, realm_id: int, version: int) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile) -> None:
|
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile,
|
||||||
|
night: bool) -> None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_realm_logo_url(self, realm_id: int, version: int) -> str:
|
def get_realm_logo_url(self, realm_id: int, version: int, night: bool) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def upload_emoji_image(self, emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
|
def upload_emoji_image(self, emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
|
||||||
|
@ -467,10 +468,15 @@ class S3UploadBackend(ZulipUploadBackend):
|
||||||
# ?x=x allows templates to append additional parameters with &s
|
# ?x=x allows templates to append additional parameters with &s
|
||||||
return "https://%s.s3.amazonaws.com/%s/realm/icon.png?version=%s" % (bucket, realm_id, version)
|
return "https://%s.s3.amazonaws.com/%s/realm/icon.png?version=%s" % (bucket, realm_id, version)
|
||||||
|
|
||||||
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile) -> None:
|
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile,
|
||||||
|
night: bool) -> None:
|
||||||
content_type = guess_type(logo_file.name)[0]
|
content_type = guess_type(logo_file.name)[0]
|
||||||
bucket_name = settings.S3_AVATAR_BUCKET
|
bucket_name = settings.S3_AVATAR_BUCKET
|
||||||
s3_file_name = os.path.join(str(user_profile.realm.id), 'realm', 'logo')
|
if night:
|
||||||
|
basename = 'night_logo'
|
||||||
|
else:
|
||||||
|
basename = 'logo'
|
||||||
|
s3_file_name = os.path.join(str(user_profile.realm.id), 'realm', basename)
|
||||||
|
|
||||||
image_data = logo_file.read()
|
image_data = logo_file.read()
|
||||||
upload_image_to_s3(
|
upload_image_to_s3(
|
||||||
|
@ -492,10 +498,14 @@ class S3UploadBackend(ZulipUploadBackend):
|
||||||
# See avatar_url in avatar.py for URL. (That code also handles the case
|
# See avatar_url in avatar.py for URL. (That code also handles the case
|
||||||
# that users use gravatar.)
|
# that users use gravatar.)
|
||||||
|
|
||||||
def get_realm_logo_url(self, realm_id: int, version: int) -> str:
|
def get_realm_logo_url(self, realm_id: int, version: int, night: bool) -> str:
|
||||||
bucket = settings.S3_AVATAR_BUCKET
|
bucket = settings.S3_AVATAR_BUCKET
|
||||||
# ?x=x allows templates to append additional parameters with &s
|
# ?x=x allows templates to append additional parameters with &s
|
||||||
return "https://%s.s3.amazonaws.com/%s/realm/logo.png?version=%s" % (bucket, realm_id, version)
|
if not night:
|
||||||
|
file_name = 'logo.png'
|
||||||
|
else:
|
||||||
|
file_name = 'night_logo.png'
|
||||||
|
return "https://%s.s3.amazonaws.com/%s/realm/%s?version=%s" % (bucket, realm_id, file_name, version)
|
||||||
|
|
||||||
def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
|
def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
|
||||||
file_path = user_avatar_path(user_profile)
|
file_path = user_avatar_path(user_profile)
|
||||||
|
@ -671,21 +681,31 @@ class LocalUploadBackend(ZulipUploadBackend):
|
||||||
# ?x=x allows templates to append additional parameters with &s
|
# ?x=x allows templates to append additional parameters with &s
|
||||||
return "/user_avatars/%s/realm/icon.png?version=%s" % (realm_id, version)
|
return "/user_avatars/%s/realm/icon.png?version=%s" % (realm_id, version)
|
||||||
|
|
||||||
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile) -> None:
|
def upload_realm_logo_image(self, logo_file: File, user_profile: UserProfile,
|
||||||
|
night: bool) -> None:
|
||||||
upload_path = os.path.join('avatars', str(user_profile.realm.id), 'realm')
|
upload_path = os.path.join('avatars', str(user_profile.realm.id), 'realm')
|
||||||
|
if night:
|
||||||
|
original_file = 'night_logo.original'
|
||||||
|
resized_file = 'night_logo.png'
|
||||||
|
else:
|
||||||
|
original_file = 'logo.original'
|
||||||
|
resized_file = 'logo.png'
|
||||||
image_data = logo_file.read()
|
image_data = logo_file.read()
|
||||||
write_local_file(
|
write_local_file(
|
||||||
upload_path,
|
upload_path,
|
||||||
'logo.original',
|
original_file,
|
||||||
image_data)
|
image_data)
|
||||||
|
|
||||||
resized_data = resize_logo(image_data)
|
resized_data = resize_logo(image_data)
|
||||||
write_local_file(upload_path, 'logo.png', resized_data)
|
write_local_file(upload_path, resized_file, resized_data)
|
||||||
|
|
||||||
def get_realm_logo_url(self, realm_id: int, version: int) -> str:
|
def get_realm_logo_url(self, realm_id: int, version: int, night: bool) -> str:
|
||||||
# ?x=x allows templates to append additional parameters with &s
|
# ?x=x allows templates to append additional parameters with &s
|
||||||
return "/user_avatars/%s/realm/logo.png?version=%s" % (realm_id, version)
|
if night:
|
||||||
|
file_name = 'night_logo.png'
|
||||||
|
else:
|
||||||
|
file_name = 'logo.png'
|
||||||
|
return "/user_avatars/%s/realm/%s?version=%s" % (realm_id, file_name, version)
|
||||||
|
|
||||||
def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
|
def ensure_medium_avatar_image(self, user_profile: UserProfile) -> None:
|
||||||
file_path = user_avatar_path(user_profile)
|
file_path = user_avatar_path(user_profile)
|
||||||
|
@ -757,8 +777,8 @@ def copy_avatar(source_profile: UserProfile, target_profile: UserProfile) -> Non
|
||||||
def upload_icon_image(user_file: File, user_profile: UserProfile) -> None:
|
def upload_icon_image(user_file: File, user_profile: UserProfile) -> None:
|
||||||
upload_backend.upload_realm_icon_image(user_file, user_profile)
|
upload_backend.upload_realm_icon_image(user_file, user_profile)
|
||||||
|
|
||||||
def upload_logo_image(user_file: File, user_profile: UserProfile) -> None:
|
def upload_logo_image(user_file: File, user_profile: UserProfile, night: bool) -> None:
|
||||||
upload_backend.upload_realm_logo_image(user_file, user_profile)
|
upload_backend.upload_realm_logo_image(user_file, user_profile, night)
|
||||||
|
|
||||||
def upload_emoji_image(emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
|
def upload_emoji_image(emoji_file: File, emoji_file_name: str, user_profile: UserProfile) -> None:
|
||||||
upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile)
|
upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.18 on 2019-01-15 16:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0207_multiuseinvite_invited_as'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='night_logo_source',
|
||||||
|
field=models.CharField(choices=[('D', 'Default to Zulip'), ('U', 'Uploaded by administrator')], default='D', max_length=1),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='night_logo_version',
|
||||||
|
field=models.PositiveSmallIntegerField(default=1),
|
||||||
|
),
|
||||||
|
]
|
|
@ -316,6 +316,10 @@ class Realm(models.Model):
|
||||||
max_length=1) # type: str
|
max_length=1) # type: str
|
||||||
logo_version = models.PositiveSmallIntegerField(default=1) # type: int
|
logo_version = models.PositiveSmallIntegerField(default=1) # type: int
|
||||||
|
|
||||||
|
night_logo_source = models.CharField(default=LOGO_DEFAULT, choices=LOGO_SOURCES,
|
||||||
|
max_length=1) # type: str
|
||||||
|
night_logo_version = models.PositiveSmallIntegerField(default=1) # type: int
|
||||||
|
|
||||||
BOT_CREATION_POLICY_TYPES = [
|
BOT_CREATION_POLICY_TYPES = [
|
||||||
BOT_CREATION_EVERYONE,
|
BOT_CREATION_EVERYONE,
|
||||||
BOT_CREATION_LIMIT_GENERIC_BOTS,
|
BOT_CREATION_LIMIT_GENERIC_BOTS,
|
||||||
|
|
|
@ -1759,6 +1759,10 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
description: The URI of the organization's top-left navbar logo
|
description: The URI of the organization's top-left navbar logo
|
||||||
(usually a wide rectangular version of the logo).
|
(usually a wide rectangular version of the logo).
|
||||||
|
realm_night_logo:
|
||||||
|
type: string
|
||||||
|
description: The URI of the organization's top-left navbar logo in night_mode
|
||||||
|
(usually a wide rectangular version of the logo).
|
||||||
realm_description:
|
realm_description:
|
||||||
type: string
|
type: string
|
||||||
description: HTML description of the organization, as
|
description: HTML description of the organization, as
|
||||||
|
@ -1771,6 +1775,7 @@ paths:
|
||||||
"msg": "",
|
"msg": "",
|
||||||
"realm_icon": "https://secure.gravatar.com/avatar/62429d594b6ffc712f54aee976a18b44?d=identicon",
|
"realm_icon": "https://secure.gravatar.com/avatar/62429d594b6ffc712f54aee976a18b44?d=identicon",
|
||||||
"realm_logo": "/static/images/logo/zulip-org-logo.png",
|
"realm_logo": "/static/images/logo/zulip-org-logo.png",
|
||||||
|
"realm_night_logo": "static/images/logo/zulip-org-logo.png",
|
||||||
"realm_description": "<p>The Zulip development environment default organization. It's great for testing!</p>",
|
"realm_description": "<p>The Zulip development environment default organization. It's great for testing!</p>",
|
||||||
"email_auth_enabled": true,
|
"email_auth_enabled": true,
|
||||||
"zulip_version": "1.9.0-rc1+git",
|
"zulip_version": "1.9.0-rc1+git",
|
||||||
|
|
|
@ -1719,6 +1719,7 @@ class FetchAuthBackends(ZulipTestCase):
|
||||||
('realm_description', check_string),
|
('realm_description', check_string),
|
||||||
('realm_icon', check_string),
|
('realm_icon', check_string),
|
||||||
('realm_logo', check_string),
|
('realm_logo', check_string),
|
||||||
|
('realm_night_logo', check_string),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_fetch_auth_backend_format(self) -> None:
|
def test_fetch_auth_backend_format(self) -> None:
|
||||||
|
|
|
@ -157,6 +157,8 @@ class HomeTest(ZulipTestCase):
|
||||||
"realm_name",
|
"realm_name",
|
||||||
"realm_name_changes_disabled",
|
"realm_name_changes_disabled",
|
||||||
"realm_name_in_notifications",
|
"realm_name_in_notifications",
|
||||||
|
"realm_night_logo_source",
|
||||||
|
"realm_night_logo_url",
|
||||||
"realm_non_active_users",
|
"realm_non_active_users",
|
||||||
"realm_notifications_stream_id",
|
"realm_notifications_stream_id",
|
||||||
"realm_password_auth_enabled",
|
"realm_password_auth_enabled",
|
||||||
|
|
|
@ -40,6 +40,7 @@ from zerver.lib.users import get_api_key
|
||||||
from zerver.views.upload import upload_file_backend
|
from zerver.views.upload import upload_file_backend
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
|
import ujson
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
@ -799,8 +800,10 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
"/user_avatars/hash-medium.png?x=x")
|
"/user_avatars/hash-medium.png?x=x")
|
||||||
self.assertEqual(backend.get_realm_icon_url(15, 1),
|
self.assertEqual(backend.get_realm_icon_url(15, 1),
|
||||||
"/user_avatars/15/realm/icon.png?version=1")
|
"/user_avatars/15/realm/icon.png?version=1")
|
||||||
self.assertEqual(backend.get_realm_logo_url(15, 1),
|
self.assertEqual(backend.get_realm_logo_url(15, 1, False),
|
||||||
"/user_avatars/15/realm/logo.png?version=1")
|
"/user_avatars/15/realm/logo.png?version=1")
|
||||||
|
self.assertEqual(backend.get_realm_logo_url(15, 1, True),
|
||||||
|
"/user_avatars/15/realm/night_logo.png?version=1")
|
||||||
|
|
||||||
with self.settings(S3_AVATAR_BUCKET="bucket"):
|
with self.settings(S3_AVATAR_BUCKET="bucket"):
|
||||||
backend = S3UploadBackend()
|
backend = S3UploadBackend()
|
||||||
|
@ -810,8 +813,10 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
"https://bucket.s3.amazonaws.com/hash-medium.png?x=x")
|
"https://bucket.s3.amazonaws.com/hash-medium.png?x=x")
|
||||||
self.assertEqual(backend.get_realm_icon_url(15, 1),
|
self.assertEqual(backend.get_realm_icon_url(15, 1),
|
||||||
"https://bucket.s3.amazonaws.com/15/realm/icon.png?version=1")
|
"https://bucket.s3.amazonaws.com/15/realm/icon.png?version=1")
|
||||||
self.assertEqual(backend.get_realm_logo_url(15, 1),
|
self.assertEqual(backend.get_realm_logo_url(15, 1, False),
|
||||||
"https://bucket.s3.amazonaws.com/15/realm/logo.png?version=1")
|
"https://bucket.s3.amazonaws.com/15/realm/logo.png?version=1")
|
||||||
|
self.assertEqual(backend.get_realm_logo_url(15, 1, True),
|
||||||
|
"https://bucket.s3.amazonaws.com/15/realm/night_logo.png?version=1")
|
||||||
|
|
||||||
def test_multiple_upload_failure(self) -> None:
|
def test_multiple_upload_failure(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1250,6 +1255,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
destroy_uploads()
|
destroy_uploads()
|
||||||
|
|
||||||
class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
|
night = False
|
||||||
|
|
||||||
def test_multiple_upload_failure(self) -> None:
|
def test_multiple_upload_failure(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1259,7 +1265,8 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
with get_test_image_file('img.png') as fp1, \
|
with get_test_image_file('img.png') as fp1, \
|
||||||
get_test_image_file('img.png') as fp2:
|
get_test_image_file('img.png') as fp2:
|
||||||
result = self.client_post("/json/realm/logo", {'f1': fp1, 'f2': fp2})
|
result = self.client_post("/json/realm/logo", {'f1': fp1, 'f2': fp2,
|
||||||
|
'night': ujson.dumps(self.night)})
|
||||||
self.assert_json_error(result, "You must upload exactly one logo.")
|
self.assert_json_error(result, "You must upload exactly one logo.")
|
||||||
|
|
||||||
def test_no_file_upload_failure(self) -> None:
|
def test_no_file_upload_failure(self) -> None:
|
||||||
|
@ -1268,7 +1275,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
|
|
||||||
result = self.client_post("/json/realm/logo")
|
result = self.client_post("/json/realm/logo", {'night': ujson.dumps(self.night)})
|
||||||
self.assert_json_error(result, "You must upload exactly one logo.")
|
self.assert_json_error(result, "You must upload exactly one logo.")
|
||||||
|
|
||||||
correct_files = [
|
correct_files = [
|
||||||
|
@ -1283,7 +1290,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
def test_no_admin_user_upload(self) -> None:
|
def test_no_admin_user_upload(self) -> None:
|
||||||
self.login(self.example_email("hamlet"))
|
self.login(self.example_email("hamlet"))
|
||||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||||
result = self.client_post("/json/realm/logo", {'file': fp})
|
result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
|
||||||
self.assert_json_error(result, 'Must be an organization administrator')
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
def test_upload_limited_plan_type(self) -> None:
|
def test_upload_limited_plan_type(self) -> None:
|
||||||
|
@ -1291,45 +1298,53 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
do_change_plan_type(user_profile.realm, Realm.LIMITED)
|
do_change_plan_type(user_profile.realm, Realm.LIMITED)
|
||||||
self.login(user_profile.email)
|
self.login(user_profile.email)
|
||||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||||
result = self.client_post("/json/realm/logo", {'file': fp})
|
result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
|
||||||
self.assert_json_error(result, 'Feature unavailable on your current plan.')
|
self.assert_json_error(result, 'Feature unavailable on your current plan.')
|
||||||
|
|
||||||
def test_get_default_logo(self) -> None:
|
def test_get_default_logo(self) -> None:
|
||||||
self.login(self.example_email("hamlet"))
|
self.login(self.example_email("hamlet"))
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
realm.logo_source = Realm.LOGO_DEFAULT
|
realm.logo_source = Realm.LOGO_DEFAULT
|
||||||
|
realm.night_logo_source = Realm.LOGO_DEFAULT
|
||||||
realm.save()
|
realm.save()
|
||||||
|
response = self.client_get("/json/realm/logo", {'night': ujson.dumps(self.night)})
|
||||||
response = self.client_get("/json/realm/logo?foo=bar")
|
|
||||||
redirect_url = response['Location']
|
redirect_url = response['Location']
|
||||||
self.assertEqual(redirect_url, realm_logo_url(realm) + '&foo=bar')
|
self.assertEqual(redirect_url, realm_logo_url(realm, self.night) +
|
||||||
|
'&night=%s' % (str(self.night).lower()))
|
||||||
|
|
||||||
def test_get_realm_logo(self) -> None:
|
def test_get_realm_logo(self) -> None:
|
||||||
self.login(self.example_email("hamlet"))
|
self.login(self.example_email("hamlet"))
|
||||||
|
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
realm.logo_source = Realm.LOGO_UPLOADED
|
realm.logo_source = Realm.LOGO_UPLOADED
|
||||||
|
realm.night_logo_source = Realm.LOGO_UPLOADED
|
||||||
realm.save()
|
realm.save()
|
||||||
response = self.client_get("/json/realm/logo?foo=bar")
|
response = self.client_get("/json/realm/logo", {'night': ujson.dumps(self.night)})
|
||||||
redirect_url = response['Location']
|
redirect_url = response['Location']
|
||||||
self.assertTrue(redirect_url.endswith(realm_logo_url(realm) + '&foo=bar'))
|
self.assertTrue(redirect_url.endswith(realm_logo_url(realm, self.night) +
|
||||||
|
'&night=%s' % (str(self.night).lower())))
|
||||||
|
|
||||||
def test_valid_logos(self) -> None:
|
def test_valid_logos(self) -> None:
|
||||||
"""
|
"""
|
||||||
A PUT request to /json/realm/logo with a valid file should return a url
|
A PUT request to /json/realm/logo with a valid file should return a url
|
||||||
and actually create an realm logo.
|
and actually create an realm logo.
|
||||||
"""
|
"""
|
||||||
|
if self.night:
|
||||||
|
field_name = 'night_logo_url'
|
||||||
|
file_name = 'night_logo.png'
|
||||||
|
else:
|
||||||
|
field_name = 'logo_url'
|
||||||
|
file_name = 'logo.png'
|
||||||
for fname, rfname in self.correct_files:
|
for fname, rfname in self.correct_files:
|
||||||
# TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
|
# TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
|
||||||
# with self.subTest(fname=fname):
|
# with self.subTest(fname=fname):
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
with get_test_image_file(fname) as fp:
|
with get_test_image_file(fname) as fp:
|
||||||
result = self.client_post("/json/realm/logo", {'file': fp})
|
result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
self.assertIn("logo_url", result.json())
|
self.assertIn(field_name, result.json())
|
||||||
base = '/user_avatars/%s/realm/logo.png' % (realm.id,)
|
base = '/user_avatars/%s/realm/%s' % (realm.id, file_name)
|
||||||
url = result.json()['logo_url']
|
url = result.json()[field_name]
|
||||||
self.assertEqual(base, url[:len(base)])
|
self.assertEqual(base, url[:len(base)])
|
||||||
|
|
||||||
if rfname is not None:
|
if rfname is not None:
|
||||||
|
@ -1339,7 +1354,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
# while trying to fit in a 800 x 100 box without losing part of the image
|
# while trying to fit in a 800 x 100 box without losing part of the image
|
||||||
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
|
self.assertEqual(Image.open(io.BytesIO(data)).size, (100, 100))
|
||||||
|
|
||||||
def test_invalid_logos(self) -> None:
|
def test_invalid_logo_upload(self) -> None:
|
||||||
"""
|
"""
|
||||||
A PUT request to /json/realm/logo with an invalid file should fail.
|
A PUT request to /json/realm/logo with an invalid file should fail.
|
||||||
"""
|
"""
|
||||||
|
@ -1347,7 +1362,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
# with self.subTest(fname=fname):
|
# with self.subTest(fname=fname):
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
with get_test_image_file(fname) as fp:
|
with get_test_image_file(fname) as fp:
|
||||||
result = self.client_post("/json/realm/logo", {'file': fp})
|
result = self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
|
||||||
|
|
||||||
self.assert_json_error(result, "Could not decode image; did you upload an image file?")
|
self.assert_json_error(result, "Could not decode image; did you upload an image file?")
|
||||||
|
|
||||||
|
@ -1355,39 +1370,57 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
"""
|
"""
|
||||||
A DELETE request to /json/realm/logo should delete the realm logo and return gravatar URL
|
A DELETE request to /json/realm/logo should delete the realm logo and return gravatar URL
|
||||||
"""
|
"""
|
||||||
|
if self.night:
|
||||||
|
field_name = 'night_logo_url'
|
||||||
|
else:
|
||||||
|
field_name = 'logo_url'
|
||||||
|
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
realm.logo_source = Realm.LOGO_UPLOADED
|
realm.logo_source = Realm.LOGO_UPLOADED
|
||||||
|
realm.night_logo_source = Realm.LOGO_UPLOADED
|
||||||
realm.save()
|
realm.save()
|
||||||
|
result = self.client_delete("/json/realm/logo", {'night': ujson.dumps(self.night)})
|
||||||
result = self.client_delete("/json/realm/logo")
|
|
||||||
|
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
self.assertIn("logo_url", result.json())
|
self.assertIn(field_name, result.json())
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
self.assertEqual(result.json()["logo_url"], realm_logo_url(realm))
|
self.assertEqual(result.json()[field_name], realm_logo_url(realm, self.night))
|
||||||
self.assertEqual(realm.logo_source, Realm.LOGO_DEFAULT)
|
if self.night:
|
||||||
|
self.assertEqual(realm.night_logo_source, Realm.LOGO_DEFAULT)
|
||||||
|
else:
|
||||||
|
self.assertEqual(realm.logo_source, Realm.LOGO_DEFAULT)
|
||||||
|
|
||||||
def test_realm_logo_version(self) -> None:
|
def test_logo_version(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
logo_version = realm.logo_version
|
if self.night:
|
||||||
self.assertEqual(logo_version, 1)
|
version = realm.night_logo_version
|
||||||
|
else:
|
||||||
|
version = realm.logo_version
|
||||||
|
self.assertEqual(version, 1)
|
||||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||||
self.client_post("/json/realm/logo", {'file': fp})
|
self.client_post("/json/realm/logo", {'file': fp, 'night': ujson.dumps(self.night)})
|
||||||
realm = get_realm('zulip')
|
realm = get_realm('zulip')
|
||||||
self.assertEqual(realm.logo_version, logo_version + 1)
|
if self.night:
|
||||||
|
self.assertEqual(realm.night_logo_version, version + 1)
|
||||||
|
else:
|
||||||
|
self.assertEqual(realm.logo_version, version + 1)
|
||||||
|
|
||||||
def test_realm_logo_upload_file_size_error(self) -> None:
|
def test_logo_upload_file_size_error(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
with get_test_image_file(self.correct_files[0][0]) as fp:
|
with get_test_image_file(self.correct_files[0][0]) as fp:
|
||||||
with self.settings(MAX_LOGO_FILE_SIZE=0):
|
with self.settings(MAX_LOGO_FILE_SIZE=0):
|
||||||
result = self.client_post("/json/realm/logo", {'file': fp})
|
result = self.client_post("/json/realm/logo", {'file': fp, 'night':
|
||||||
|
ujson.dumps(self.night)})
|
||||||
self.assert_json_error(result, "Uploaded file is larger than the allowed limit of 0 MB")
|
self.assert_json_error(result, "Uploaded file is larger than the allowed limit of 0 MB")
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
destroy_uploads()
|
destroy_uploads()
|
||||||
|
|
||||||
|
class RealmNightLogoTest(RealmLogoTest):
|
||||||
|
# Run the same tests as for RealmLogoTest, just with night mode enabled
|
||||||
|
night = True
|
||||||
|
|
||||||
class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
|
class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
|
||||||
|
|
||||||
def test_file_upload_local(self) -> None:
|
def test_file_upload_local(self) -> None:
|
||||||
|
@ -1662,23 +1695,29 @@ class S3Test(ZulipTestCase):
|
||||||
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
|
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
|
||||||
|
|
||||||
@use_s3_backend
|
@use_s3_backend
|
||||||
def test_upload_realm_logo_image(self) -> None:
|
def _test_upload_logo_image(self, night: bool, file_name: str) -> None:
|
||||||
bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
|
bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
|
||||||
|
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
image_file = get_test_image_file("img.png")
|
image_file = get_test_image_file("img.png")
|
||||||
zerver.lib.upload.upload_backend.upload_realm_logo_image(image_file, user_profile)
|
zerver.lib.upload.upload_backend.upload_realm_logo_image(image_file, user_profile, night)
|
||||||
|
|
||||||
original_path_id = os.path.join(str(user_profile.realm.id), "realm", "logo.original")
|
original_path_id = os.path.join(str(user_profile.realm.id), "realm", "%s.original" % (file_name))
|
||||||
|
print(original_path_id)
|
||||||
original_key = bucket.get_key(original_path_id)
|
original_key = bucket.get_key(original_path_id)
|
||||||
|
print(original_key)
|
||||||
image_file.seek(0)
|
image_file.seek(0)
|
||||||
self.assertEqual(image_file.read(), original_key.get_contents_as_string())
|
self.assertEqual(image_file.read(), original_key.get_contents_as_string())
|
||||||
|
|
||||||
resized_path_id = os.path.join(str(user_profile.realm.id), "realm", "logo.png")
|
resized_path_id = os.path.join(str(user_profile.realm.id), "realm", "%s.png" % (file_name))
|
||||||
resized_data = bucket.get_key(resized_path_id).read()
|
resized_data = bucket.get_key(resized_path_id).read()
|
||||||
resized_image = Image.open(io.BytesIO(resized_data)).size
|
resized_image = Image.open(io.BytesIO(resized_data)).size
|
||||||
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
|
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
|
||||||
|
|
||||||
|
def test_upload_realm_logo_image(self) -> None:
|
||||||
|
self._test_upload_logo_image(night = False, file_name = 'logo')
|
||||||
|
self._test_upload_logo_image(night = True, file_name = 'night_logo')
|
||||||
|
|
||||||
@use_s3_backend
|
@use_s3_backend
|
||||||
def test_upload_emoji_image(self) -> None:
|
def test_upload_emoji_image(self) -> None:
|
||||||
bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
|
bucket = create_s3_buckets(settings.S3_AVATAR_BUCKET)[0]
|
||||||
|
@ -1802,5 +1841,8 @@ class DecompressionBombTests(ZulipTestCase):
|
||||||
with get_test_image_file("bomb.png") as fp:
|
with get_test_image_file("bomb.png") as fp:
|
||||||
for url, error_string in self.test_urls.items():
|
for url, error_string in self.test_urls.items():
|
||||||
fp.seek(0, 0)
|
fp.seek(0, 0)
|
||||||
result = self.client_post(url, {'f1': fp})
|
if (url == "/json/realm/logo"):
|
||||||
|
result = self.client_post(url, {'f1': fp, 'night': ujson.dumps(False)})
|
||||||
|
else:
|
||||||
|
result = self.client_post(url, {'f1': fp})
|
||||||
self.assert_json_error(result, error_string)
|
self.assert_json_error(result, error_string)
|
||||||
|
|
|
@ -872,6 +872,7 @@ def api_get_server_settings(request: HttpRequest) -> HttpResponse:
|
||||||
"realm_name",
|
"realm_name",
|
||||||
"realm_icon",
|
"realm_icon",
|
||||||
"realm_logo",
|
"realm_logo",
|
||||||
|
"realm_night_logo",
|
||||||
"realm_description"]:
|
"realm_description"]:
|
||||||
if context[settings_item] is not None:
|
if context[settings_item] is not None:
|
||||||
result[settings_item] = context[settings_item]
|
result[settings_item] = context[settings_item]
|
||||||
|
|
|
@ -285,6 +285,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
||||||
'show_plans': show_plans,
|
'show_plans': show_plans,
|
||||||
'is_admin': user_profile.is_realm_admin,
|
'is_admin': user_profile.is_realm_admin,
|
||||||
'is_guest': user_profile.is_guest,
|
'is_guest': user_profile.is_guest,
|
||||||
|
'night_mode': user_profile.night_mode,
|
||||||
'show_webathena': user_profile.realm.webathena_enabled,
|
'show_webathena': user_profile.realm.webathena_enabled,
|
||||||
'enable_feedback': settings.ENABLE_FEEDBACK,
|
'enable_feedback': settings.ENABLE_FEEDBACK,
|
||||||
'embedded': narrow_stream is not None,
|
'embedded': narrow_stream is not None,
|
||||||
|
|
|
@ -2,7 +2,11 @@ from django.conf import settings
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.http import HttpResponse, HttpRequest
|
from django.http import HttpResponse, HttpRequest
|
||||||
|
from typing import Optional, Callable, Any
|
||||||
|
|
||||||
|
from zerver.lib.validator import check_string, check_int, check_list, check_dict, \
|
||||||
|
check_bool, check_variable_type, check_capped_string, check_color
|
||||||
|
from zerver.lib.request import REQ, has_request_variables
|
||||||
from zerver.decorator import require_realm_admin
|
from zerver.decorator import require_realm_admin
|
||||||
from zerver.lib.actions import do_change_logo_source
|
from zerver.lib.actions import do_change_logo_source
|
||||||
from zerver.lib.realm_logo import realm_logo_url
|
from zerver.lib.realm_logo import realm_logo_url
|
||||||
|
@ -12,42 +16,54 @@ from zerver.models import Realm, UserProfile
|
||||||
|
|
||||||
|
|
||||||
@require_realm_admin
|
@require_realm_admin
|
||||||
def upload_logo(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
@has_request_variables
|
||||||
|
def upload_logo(request: HttpRequest, user_profile: UserProfile,
|
||||||
|
night: bool=REQ(validator=check_bool)) -> HttpResponse:
|
||||||
if user_profile.realm.plan_type == Realm.LIMITED:
|
if user_profile.realm.plan_type == Realm.LIMITED:
|
||||||
return json_error(_("Feature unavailable on your current plan."))
|
return json_error(_("Feature unavailable on your current plan."))
|
||||||
|
|
||||||
if len(request.FILES) != 1:
|
if len(request.FILES) != 1:
|
||||||
return json_error(_("You must upload exactly one logo."))
|
return json_error(_("You must upload exactly one logo."))
|
||||||
|
|
||||||
logo_file = list(request.FILES.values())[0]
|
logo_file = list(request.FILES.values())[0]
|
||||||
if ((settings.MAX_LOGO_FILE_SIZE * 1024 * 1024) < logo_file.size):
|
if ((settings.MAX_LOGO_FILE_SIZE * 1024 * 1024) < logo_file.size):
|
||||||
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
||||||
settings.MAX_LOGO_FILE_SIZE))
|
settings.MAX_LOGO_FILE_SIZE))
|
||||||
upload_logo_image(logo_file, user_profile)
|
upload_logo_image(logo_file, user_profile, night)
|
||||||
do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_UPLOADED)
|
do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_UPLOADED, night)
|
||||||
logo_url = realm_logo_url(user_profile.realm)
|
logo_url = realm_logo_url(user_profile.realm, night)
|
||||||
|
if night:
|
||||||
json_result = dict(
|
json_result = dict(
|
||||||
logo_url=logo_url
|
night_logo_url=logo_url
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
json_result = dict(
|
||||||
|
logo_url=logo_url
|
||||||
|
)
|
||||||
return json_success(json_result)
|
return json_success(json_result)
|
||||||
|
|
||||||
|
|
||||||
@require_realm_admin
|
@require_realm_admin
|
||||||
def delete_logo_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
@has_request_variables
|
||||||
|
def delete_logo_backend(request: HttpRequest, user_profile: UserProfile,
|
||||||
|
night: bool=REQ(validator=check_bool)) -> HttpResponse:
|
||||||
# We don't actually delete the logo because it might still
|
# We don't actually delete the logo because it might still
|
||||||
# be needed if the URL was cached and it is rewrited
|
# be needed if the URL was cached and it is rewrited
|
||||||
# in any case after next update.
|
# in any case after next update.
|
||||||
do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_DEFAULT)
|
do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_DEFAULT, night)
|
||||||
default_url = realm_logo_url(user_profile.realm)
|
default_url = realm_logo_url(user_profile.realm, night)
|
||||||
json_result = dict(
|
if night:
|
||||||
logo_url=default_url
|
json_result = dict(
|
||||||
)
|
night_logo_url=default_url
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
json_result = dict(
|
||||||
|
logo_url=default_url
|
||||||
|
)
|
||||||
return json_success(json_result)
|
return json_success(json_result)
|
||||||
|
|
||||||
|
@has_request_variables
|
||||||
def get_logo_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
def get_logo_backend(request: HttpRequest, user_profile: UserProfile,
|
||||||
url = realm_logo_url(user_profile.realm)
|
night: bool=REQ(validator=check_bool)) -> HttpResponse:
|
||||||
|
url = realm_logo_url(user_profile.realm, night)
|
||||||
|
|
||||||
# We can rely on the url already having query parameters. Because
|
# We can rely on the url already having query parameters. Because
|
||||||
# our templates depend on being able to use the ampersand to
|
# our templates depend on being able to use the ampersand to
|
||||||
|
|
|
@ -98,7 +98,7 @@ v1_api_and_json_patterns = [
|
||||||
'DELETE': 'zerver.views.realm_icon.delete_icon_backend',
|
'DELETE': 'zerver.views.realm_icon.delete_icon_backend',
|
||||||
'GET': 'zerver.views.realm_icon.get_icon_backend'}),
|
'GET': 'zerver.views.realm_icon.get_icon_backend'}),
|
||||||
|
|
||||||
# realm/logo -> zerver.views.realm_logo_
|
# realm/logo -> zerver.views.realm_logo
|
||||||
url(r'^realm/logo$', rest_dispatch,
|
url(r'^realm/logo$', rest_dispatch,
|
||||||
{'POST': 'zerver.views.realm_logo.upload_logo',
|
{'POST': 'zerver.views.realm_logo.upload_logo',
|
||||||
'DELETE': 'zerver.views.realm_logo.delete_logo_backend',
|
'DELETE': 'zerver.views.realm_logo.delete_logo_backend',
|
||||||
|
|
Loading…
Reference in New Issue