mirror of
https://github.com/zulip/zulip.git
synced 2026-06-21 21:32:29 +08:00
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:
parent
5fdc2e5161
commit
6b1d724f5c
@ -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:
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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')
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)),
|
||||
])),
|
||||
])
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user