diff --git a/zerver/context_processors.py b/zerver/context_processors.py
index 6cc89d1d6d..477942fd17 100644
--- a/zerver/context_processors.py
+++ b/zerver/context_processors.py
@@ -55,6 +55,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
realm_name = None
realm_icon = None
realm_logo = None
+ realm_night_logo = None
realm_description = None
realm_invite_required = False
realm_plan_type = 0
@@ -62,7 +63,8 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
realm_uri = realm.uri
realm_name = realm.name
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 = bugdown_convert(realm_description_raw, message_realm=realm)
realm_invite_required = realm.invite_required
@@ -118,6 +120,7 @@ def zulip_default_context(request: HttpRequest) -> Dict[str, Any]:
'realm_name': realm_name,
'realm_icon': realm_icon,
'realm_logo': realm_logo,
+ 'realm_night_logo': realm_night_logo,
'realm_description': realm_description,
'realm_plan_type': realm_plan_type,
'root_domain_uri': settings.ROOT_DOMAIN_URI,
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
index 5aa3678c38..55b90134f2 100644
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -3266,21 +3266,38 @@ def do_change_icon_source(realm: Realm, icon_source: str, log: bool=True) -> Non
icon_url=realm_icon_url(realm))),
active_user_ids(realm.id))
-def do_change_logo_source(realm: Realm, logo_source: str) -> None:
- realm.logo_source = logo_source
- realm.logo_version += 1
- realm.save(update_fields=["logo_source", "logo_version"])
+def do_change_logo_source(realm: Realm, logo_source: str, night: bool) -> None:
+ if not night:
+ realm.logo_source = logo_source
+ 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,
realm=realm, event_time=timezone_now())
- send_event(realm,
- dict(type='realm',
- op='update_dict',
- property="logo",
- data=dict(logo_source=realm.logo_source,
- logo_url=realm_logo_url(realm))),
- active_user_ids(realm.id))
+ if not night:
+ send_event(realm,
+ dict(type='realm',
+ op='update_dict',
+ property="logo",
+ data=dict(logo_source=realm.logo_source,
+ 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:
old_value = realm.plan_type
diff --git a/zerver/lib/events.py b/zerver/lib/events.py
index 9676a17dc1..602784cfe1 100644
--- a/zerver/lib/events.py
+++ b/zerver/lib/events.py
@@ -180,8 +180,10 @@ def fetch_initial_state_data(user_profile: UserProfile,
state['realm_icon_url'] = realm_icon_url(realm)
state['realm_icon_source'] = realm.icon_source
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_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['realm_bot_domain'] = realm.get_bot_domain()
state['realm_uri'] = realm.uri
diff --git a/zerver/lib/realm_logo.py b/zerver/lib/realm_logo.py
index 9e2dfe089a..5f0d4b5442 100644
--- a/zerver/lib/realm_logo.py
+++ b/zerver/lib/realm_logo.py
@@ -3,11 +3,17 @@ from django.conf import settings
from zerver.lib.upload import upload_backend
from zerver.models import Realm
-def realm_logo_url(realm: Realm) -> str:
- return get_realm_logo_url(realm)
+def realm_logo_url(realm: Realm, night: bool) -> str:
+ return get_realm_logo_url(realm, night)
-def get_realm_logo_url(realm: Realm) -> str:
- if realm.logo_source == 'U':
- return upload_backend.get_realm_logo_url(realm.id, realm.logo_version)
+def get_realm_logo_url(realm: Realm, night: bool) -> str:
+ if not night:
+ 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:
- 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'
diff --git a/zerver/lib/upload.py b/zerver/lib/upload.py
index 6195a5755f..17436cdaaf 100644
--- a/zerver/lib/upload.py
+++ b/zerver/lib/upload.py
@@ -225,10 +225,11 @@ class ZulipUploadBackend:
def get_realm_icon_url(self, realm_id: int, version: int) -> str:
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()
- 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()
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
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]
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()
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
# 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
# ?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:
file_path = user_avatar_path(user_profile)
@@ -671,21 +681,31 @@ class LocalUploadBackend(ZulipUploadBackend):
# ?x=x allows templates to append additional parameters with &s
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')
-
+ 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()
write_local_file(
upload_path,
- 'logo.original',
+ original_file,
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
- 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:
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:
upload_backend.upload_realm_icon_image(user_file, user_profile)
-def upload_logo_image(user_file: File, user_profile: UserProfile) -> None:
- upload_backend.upload_realm_logo_image(user_file, user_profile)
+def upload_logo_image(user_file: File, user_profile: UserProfile, night: bool) -> None:
+ 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:
upload_backend.upload_emoji_image(emoji_file, emoji_file_name, user_profile)
diff --git a/zerver/migrations/0208_add_realm_night_logo_fields.py b/zerver/migrations/0208_add_realm_night_logo_fields.py
new file mode 100644
index 0000000000..5370e18c7d
--- /dev/null
+++ b/zerver/migrations/0208_add_realm_night_logo_fields.py
@@ -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),
+ ),
+ ]
diff --git a/zerver/models.py b/zerver/models.py
index 802f8ca856..283d5e9266 100644
--- a/zerver/models.py
+++ b/zerver/models.py
@@ -316,6 +316,10 @@ class Realm(models.Model):
max_length=1) # type: str
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_EVERYONE,
BOT_CREATION_LIMIT_GENERIC_BOTS,
diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml
index 6bf530ad28..7a681a9629 100644
--- a/zerver/openapi/zulip.yaml
+++ b/zerver/openapi/zulip.yaml
@@ -1759,6 +1759,10 @@ paths:
type: string
description: The URI of the organization's top-left navbar 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:
type: string
description: HTML description of the organization, as
@@ -1771,6 +1775,7 @@ paths:
"msg": "",
"realm_icon": "https://secure.gravatar.com/avatar/62429d594b6ffc712f54aee976a18b44?d=identicon",
"realm_logo": "/static/images/logo/zulip-org-logo.png",
+ "realm_night_logo": "static/images/logo/zulip-org-logo.png",
"realm_description": "
The Zulip development environment default organization. It's great for testing!
",
"email_auth_enabled": true,
"zulip_version": "1.9.0-rc1+git",
diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py
index 2e8e01e607..e09035f9ac 100644
--- a/zerver/tests/test_auth_backends.py
+++ b/zerver/tests/test_auth_backends.py
@@ -1719,6 +1719,7 @@ class FetchAuthBackends(ZulipTestCase):
('realm_description', check_string),
('realm_icon', check_string),
('realm_logo', check_string),
+ ('realm_night_logo', check_string),
])
def test_fetch_auth_backend_format(self) -> None:
diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py
index 5d247c782c..e2c7d4e395 100644
--- a/zerver/tests/test_home.py
+++ b/zerver/tests/test_home.py
@@ -157,6 +157,8 @@ class HomeTest(ZulipTestCase):
"realm_name",
"realm_name_changes_disabled",
"realm_name_in_notifications",
+ "realm_night_logo_source",
+ "realm_night_logo_url",
"realm_non_active_users",
"realm_notifications_stream_id",
"realm_password_auth_enabled",
diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py
index 40308ebded..b2cfb684c5 100644
--- a/zerver/tests/test_upload.py
+++ b/zerver/tests/test_upload.py
@@ -40,6 +40,7 @@ from zerver.lib.users import get_api_key
from zerver.views.upload import upload_file_backend
import urllib
+import ujson
from PIL import Image
from io import StringIO
@@ -799,8 +800,10 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
"/user_avatars/hash-medium.png?x=x")
self.assertEqual(backend.get_realm_icon_url(15, 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")
+ 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"):
backend = S3UploadBackend()
@@ -810,8 +813,10 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase):
"https://bucket.s3.amazonaws.com/hash-medium.png?x=x")
self.assertEqual(backend.get_realm_icon_url(15, 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")
+ 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:
"""
@@ -1250,6 +1255,7 @@ class RealmIconTest(UploadSerializeMixin, ZulipTestCase):
destroy_uploads()
class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
+ night = False
def test_multiple_upload_failure(self) -> None:
"""
@@ -1259,7 +1265,8 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
self.login(self.example_email("iago"))
with get_test_image_file('img.png') as fp1, \
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.")
def test_no_file_upload_failure(self) -> None:
@@ -1268,7 +1275,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
"""
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.")
correct_files = [
@@ -1283,7 +1290,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
def test_no_admin_user_upload(self) -> None:
self.login(self.example_email("hamlet"))
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')
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)
self.login(user_profile.email)
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.')
def test_get_default_logo(self) -> None:
self.login(self.example_email("hamlet"))
realm = get_realm('zulip')
realm.logo_source = Realm.LOGO_DEFAULT
+ realm.night_logo_source = Realm.LOGO_DEFAULT
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']
- 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:
self.login(self.example_email("hamlet"))
-
realm = get_realm('zulip')
realm.logo_source = Realm.LOGO_UPLOADED
+ realm.night_logo_source = Realm.LOGO_UPLOADED
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']
- 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:
"""
A PUT request to /json/realm/logo with a valid file should return a url
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:
# TODO: use self.subTest once we're exclusively on python 3 by uncommenting the line below.
# with self.subTest(fname=fname):
self.login(self.example_email("iago"))
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')
self.assert_json_success(result)
- self.assertIn("logo_url", result.json())
- base = '/user_avatars/%s/realm/logo.png' % (realm.id,)
- url = result.json()['logo_url']
+ self.assertIn(field_name, result.json())
+ base = '/user_avatars/%s/realm/%s' % (realm.id, file_name)
+ url = result.json()[field_name]
self.assertEqual(base, url[:len(base)])
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
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.
"""
@@ -1347,7 +1362,7 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
# with self.subTest(fname=fname):
self.login(self.example_email("iago"))
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?")
@@ -1355,39 +1370,57 @@ class RealmLogoTest(UploadSerializeMixin, ZulipTestCase):
"""
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"))
realm = get_realm('zulip')
realm.logo_source = Realm.LOGO_UPLOADED
+ realm.night_logo_source = Realm.LOGO_UPLOADED
realm.save()
-
- result = self.client_delete("/json/realm/logo")
-
+ result = self.client_delete("/json/realm/logo", {'night': ujson.dumps(self.night)})
self.assert_json_success(result)
- self.assertIn("logo_url", result.json())
+ self.assertIn(field_name, result.json())
realm = get_realm('zulip')
- self.assertEqual(result.json()["logo_url"], realm_logo_url(realm))
- self.assertEqual(realm.logo_source, Realm.LOGO_DEFAULT)
+ self.assertEqual(result.json()[field_name], realm_logo_url(realm, self.night))
+ 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"))
realm = get_realm('zulip')
- logo_version = realm.logo_version
- self.assertEqual(logo_version, 1)
+ if self.night:
+ 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:
- 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')
- 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"))
with get_test_image_file(self.correct_files[0][0]) as fp:
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")
def tearDown(self) -> None:
destroy_uploads()
+class RealmNightLogoTest(RealmLogoTest):
+ # Run the same tests as for RealmLogoTest, just with night mode enabled
+ night = True
+
class LocalStorageTest(UploadSerializeMixin, ZulipTestCase):
def test_file_upload_local(self) -> None:
@@ -1662,23 +1695,29 @@ class S3Test(ZulipTestCase):
self.assertEqual(resized_image, (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE))
@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]
user_profile = self.example_user("hamlet")
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)
+ print(original_key)
image_file.seek(0)
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_image = Image.open(io.BytesIO(resized_data)).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
def test_upload_emoji_image(self) -> None:
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:
for url, error_string in self.test_urls.items():
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)
diff --git a/zerver/views/auth.py b/zerver/views/auth.py
index 302bd90284..9175f9012a 100644
--- a/zerver/views/auth.py
+++ b/zerver/views/auth.py
@@ -872,6 +872,7 @@ def api_get_server_settings(request: HttpRequest) -> HttpResponse:
"realm_name",
"realm_icon",
"realm_logo",
+ "realm_night_logo",
"realm_description"]:
if context[settings_item] is not None:
result[settings_item] = context[settings_item]
diff --git a/zerver/views/home.py b/zerver/views/home.py
index 2942f9d96a..62a6115234 100644
--- a/zerver/views/home.py
+++ b/zerver/views/home.py
@@ -285,6 +285,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
'show_plans': show_plans,
'is_admin': user_profile.is_realm_admin,
'is_guest': user_profile.is_guest,
+ 'night_mode': user_profile.night_mode,
'show_webathena': user_profile.realm.webathena_enabled,
'enable_feedback': settings.ENABLE_FEEDBACK,
'embedded': narrow_stream is not None,
diff --git a/zerver/views/realm_logo.py b/zerver/views/realm_logo.py
index abe1960a48..506d8f0922 100644
--- a/zerver/views/realm_logo.py
+++ b/zerver/views/realm_logo.py
@@ -2,7 +2,11 @@ from django.conf import settings
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
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.lib.actions import do_change_logo_source
from zerver.lib.realm_logo import realm_logo_url
@@ -12,42 +16,54 @@ from zerver.models import Realm, UserProfile
@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:
return json_error(_("Feature unavailable on your current plan."))
if len(request.FILES) != 1:
return json_error(_("You must upload exactly one logo."))
-
logo_file = list(request.FILES.values())[0]
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") % (
settings.MAX_LOGO_FILE_SIZE))
- upload_logo_image(logo_file, user_profile)
- do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_UPLOADED)
- logo_url = realm_logo_url(user_profile.realm)
-
- json_result = dict(
- logo_url=logo_url
- )
+ upload_logo_image(logo_file, user_profile, night)
+ do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_UPLOADED, night)
+ logo_url = realm_logo_url(user_profile.realm, night)
+ if night:
+ json_result = dict(
+ night_logo_url=logo_url
+ )
+ else:
+ json_result = dict(
+ logo_url=logo_url
+ )
return json_success(json_result)
-
@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
# be needed if the URL was cached and it is rewrited
# in any case after next update.
- do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_DEFAULT)
- default_url = realm_logo_url(user_profile.realm)
- json_result = dict(
- logo_url=default_url
- )
+ do_change_logo_source(user_profile.realm, user_profile.realm.LOGO_DEFAULT, night)
+ default_url = realm_logo_url(user_profile.realm, night)
+ if night:
+ json_result = dict(
+ night_logo_url=default_url
+ )
+ else:
+ json_result = dict(
+ logo_url=default_url
+ )
return json_success(json_result)
-
-def get_logo_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
- url = realm_logo_url(user_profile.realm)
+@has_request_variables
+def get_logo_backend(request: HttpRequest, user_profile: UserProfile,
+ 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
# our templates depend on being able to use the ampersand to
diff --git a/zproject/urls.py b/zproject/urls.py
index 615e8f3dd5..377a698485 100644
--- a/zproject/urls.py
+++ b/zproject/urls.py
@@ -98,7 +98,7 @@ v1_api_and_json_patterns = [
'DELETE': 'zerver.views.realm_icon.delete_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,
{'POST': 'zerver.views.realm_logo.upload_logo',
'DELETE': 'zerver.views.realm_logo.delete_logo_backend',