From 6b1d724f5ce4e2c8ec319b7ce07060a5c669cfe5 Mon Sep 17 00:00:00 2001 From: "Hemanth V. Alluri" Date: Tue, 6 Nov 2018 14:35:31 +0530 Subject: [PATCH] zerver: Add bugdown rendering for text custom profile fields. This is the first step of letting users use Zulip markdown in their SHORT_TEXT and LONG_TEXT custom profile fields, so that they can include emphasis, links, etc. This doesn't include any frontend logic yet, however. --- zerver/lib/actions.py | 25 ++++++---- ..._customprofilefieldvalue_rendered_value.py | 20 ++++++++ zerver/models.py | 15 +++++- zerver/tests/test_custom_profile_data.py | 46 +++++++++++++++++-- zerver/tests/test_events.py | 4 +- zerver/views/custom_profile_fields.py | 1 + 6 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 zerver/migrations/0192_customprofilefieldvalue_rendered_value.py diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 48a8f8740a..fd75226f85 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -17,7 +17,8 @@ from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat, \ from zerver.lib.bugdown import ( version as bugdown_version, - url_embed_preview_enabled_for_realm + url_embed_preview_enabled_for_realm, + convert as bugdown_convert ) from zerver.lib.addressee import Addressee from zerver.lib.bot_config import ( @@ -4945,12 +4946,14 @@ def try_reorder_realm_custom_profile_fields(realm: Realm, order: List[int]) -> N def notify_user_update_custom_profile_data(user_profile: UserProfile, field: Dict[str, Union[int, str, List[int], None]]) -> None: + data = dict(id=field['id']) if field['type'] == CustomProfileField.USER: - field_value = ujson.dumps(field['value']) # type: Union[int, str, List[int], None] + data["value"] = ujson.dumps(field['value']) else: - field_value = field['value'] - payload = dict(user_id=user_profile.id, custom_profile_field=dict(id=field['id'], - value=field_value)) + data['value'] = field['value'] + if field['rendered_value']: + data['rendered_value'] = field['rendered_value'] + payload = dict(user_id=user_profile.id, custom_profile_field=data) event = dict(type="realm_user", op="update", person=payload) send_event(user_profile.realm, event, active_user_ids(user_profile.realm.id)) @@ -4958,13 +4961,19 @@ def do_update_user_custom_profile_data(user_profile: UserProfile, data: List[Dict[str, Union[int, str, List[int]]]]) -> None: with transaction.atomic(): for field in data: - field_value, created = CustomProfileFieldValue.objects.update_or_create( + field_value, created = CustomProfileFieldValue.objects.get_or_create( user_profile=user_profile, - field_id=field['id'], - defaults={'value': field['value']}) + field_id=field['id']) + field_value.value = field['value'] + if field_value.field.is_renderable(): + field_value.rendered_value = bugdown_convert(str(field['value'])) + field_value.save(update_fields=['value', 'rendered_value']) + else: + field_value.save(update_fields=['value']) notify_user_update_custom_profile_data(user_profile, { "id": field_value.field_id, "value": field_value.value, + "rendered_value": field_value.rendered_value, "type": field_value.field.field_type}) def do_send_create_user_group_event(user_group: UserGroup, members: List[UserProfile]) -> None: diff --git a/zerver/migrations/0192_customprofilefieldvalue_rendered_value.py b/zerver/migrations/0192_customprofilefieldvalue_rendered_value.py new file mode 100644 index 0000000000..b74f2ce366 --- /dev/null +++ b/zerver/migrations/0192_customprofilefieldvalue_rendered_value.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2018-11-14 12:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zerver', '0191_realm_seat_limit'), + ] + + operations = [ + migrations.AddField( + model_name='customprofilefieldvalue', + name='rendered_value', + field=models.TextField(default=None, null=True), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index f83176c096..eb0496b288 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -857,10 +857,14 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): @property def profile_data(self) -> ProfileData: values = CustomProfileFieldValue.objects.filter(user_profile=self) - user_data = {v.field_id: v.value for v in values} + user_data = {v.field_id: {"value": v.value, "rendered_value": v.rendered_value} for v in values} data = [] # type: ProfileData for field in custom_profile_fields_for_realm(self.realm_id): - value = user_data.get(field.id, None) + field_values = user_data.get(field.id, None) + if field_values: + value, rendered_value = field_values.get("value"), field_values.get("rendered_value") + else: + value, rendered_value = None, None field_type = field.field_type if value is not None: converter = field.FIELD_CONVERTERS[field_type] @@ -870,6 +874,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin): for k, v in field.as_dict().items(): field_data[k] = v field_data['value'] = value + field_data['rendered_value'] = rendered_value data.append(field_data) return data @@ -2387,6 +2392,11 @@ class CustomProfileField(models.Model): 'order': self.order, } + def is_renderable(self) -> bool: + if self.field_type in [CustomProfileField.SHORT_TEXT, CustomProfileField.LONG_TEXT]: + return True + return False + def __str__(self) -> str: return "" % (self.realm, self.name, self.field_type, self.order) @@ -2397,6 +2407,7 @@ class CustomProfileFieldValue(models.Model): user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile field = models.ForeignKey(CustomProfileField, on_delete=CASCADE) # type: CustomProfileField value = models.TextField() # type: str + rendered_value = models.TextField(null=True, default=None) # type: Optional[str] class Meta: unique_together = ('user_profile', 'field') diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py index 49b0e4c90b..b97d1a5fc6 100644 --- a/zerver/tests/test_custom_profile_data.py +++ b/zerver/tests/test_custom_profile_data.py @@ -7,6 +7,7 @@ from zerver.lib.actions import get_realm, try_add_realm_custom_profile_field, \ do_update_user_custom_profile_data, do_remove_realm_custom_profile_field, \ try_reorder_realm_custom_profile_fields from zerver.lib.test_classes import ZulipTestCase +from zerver.lib.bugdown import convert as bugdown_convert from zerver.models import CustomProfileField, \ custom_profile_fields_for_realm, get_realm, CustomProfileFieldValue import ujson @@ -409,9 +410,9 @@ class CustomProfileFieldTest(ZulipTestCase): self.login(self.example_email("iago")) realm = get_realm('zulip') fields = [ - ('Phone number', 'short text data'), - ('Biography', 'long text data'), - ('Favorite food', 'short text data'), + ('Phone number', '*short* text data'), + ('Biography', '~~short~~ **long** text data'), + ('Favorite food', 'long short text data'), ('Favorite editor', 'vim'), ('Birthday', '1909-3-5'), ('GitHub profile', 'https://github.com/ABC'), @@ -425,6 +426,7 @@ class CustomProfileFieldTest(ZulipTestCase): data.append({ 'id': field.id, 'value': value, + 'field': field, }) # Update value of field @@ -434,9 +436,16 @@ class CustomProfileFieldTest(ZulipTestCase): iago = self.example_user('iago') expected_value = {f['id']: f['value'] for f in data} + expected_rendered_value = {} # type: Dict[Union[int, float, str, None], Union[str, None]] + for f in data: + if f['field'].is_renderable(): + expected_rendered_value[f['id']] = bugdown_convert(f['value']) + else: + expected_rendered_value[f['id']] = None for field_dict in iago.profile_data: self.assertEqual(field_dict['value'], expected_value[field_dict['id']]) + self.assertEqual(field_dict['rendered_value'], expected_rendered_value[field_dict['id']]) for k in ['id', 'type', 'name', 'field_data']: self.assertIn(k, field_dict) @@ -486,3 +495,34 @@ class CustomProfileFieldTest(ZulipTestCase): self.assertFalse(self.custom_field_exists_in_realm(field.id)) self.assertEqual(user_profile.customprofilefieldvalue_set.count(), self.original_count - 1) + + def test_null_value_and_rendered_value(self) -> None: + self.login(self.example_email("iago")) + realm = get_realm("zulip") + + quote = try_add_realm_custom_profile_field( + realm=realm, + name="Quote", + hint="Saying or phrase which you known for.", + field_type=CustomProfileField.SHORT_TEXT + ) + + iago = self.example_user("iago") + iago_profile_quote = iago.profile_data[-1] + value = iago_profile_quote["value"] + rendered_value = iago_profile_quote["rendered_value"] + self.assertIsNone(value) + self.assertIsNone(rendered_value) + + update_dict = { + "id": quote.id, + "value": "***beware*** of jealousy..." + } + do_update_user_custom_profile_data(iago, [update_dict]) + + iago_profile_quote = self.example_user("iago").profile_data[-1] + value = iago_profile_quote["value"] + rendered_value = iago_profile_quote["rendered_value"] + self.assertIsNotNone(value) + self.assertIsNotNone(rendered_value) + self.assertEqual("

beware of jealousy...

", rendered_value) diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 3f2ebc8003..0a341e25df 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -1071,10 +1071,10 @@ class EventsRegisterTest(ZulipTestCase): ('op', equals('update')), ('person', check_dict_only([ ('user_id', check_int), - ('custom_profile_field', check_dict_only([ + ('custom_profile_field', check_dict([ ('id', check_int), ('value', check_none_or(check_string)), - ])), + ], _allow_only_listed_keys=False)), ])), ]) diff --git a/zerver/views/custom_profile_fields.py b/zerver/views/custom_profile_fields.py index b889f7299a..ce9045b9f2 100644 --- a/zerver/views/custom_profile_fields.py +++ b/zerver/views/custom_profile_fields.py @@ -144,6 +144,7 @@ def remove_user_custom_profile_data(request: HttpRequest, user_profile: UserProf field_value.delete() notify_user_update_custom_profile_data(user_profile, {'id': field_id, 'value': None, + 'rendered_value': None, 'type': field.field_type}) return json_success()