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.
This commit is contained in:
Hemanth V. Alluri 2018-11-06 14:35:31 +05:30 committed by Tim Abbott
parent 5fdc2e5161
commit 6b1d724f5c
6 changed files with 96 additions and 15 deletions

View File

@ -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:

View File

@ -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),
),
]

View File

@ -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 "<CustomProfileField: %s %s %s %d>" % (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')

View File

@ -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("<p><strong><em>beware</em></strong> of jealousy...</p>", rendered_value)

View File

@ -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)),
])),
])

View File

@ -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()