mirror of https://github.com/zulip/zulip.git
custom_profile_fields: Support changing the sort order of the fields.
Tweaked by tabbott for variable naming and the URL. Closes #8879.
This commit is contained in:
parent
acf00c1130
commit
f4f64243dd
|
@ -6,6 +6,8 @@ var meta = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var order = [];
|
||||||
|
|
||||||
function field_type_id_to_string(type_id) {
|
function field_type_id_to_string(type_id) {
|
||||||
var name = _.find(page_params.custom_profile_field_types, function (type) {
|
var name = _.find(page_params.custom_profile_field_types, function (type) {
|
||||||
return type[0] === type_id;
|
return type[0] === type_id;
|
||||||
|
@ -67,6 +69,26 @@ function delete_choice_row(e) {
|
||||||
row.remove();
|
row.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function move_field(e, btn, direction) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var button_id = parseInt(btn.attr('data-profile-field-id'), 10);
|
||||||
|
var button_index = order.indexOf(button_id);
|
||||||
|
order[button_index] = order[button_index + direction];
|
||||||
|
order[button_index + direction] = button_id;
|
||||||
|
settings_ui.do_settings_change(channel.patch, "/json/realm/profile_fields",
|
||||||
|
{order: JSON.stringify(order)},
|
||||||
|
$('#admin-profile-field-status').expectOne());
|
||||||
|
}
|
||||||
|
|
||||||
|
function move_field_up(e) {
|
||||||
|
move_field(e, $(this), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function move_field_down(e) {
|
||||||
|
move_field(e, $(this), 1);
|
||||||
|
}
|
||||||
|
|
||||||
function get_profile_field_info(id) {
|
function get_profile_field_info(id) {
|
||||||
var info = {};
|
var info = {};
|
||||||
info.row = $("tr.profile-field-row[data-profile-field-id='" + id + "']");
|
info.row = $("tr.profile-field-row[data-profile-field-id='" + id + "']");
|
||||||
|
@ -123,7 +145,9 @@ exports.populate_profile_fields = function (profile_fields_data) {
|
||||||
var profile_fields_table = $("#admin_profile_fields_table").expectOne();
|
var profile_fields_table = $("#admin_profile_fields_table").expectOne();
|
||||||
profile_fields_table.find("tr.profile-field-row").remove(); // Clear all rows.
|
profile_fields_table.find("tr.profile-field-row").remove(); // Clear all rows.
|
||||||
profile_fields_table.find("tr.profile-field-form").remove(); // Clear all rows.
|
profile_fields_table.find("tr.profile-field-form").remove(); // Clear all rows.
|
||||||
_.each(profile_fields_data, function (profile_field) {
|
order = [];
|
||||||
|
_.each(profile_fields_data, function (profile_field, index) {
|
||||||
|
order.push(profile_field.id);
|
||||||
var field_data = {};
|
var field_data = {};
|
||||||
if (profile_field.field_data !== "") {
|
if (profile_field.field_data !== "") {
|
||||||
field_data = JSON.parse(profile_field.field_data);
|
field_data = JSON.parse(profile_field.field_data);
|
||||||
|
@ -161,6 +185,8 @@ exports.populate_profile_fields = function (profile_fields_data) {
|
||||||
is_choice_field: is_choice_field,
|
is_choice_field: is_choice_field,
|
||||||
},
|
},
|
||||||
can_modify: page_params.is_admin,
|
can_modify: page_params.is_admin,
|
||||||
|
first: index === 0,
|
||||||
|
last: index === _.size(profile_fields_data) - 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -199,7 +225,8 @@ exports.set_up = function () {
|
||||||
$('#admin_profile_fields_table').on('click', '.delete', delete_profile_field);
|
$('#admin_profile_fields_table').on('click', '.delete', delete_profile_field);
|
||||||
$(".organization").on("submit", "form.admin-profile-field-form", create_profile_field);
|
$(".organization").on("submit", "form.admin-profile-field-form", create_profile_field);
|
||||||
$("#admin_profile_fields_table").on("click", ".open-edit-form", open_edit_form);
|
$("#admin_profile_fields_table").on("click", ".open-edit-form", open_edit_form);
|
||||||
|
$("#admin_profile_fields_table").on("click", ".move-field-up", move_field_up);
|
||||||
|
$("#admin_profile_fields_table").on("click", ".move-field-down", move_field_down);
|
||||||
set_up_choices_field();
|
set_up_choices_field();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,12 @@
|
||||||
<button class="button rounded small btn-warning open-edit-form" title="{{t 'Edit' }}" data-profile-field-id="{{id}}">
|
<button class="button rounded small btn-warning open-edit-form" title="{{t 'Edit' }}" data-profile-field-id="{{id}}">
|
||||||
<i class="icon-vector-pencil"></i>
|
<i class="icon-vector-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="button rounded small btn-warning move-field-up" {{#if ../first}}disabled="disabled"{{/if}} title="{{t 'Up' }}" data-profile-field-id="{{id}}">
|
||||||
|
<i class="icon-vector-chevron-up"></i>
|
||||||
|
</button>
|
||||||
|
<button class="button rounded small btn-warning move-field-down" {{#if ../last}}disabled="disabled"{{/if}} title="{{t 'Down' }}" data-profile-field-id="{{id}}">
|
||||||
|
<i class="icon-vector-chevron-down"></i>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -4577,6 +4577,8 @@ def try_add_realm_custom_profile_field(realm: Realm, name: Text, field_type: int
|
||||||
field.field_data = ujson.dumps(field_data or {})
|
field.field_data = ujson.dumps(field_data or {})
|
||||||
|
|
||||||
field.save()
|
field.save()
|
||||||
|
field.order = field.id
|
||||||
|
field.save(update_fields=['order'])
|
||||||
notify_realm_custom_profile_fields(realm, 'add')
|
notify_realm_custom_profile_fields(realm, 'add')
|
||||||
return field
|
return field
|
||||||
|
|
||||||
|
@ -4598,6 +4600,17 @@ def try_update_realm_custom_profile_field(realm: Realm, field: CustomProfileFiel
|
||||||
field.save()
|
field.save()
|
||||||
notify_realm_custom_profile_fields(realm, 'update')
|
notify_realm_custom_profile_fields(realm, 'update')
|
||||||
|
|
||||||
|
def try_reorder_realm_custom_profile_fields(realm: Realm, order: List[int]) -> None:
|
||||||
|
order_mapping = dict((_[1], _[0]) for _ in enumerate(order))
|
||||||
|
fields = CustomProfileField.objects.filter(realm=realm)
|
||||||
|
for field in fields:
|
||||||
|
if field.id not in order_mapping:
|
||||||
|
raise JsonableError(_("Invalid order mapping."))
|
||||||
|
for field in fields:
|
||||||
|
field.order = order_mapping[field.id]
|
||||||
|
field.save(update_fields=['order'])
|
||||||
|
notify_realm_custom_profile_fields(realm, 'update')
|
||||||
|
|
||||||
def do_update_user_custom_profile_data(user_profile: UserProfile,
|
def do_update_user_custom_profile_data(user_profile: UserProfile,
|
||||||
data: List[Dict[str, Union[int, Text]]]) -> None:
|
data: List[Dict[str, Union[int, Text]]]) -> None:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.11 on 2018-04-08 15:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.models import F
|
||||||
|
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
|
||||||
|
def migrate_set_order_value(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
|
||||||
|
CustomProfileField = apps.get_model('zerver', 'CustomProfileField')
|
||||||
|
CustomProfileField.objects.all().update(order=F('id'))
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0166_add_url_to_profile_field'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customprofilefield',
|
||||||
|
name='order',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_set_order_value),
|
||||||
|
]
|
|
@ -1927,6 +1927,7 @@ class CustomProfileField(models.Model):
|
||||||
|
|
||||||
field_type = models.PositiveSmallIntegerField(choices=FIELD_TYPE_CHOICES,
|
field_type = models.PositiveSmallIntegerField(choices=FIELD_TYPE_CHOICES,
|
||||||
default=SHORT_TEXT) # type: int
|
default=SHORT_TEXT) # type: int
|
||||||
|
order = models.IntegerField(default=0) # type: int
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('realm', 'name')
|
unique_together = ('realm', 'name')
|
||||||
|
@ -1938,13 +1939,14 @@ class CustomProfileField(models.Model):
|
||||||
'type': self.field_type,
|
'type': self.field_type,
|
||||||
'hint': self.hint,
|
'hint': self.hint,
|
||||||
'field_data': self.field_data,
|
'field_data': self.field_data,
|
||||||
|
'order': self.order,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "<CustomProfileField: %s %s %s>" % (self.realm, self.name, self.field_type)
|
return "<CustomProfileField: %s %s %s %d>" % (self.realm, self.name, self.field_type, self.order)
|
||||||
|
|
||||||
def custom_profile_fields_for_realm(realm_id: int) -> List[CustomProfileField]:
|
def custom_profile_fields_for_realm(realm_id: int) -> List[CustomProfileField]:
|
||||||
return CustomProfileField.objects.filter(realm=realm_id).order_by('name')
|
return CustomProfileField.objects.filter(realm=realm_id).order_by('order')
|
||||||
|
|
||||||
class CustomProfileFieldValue(models.Model):
|
class CustomProfileFieldValue(models.Model):
|
||||||
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile
|
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
from typing import Union, List, Dict, Text, Any
|
from typing import Union, List, Dict, Text, Any
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
from zerver.lib.actions import try_add_realm_custom_profile_field, \
|
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
|
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.test_classes import ZulipTestCase
|
||||||
from zerver.models import CustomProfileField, \
|
from zerver.models import CustomProfileField, \
|
||||||
custom_profile_fields_for_realm, get_realm
|
custom_profile_fields_for_realm, get_realm
|
||||||
|
@ -22,8 +23,23 @@ class CustomProfileFieldTest(ZulipTestCase):
|
||||||
content = result.json()
|
content = result.json()
|
||||||
self.assertEqual(len(content["custom_fields"]), self.original_count)
|
self.assertEqual(len(content["custom_fields"]), self.original_count)
|
||||||
|
|
||||||
|
def test_list_order(self) -> None:
|
||||||
|
self.login(self.example_email("iago"))
|
||||||
|
realm = get_realm('zulip')
|
||||||
|
order = (
|
||||||
|
CustomProfileField.objects.filter(realm=realm)
|
||||||
|
.order_by('-order')
|
||||||
|
.values_list('order', flat=True)
|
||||||
|
)
|
||||||
|
try_reorder_realm_custom_profile_fields(realm, order)
|
||||||
|
result = self.client_get("/json/realm/profile_fields")
|
||||||
|
content = result.json()
|
||||||
|
self.assertListEqual(content["custom_fields"],
|
||||||
|
sorted(content["custom_fields"], key=lambda x: -x["id"]))
|
||||||
|
|
||||||
def test_create(self) -> None:
|
def test_create(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
|
realm = get_realm('zulip')
|
||||||
data = {"name": u"Phone", "field_type": "text id"} # type: Dict[str, Any]
|
data = {"name": u"Phone", "field_type": "text id"} # type: Dict[str, Any]
|
||||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||||
self.assert_json_error(result, u'Argument "field_type" is not valid JSON.')
|
self.assert_json_error(result, u'Argument "field_type" is not valid JSON.')
|
||||||
|
@ -50,6 +66,9 @@ class CustomProfileFieldTest(ZulipTestCase):
|
||||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
field = CustomProfileField.objects.get(name="Phone", realm=realm)
|
||||||
|
self.assertEqual(field.id, field.order)
|
||||||
|
|
||||||
result = self.client_post("/json/realm/profile_fields", info=data)
|
result = self.client_post("/json/realm/profile_fields", info=data)
|
||||||
self.assert_json_error(result,
|
self.assert_json_error(result,
|
||||||
u'A field with that name already exists.')
|
u'A field with that name already exists.')
|
||||||
|
@ -236,6 +255,63 @@ class CustomProfileFieldTest(ZulipTestCase):
|
||||||
{'data': ujson.dumps([{"id": field.id, "value": new_value}])})
|
{'data': ujson.dumps([{"id": field.id, "value": new_value}])})
|
||||||
self.assert_json_error(result, error_msg)
|
self.assert_json_error(result, error_msg)
|
||||||
|
|
||||||
|
def test_reorder(self) -> None:
|
||||||
|
self.login(self.example_email("iago"))
|
||||||
|
realm = get_realm('zulip')
|
||||||
|
order = (
|
||||||
|
CustomProfileField.objects.filter(realm=realm)
|
||||||
|
.order_by('-order')
|
||||||
|
.values_list('order', flat=True)
|
||||||
|
)
|
||||||
|
result = self.client_patch("/json/realm/profile_fields",
|
||||||
|
info={'order': ujson.dumps(order)})
|
||||||
|
self.assert_json_success(result)
|
||||||
|
fields = CustomProfileField.objects.filter(realm=realm).order_by('order')
|
||||||
|
for field in fields:
|
||||||
|
self.assertEqual(field.id, order[field.order])
|
||||||
|
|
||||||
|
def test_reorder_duplicates(self) -> None:
|
||||||
|
self.login(self.example_email("iago"))
|
||||||
|
realm = get_realm('zulip')
|
||||||
|
order = (
|
||||||
|
CustomProfileField.objects.filter(realm=realm)
|
||||||
|
.order_by('-order')
|
||||||
|
.values_list('order', flat=True)
|
||||||
|
)
|
||||||
|
order = list(order)
|
||||||
|
order.append(4)
|
||||||
|
result = self.client_patch("/json/realm/profile_fields",
|
||||||
|
info={'order': ujson.dumps(order)})
|
||||||
|
self.assert_json_success(result)
|
||||||
|
fields = CustomProfileField.objects.filter(realm=realm).order_by('order')
|
||||||
|
for field in fields:
|
||||||
|
self.assertEqual(field.id, order[field.order])
|
||||||
|
|
||||||
|
def test_reorder_unauthorized(self) -> None:
|
||||||
|
self.login(self.example_email("hamlet"))
|
||||||
|
realm = get_realm('zulip')
|
||||||
|
order = (
|
||||||
|
CustomProfileField.objects.filter(realm=realm)
|
||||||
|
.order_by('-order')
|
||||||
|
.values_list('order', flat=True)
|
||||||
|
)
|
||||||
|
result = self.client_patch("/json/realm/profile_fields",
|
||||||
|
info={'order': ujson.dumps(order)})
|
||||||
|
self.assert_json_error(result, "Must be an organization administrator")
|
||||||
|
|
||||||
|
def test_reorder_invalid(self) -> None:
|
||||||
|
self.login(self.example_email("iago"))
|
||||||
|
order = [100, 200, 300]
|
||||||
|
result = self.client_patch("/json/realm/profile_fields",
|
||||||
|
info={'order': ujson.dumps(order)})
|
||||||
|
self.assert_json_error(
|
||||||
|
result, u'Invalid order mapping.')
|
||||||
|
order = [1, 2]
|
||||||
|
result = self.client_patch("/json/realm/profile_fields",
|
||||||
|
info={'order': ujson.dumps(order)})
|
||||||
|
self.assert_json_error(
|
||||||
|
result, u'Invalid order mapping.')
|
||||||
|
|
||||||
def test_update_invalid_field(self) -> None:
|
def test_update_invalid_field(self) -> None:
|
||||||
self.login(self.example_email("iago"))
|
self.login(self.example_email("iago"))
|
||||||
data = [{'id': 1234, 'value': '12'}]
|
data = [{'id': 1234, 'value': '12'}]
|
||||||
|
|
|
@ -918,6 +918,7 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
('name', check_string),
|
('name', check_string),
|
||||||
('hint', check_string),
|
('hint', check_string),
|
||||||
('field_data', check_string),
|
('field_data', check_string),
|
||||||
|
('order', check_int),
|
||||||
]))),
|
]))),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ from zerver.lib.request import has_request_variables, REQ
|
||||||
from zerver.lib.actions import (try_add_realm_custom_profile_field,
|
from zerver.lib.actions import (try_add_realm_custom_profile_field,
|
||||||
do_remove_realm_custom_profile_field,
|
do_remove_realm_custom_profile_field,
|
||||||
try_update_realm_custom_profile_field,
|
try_update_realm_custom_profile_field,
|
||||||
do_update_user_custom_profile_data)
|
do_update_user_custom_profile_data,
|
||||||
|
try_reorder_realm_custom_profile_fields)
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.lib.types import ProfileFieldData
|
from zerver.lib.types import ProfileFieldData
|
||||||
from zerver.lib.validator import (check_dict, check_list, check_int,
|
from zerver.lib.validator import (check_dict, check_list, check_int,
|
||||||
|
@ -107,6 +108,14 @@ def update_realm_custom_profile_field(request: HttpRequest, user_profile: UserPr
|
||||||
return json_error(_('A field with that name already exists.'))
|
return json_error(_('A field with that name already exists.'))
|
||||||
return json_success()
|
return json_success()
|
||||||
|
|
||||||
|
@require_realm_admin
|
||||||
|
@has_request_variables
|
||||||
|
def reorder_realm_custom_profile_fields(request: HttpRequest, user_profile: UserProfile,
|
||||||
|
order: List[int]=REQ(validator=check_list(
|
||||||
|
check_int))) -> HttpResponse:
|
||||||
|
try_reorder_realm_custom_profile_fields(user_profile.realm, order)
|
||||||
|
return json_success()
|
||||||
|
|
||||||
@human_users_only
|
@human_users_only
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def update_user_custom_profile_data(
|
def update_user_custom_profile_data(
|
||||||
|
|
|
@ -105,6 +105,7 @@ v1_api_and_json_patterns = [
|
||||||
# realm/profile_fields -> zerver.views.custom_profile_fields
|
# realm/profile_fields -> zerver.views.custom_profile_fields
|
||||||
url(r'^realm/profile_fields$', rest_dispatch,
|
url(r'^realm/profile_fields$', rest_dispatch,
|
||||||
{'GET': 'zerver.views.custom_profile_fields.list_realm_custom_profile_fields',
|
{'GET': 'zerver.views.custom_profile_fields.list_realm_custom_profile_fields',
|
||||||
|
'PATCH': 'zerver.views.custom_profile_fields.reorder_realm_custom_profile_fields',
|
||||||
'POST': 'zerver.views.custom_profile_fields.create_realm_custom_profile_field'}),
|
'POST': 'zerver.views.custom_profile_fields.create_realm_custom_profile_field'}),
|
||||||
url(r'^realm/profile_fields/(?P<field_id>\d+)$', rest_dispatch,
|
url(r'^realm/profile_fields/(?P<field_id>\d+)$', rest_dispatch,
|
||||||
{'PATCH': 'zerver.views.custom_profile_fields.update_realm_custom_profile_field',
|
{'PATCH': 'zerver.views.custom_profile_fields.update_realm_custom_profile_field',
|
||||||
|
|
Loading…
Reference in New Issue