diff --git a/static/js/settings_profile_fields.js b/static/js/settings_profile_fields.js
index c0b9b33d24..fdf12413f3 100644
--- a/static/js/settings_profile_fields.js
+++ b/static/js/settings_profile_fields.js
@@ -6,6 +6,8 @@ var meta = {
loaded: false,
};
+var order = [];
+
function field_type_id_to_string(type_id) {
var name = _.find(page_params.custom_profile_field_types, function (type) {
return type[0] === type_id;
@@ -67,6 +69,26 @@ function delete_choice_row(e) {
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) {
var info = {};
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();
profile_fields_table.find("tr.profile-field-row").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 = {};
if (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,
},
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);
$(".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", ".move-field-up", move_field_up);
+ $("#admin_profile_fields_table").on("click", ".move-field-down", move_field_down);
set_up_choices_field();
};
diff --git a/static/templates/admin_profile_field_list.handlebars b/static/templates/admin_profile_field_list.handlebars
index b7e554ff23..1669b5dd50 100644
--- a/static/templates/admin_profile_field_list.handlebars
+++ b/static/templates/admin_profile_field_list.handlebars
@@ -17,6 +17,12 @@
+
+
{{/if}}
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
index 9dd10b6633..9752248063 100644
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -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.save()
+ field.order = field.id
+ field.save(update_fields=['order'])
notify_realm_custom_profile_fields(realm, 'add')
return field
@@ -4598,6 +4600,17 @@ def try_update_realm_custom_profile_field(realm: Realm, field: CustomProfileFiel
field.save()
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,
data: List[Dict[str, Union[int, Text]]]) -> None:
with transaction.atomic():
diff --git a/zerver/migrations/0167_custom_profile_fields_sort_order.py b/zerver/migrations/0167_custom_profile_fields_sort_order.py
new file mode 100644
index 0000000000..a9f54425ad
--- /dev/null
+++ b/zerver/migrations/0167_custom_profile_fields_sort_order.py
@@ -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),
+ ]
diff --git a/zerver/models.py b/zerver/models.py
index b7fda4aa8c..a6c10a41ad 100644
--- a/zerver/models.py
+++ b/zerver/models.py
@@ -1927,6 +1927,7 @@ class CustomProfileField(models.Model):
field_type = models.PositiveSmallIntegerField(choices=FIELD_TYPE_CHOICES,
default=SHORT_TEXT) # type: int
+ order = models.IntegerField(default=0) # type: int
class Meta:
unique_together = ('realm', 'name')
@@ -1938,13 +1939,14 @@ class CustomProfileField(models.Model):
'type': self.field_type,
'hint': self.hint,
'field_data': self.field_data,
+ 'order': self.order,
}
def __str__(self) -> str:
- return "" % (self.realm, self.name, self.field_type)
+ return "" % (self.realm, self.name, self.field_type, self.order)
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):
user_profile = models.ForeignKey(UserProfile, on_delete=CASCADE) # type: UserProfile
diff --git a/zerver/tests/test_custom_profile_data.py b/zerver/tests/test_custom_profile_data.py
index 11ecd57659..59f7298070 100644
--- a/zerver/tests/test_custom_profile_data.py
+++ b/zerver/tests/test_custom_profile_data.py
@@ -3,8 +3,9 @@
from typing import Union, List, Dict, Text, Any
from mock import patch
-from zerver.lib.actions import try_add_realm_custom_profile_field, \
- do_update_user_custom_profile_data, do_remove_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, \
+ try_reorder_realm_custom_profile_fields
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import CustomProfileField, \
custom_profile_fields_for_realm, get_realm
@@ -22,8 +23,23 @@ class CustomProfileFieldTest(ZulipTestCase):
content = result.json()
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:
self.login(self.example_email("iago"))
+ realm = get_realm('zulip')
data = {"name": u"Phone", "field_type": "text id"} # type: Dict[str, Any]
result = self.client_post("/json/realm/profile_fields", info=data)
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)
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)
self.assert_json_error(result,
u'A field with that name already exists.')
@@ -236,6 +255,63 @@ class CustomProfileFieldTest(ZulipTestCase):
{'data': ujson.dumps([{"id": field.id, "value": new_value}])})
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:
self.login(self.example_email("iago"))
data = [{'id': 1234, 'value': '12'}]
diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py
index 45bb4dadd7..6abf06b533 100644
--- a/zerver/tests/test_events.py
+++ b/zerver/tests/test_events.py
@@ -918,6 +918,7 @@ class EventsRegisterTest(ZulipTestCase):
('name', check_string),
('hint', check_string),
('field_data', check_string),
+ ('order', check_int),
]))),
])
diff --git a/zerver/views/custom_profile_fields.py b/zerver/views/custom_profile_fields.py
index 088310f254..750e2833d9 100644
--- a/zerver/views/custom_profile_fields.py
+++ b/zerver/views/custom_profile_fields.py
@@ -13,7 +13,8 @@ from zerver.lib.request import has_request_variables, REQ
from zerver.lib.actions import (try_add_realm_custom_profile_field,
do_remove_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.types import ProfileFieldData
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_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
@has_request_variables
def update_user_custom_profile_data(
diff --git a/zproject/urls.py b/zproject/urls.py
index dc6b186574..57686e9d26 100644
--- a/zproject/urls.py
+++ b/zproject/urls.py
@@ -105,6 +105,7 @@ v1_api_and_json_patterns = [
# realm/profile_fields -> zerver.views.custom_profile_fields
url(r'^realm/profile_fields$', rest_dispatch,
{'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'}),
url(r'^realm/profile_fields/(?P\d+)$', rest_dispatch,
{'PATCH': 'zerver.views.custom_profile_fields.update_realm_custom_profile_field',