Improve importing and sorting of users

* Add is_present field for csv import of users.
* Refactor JS functions get_full_name and get_short_name (Fixes #2136).
  - Show participant number in get_full_name() output.
  - Sort users by first or last name.
  - Extend config option to sort users.
  - Mark unused Python methods get_short_name and get_full_name.
This commit is contained in:
Emanuel Schütze 2016-11-04 13:26:44 +01:00
parent 4c08bca34a
commit 5b544ceed2
9 changed files with 89 additions and 247 deletions

View File

@ -442,7 +442,10 @@ angular.module('OpenSlidesApp.agenda.site', [
!item.speaker_list_closed &&
$.inArray(operator.user.id, nextUsers) == -1);
case 'remove':
if (operator.user) {
return ($.inArray(operator.user.id, nextUsers) != -1);
}
return false;
case 'removeAll':
return (operator.hasPerms('agenda.can_manage') &&
$scope.speakers.length > 0);

View File

@ -10,14 +10,16 @@ def get_config_variables():
"""
# Sorting
yield ConfigVariable(
name='users_sort_users_by_first_name',
default_value=False,
input_type='boolean',
label='Sort users by first name',
help_text='Disable for sorting by last name',
name='users_sort_by',
default_value='firstName',
input_type='choice',
label='Sort name of participants by',
choices=(
{'value': 'firstName', 'display_name': 'First name'},
{'value': 'lastName', 'display_name': 'Last name'}),
weight=510,
group='Participants',
subgroup='Sorting')
subgroup='General')
# PDF

View File

@ -13,7 +13,6 @@ from django.db.models import Q
from openslides.utils.search import user_name_helper
from ..core.config import config
from ..utils.models import RESTModelMixin
from .access_permissions import UserAccessPermissions
@ -175,38 +174,13 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
ordering = ('last_name', 'first_name', 'username', )
def __str__(self):
return self.get_full_name()
def get_full_name(self):
"""
Returns a long form of the name.
E. g.: * Dr. Max Mustermann (Villingen)
* Professor Dr. Enders, Christoph (Leipzig)
"""
structure = '(%s)' % self.structure_level if self.structure_level else ''
return ' '.join((self.title, self.get_short_name(), structure)).strip()
def get_short_name(self, sort_by_first_name=None):
"""
Returns only the name of the user.
E. g.: * Max Mustermann
* Enders, Christoph
"""
# Strip white spaces from the name parts
first_name = self.first_name.strip()
last_name = self.last_name.strip()
# The user has a last_name and a first_name
if first_name and last_name:
if sort_by_first_name is None:
sort_by_first_name = config['users_sort_users_by_first_name']
if sort_by_first_name:
name = ' '.join((first_name, last_name))
else:
name = ', '.join((last_name, first_name))
name = ' '.join((self.first_name, self.last_name))
# The user has only a first_name or a last_name or no name
else:
name = first_name or last_name or self.username
@ -214,6 +188,14 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
# Return result
return name
# TODO: remove this function after PR#2476 is merged. (see Issue#2594)
def get_full_name(self):
return ''
# TODO: remove this function after PR#2476 is merged. (see Issue#2594)
def get_short_name(self):
return ''
def get_search_index_string(self):
"""
Returns a string that can be indexed for the search.

View File

@ -24,10 +24,7 @@ def users_to_pdf(pdf):
Create a list of all users as PDF.
"""
data = [['#', _('Name'), _('Structure level'), _('Group')]]
if config['users_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
counter = 0
for user in User.objects.all().order_by(sort):
counter += 1
@ -73,10 +70,7 @@ def users_passwords_to_pdf(pdf):
users_pdf_welcometitle = config["users_pdf_welcometitle"]
users_pdf_welcometext = config["users_pdf_welcometext"]
if config['users_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
qrcode_size = 2 * cm
# qrcode for system url
qrcode_url = QrCodeWidget(users_pdf_url)

View File

@ -69,7 +69,8 @@ angular.module('OpenSlidesApp.users', [])
'Group',
'jsDataModel',
'gettext',
function(DS, Group, jsDataModel, gettext) {
'Config',
function(DS, Group, jsDataModel, gettext, Config) {
var name = 'users/user';
return DS.defineResource({
name: name,
@ -88,41 +89,54 @@ angular.module('OpenSlidesApp.users', [])
getResourceName: function () {
return name;
},
/*
* Returns a short form of the name.
*
* Example:
* - Dr. Max Mustermann
* - Professor Dr. Enders, Christoph
*/
get_short_name: function() {
// should be the same as in the python user model.
var title = _.trim(this.title),
firstName = _.trim(this.first_name),
lastName = _.trim(this.last_name),
name = '';
if (title) {
name = title + ' ';
}
if (firstName && lastName) {
name += [firstName, lastName].join(' ');
if (Config.get('users_sort_by').value == 'lastName') {
if (lastName && firstName) {
name += [lastName, firstName].join(', ');
} else {
name += firstName || lastName || this.username;
name += lastName || firstName;
}
} else {
name += [firstName, lastName].join(' ');
}
if (title !== '') {
name = title + ' ' + name;
}
return name;
},
/*
* Returns a long form of the name.
*
* Example:
* - Dr. Max Mustermann (Villingen)
* - Professor Dr. Enders, Christoph (Leipzig)
*/
get_full_name: function() {
// should be the same as in the python user model.
var title = _.trim(this.title),
firstName = _.trim(this.first_name),
lastName = _.trim(this.last_name),
var name = this.get_short_name(),
structure_level = _.trim(this.structure_level),
name = '';
number = _.trim(this.number),
addition = [];
if (title) {
name = title + ' ';
}
if (firstName && lastName) {
name += [firstName, lastName].join(' ');
} else {
name += firstName || lastName || this.username;
// addition: add number and structure level
if (number) {
addition.push(number);
}
if (structure_level) {
name += " (" + structure_level + ")";
addition.push(structure_level);
}
if (addition.length > 0) {
name += ' (' + addition.join(', ') + ')';
}
return name;
},

View File

@ -897,6 +897,17 @@ angular.module('OpenSlidesApp.users.site', [
} else {
user.is_active = false;
}
// is present
if (user.is_present) {
user.is_present = user.is_present.replace(quotionRe, '$1');
if (user.is_present == '1') {
user.is_present = true;
} else {
user.is_present = false;
}
} else {
user.is_present = false;
}
// is committee
if (user.is_committee) {
user.is_committee = user.is_committee.replace(quotionRe, '$1');
@ -1016,12 +1027,12 @@ angular.module('OpenSlidesApp.users.site', [
var element = document.getElementById('downloadLink');
var csvRows = [
// column header line
['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_committee'],
['title', 'first_name', 'last_name', 'structure_level', 'number', 'groups', 'comment', 'is_active', 'is_present', 'is_committee'],
// example entries
['Dr.', 'Max', 'Mustermann', 'Berlin','1234567890', '"3,4"', 'xyz', '1', ''],
['', 'John', 'Doe', 'Washington','75/99/8-2', '3', 'abc', '1', ''],
['', 'Fred', 'Bloggs', 'London', '', '', '', '', ''],
['', '', 'Executive Board', '', '', '5', '', '', '1'],
['Dr.', 'Max', 'Mustermann', 'Berlin','1234567890', '"3,4"', 'xyz', '1', '1', ''],
['', 'John', 'Doe', 'Washington','75/99/8-2', '3', 'abc', '1', '1', ''],
['', 'Fred', 'Bloggs', 'London', '', '', '', '', '', ''],
['', '', 'Executive Board', '', '', '5', '', '', '', '1'],
];
var csvString = csvRows.join("%0A");
@ -1373,14 +1384,15 @@ angular.module('OpenSlidesApp.users.site', [
gettext('Can manage users');
// config strings in users/config_variables.py
gettext('[Place for your welcome and help text.]');
gettext('Sort users by first name');
gettext('Disable for sorting by last name');
gettext('General');
gettext('Sort name of participants by');
gettext('Participants');
gettext('Sorting');
gettext('First name');
gettext('Last name');
gettext('PDF');
gettext('Welcome to OpenSlides');
gettext('Title for access data and welcome PDF');
gettext('PDF');
gettext('[Place for your welcome and help text.]');
gettext('Help text for access data and welcome PDF');
gettext('System URL');
gettext('Used for QRCode in PDF of access data.');

View File

@ -68,7 +68,7 @@
<h4 translate>Please note:</h4>
<ul class="indentation">
<li><translate>Required comma or semicolon separated values with these column header names in the first row</translate>:<br>
<code>title, first_name, last_name, structure_level, number, groups, comment, is_active, is_committee</code>
<code>title, first_name, last_name, structure_level, number, groups, comment, is_active, is_present, is_committee</code>
<li><translate>Default groups</translate>:
<translate>Delegates</translate> <code>2</code>,
<translate>Staff</translate> <code>3</code>
@ -94,6 +94,7 @@
<th translate>Groups
<th translate>Comment
<th translate>Is active
<th translate>Is present
<th translate>Is committee</th>
<th ng-if="duplicates > 0">
<i class="fa fa-exclamation-triangle text-danger"></i>
@ -155,6 +156,9 @@
<td>
<i class="fa pointer" ng-class="user.is_active ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="user.is_active = !user.is_active"></i>
<td>
<i class="fa pointer" ng-class="user.is_present ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="user.is_present = !user.is_present"></i>
<td>
<i class="fa pointer" ng-class="user.is_committee ? 'fa-check-square-o' : 'fa-square-o'"
ng-click="user.is_committee = !user.is_committee"></i>

View File

@ -97,10 +97,9 @@ def user_name_helper(users):
then the str(users) is returned.
"""
if isinstance(users, list) or isinstance(users, QuerySet):
user_string = " ".join(
user.get_short_name(sort_by_first_name=True) for user in users)
user_string = " ".join(str(user) for user in users)
elif isinstance(users, get_user_model()):
user_string = users.get_short_name(sort_by_first_name=True)
user_string = str(users)
else:
user_string = str(users)
return user_string

View File

@ -1,175 +1,7 @@
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openslides.users.models import User, UserManager
class UserTest(TestCase):
def test_str(self):
"""
Tests, that the str representaion of a User object returns the same as
User.get_full_name().
"""
user = User()
user.get_full_name = MagicMock(return_value='Test Value IJee1yoet1ooGhesh5li')
self.assertEqual(
str(user),
'Test Value IJee1yoet1ooGhesh5li',
"The str representation of User is not user.get_full_name().")
class UserGetFullName(TestCase):
def test_get_full_name_with_structure_level_and_title(self):
"""
Tests that get_full_name returns the write string.
"""
user = User()
user.title = 'test_title'
user.structure_level = 'test_structure_level'
user.get_short_name = MagicMock(return_value='test_short_name')
self.assertEqual(
user.get_full_name(),
'test_title test_short_name (test_structure_level)',
"User.get_full_name() returns wrong string when it has a "
"structure_level and title.")
def test_get_full_name_without_structure_level_and_with_title(self):
"""
Tests that get_full_name returns the write string.
"""
user = User()
user.title = 'test_title'
user.structure_level = ''
user.get_short_name = MagicMock(return_value='test_short_name')
self.assertEqual(
user.get_full_name(),
'test_title test_short_name',
"User.get_full_name() returns wrong string when it has no "
"structure_level but a title.")
def test_get_full_name_without_structure_level_and_without_title(self):
"""
Tests that get_full_name returns the write string.
"""
user = User()
user.title = ''
user.structure_level = ''
user.get_short_name = MagicMock(return_value='test_short_name')
self.assertEqual(
user.get_full_name(),
'test_short_name',
"User.get_full_name() returns wrong string when it has no "
"structure_level and no title.")
class UserGetShortName(TestCase):
def test_get_short_name_sort_first_name_only_first_name(self):
"""
Tests the output of get_short_name.
"""
user = User()
user.first_name = 'test_first_name'
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = True
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_first_name',
"User.get_short_name() returns wrong string when it has only a "
"first_name and is sorted by first_name.")
def test_get_short_name_sort_first_name_both_names(self):
"""
Tests the output of get_short_name.
"""
user = User()
user.first_name = 'test_first_name'
user.last_name = 'test_last_name'
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = True
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_first_name test_last_name',
"User.get_short_name() returns wrong string when it has a fist_name "
"and a last_name and is sorted by first_name.")
def test_get_short_name_sort_last_name_only_first_name(self):
"""
Tests the output of get_short_name.
"""
user = User()
user.first_name = 'test_first_name'
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = False
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_first_name',
"User.get_short_name() returns wrong string when it has only a "
"first_name and is sorted by last_name.")
def test_get_short_name_sort_last_name_both_names(self):
"""
Tests the output of get_short_name.
"""
user = User()
user.first_name = 'test_first_name'
user.last_name = 'test_last_name'
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = False
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_last_name, test_first_name',
"User.get_short_name() returns wrong string when it has a fist_name "
"and a last_name and is sorted by last_name.")
def test_get_short_name_no_names(self):
"""
Tests the output of get_short_name.
"""
user = User(username='test_username')
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = False
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_username',
"User.get_short_name() returns wrong string when it has no fist_name "
"and no last_name and is sorted by last_name.")
def test_while_spaces_in_name_parts(self):
"""
Tests the output if the name parts have white spaces at the begin or
end.
"""
user = User()
user.first_name = ' test_first_name\n '
user.last_name = ' test_last_name \n'
with patch('openslides.users.models.config') as mock_config:
mock_config.__getitem__.return_value = True
short_name = user.get_short_name()
self.assertEqual(
short_name,
'test_first_name test_last_name',
"User.get_short_name() has to strip whitespaces from the name parts.")
from openslides.users.models import UserManager
class UserManagerTest(TestCase):