From 562ee5e52b88f907edd655c15c3e7817131f7a2d Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sun, 23 Jun 2019 13:08:11 +0200 Subject: [PATCH] manager: add help on users imports (#34238) --- src/authentic2/attribute_kinds.py | 1 + .../static/authentic2/manager/css/style.css | 8 + .../authentic2/manager/user_imports.html | 175 +++++++++++++++--- src/authentic2/manager/user_views.py | 36 ++++ 4 files changed, 197 insertions(+), 23 deletions(-) diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py index 6ae7680f..33fbe042 100644 --- a/src/authentic2/attribute_kinds.py +++ b/src/authentic2/attribute_kinds.py @@ -244,6 +244,7 @@ DEFAULT_ATTRIBUTE_KINDS = [ }, 'html_value': profile_image_html_value, 'attributes_ng_serialize': profile_attributes_ng_serialize, + 'csv_importable': False, }, ] diff --git a/src/authentic2/manager/static/authentic2/manager/css/style.css b/src/authentic2/manager/static/authentic2/manager/css/style.css index 24f106e1..8b8369e6 100644 --- a/src/authentic2/manager/static/authentic2/manager/css/style.css +++ b/src/authentic2/manager/static/authentic2/manager/css/style.css @@ -52,6 +52,14 @@ table.main th.name, table.main td.name, #user-table .link, #user-table .username .email, #user-table .first_name, #user-table .last_name, #user-table .ou { text-align: left; } +table.main.left { + padding-left: 1rem; +} +table.main.left td, +table.main.left th { + text-align: left; +} + table.feeds td.url { text-align: left; padding-left: 1em; diff --git a/src/authentic2/manager/templates/authentic2/manager/user_imports.html b/src/authentic2/manager/templates/authentic2/manager/user_imports.html index 6c2a37b6..e4baaf71 100644 --- a/src/authentic2/manager/templates/authentic2/manager/user_imports.html +++ b/src/authentic2/manager/templates/authentic2/manager/user_imports.html @@ -21,27 +21,156 @@ {% endblock %} {% block content %} -

{% trans "Imports" %}

- - - - - - - - - - - - {% for import in imports %} - - - - - - - - {% endfor %} - -
{% trans "Filename" %}{% trans "Creation date" %}{% trans "By" %}{% trans "Rows" %}
{{ import.filename }}{{ import.created }}{{ import.user }}{{ import.rows_count }}
{% csrf_token %}
+
+

{% trans "Imports" %}

+ + + + + + + + + + + + {% for import in imports %} + + + + + + + + {% endfor %} + +
{% trans "Filename" %}{% trans "Creation date" %}{% trans "By" %}{% trans "Rows" %}
{{ import.filename }}{{ import.created }}{{ import.user }}{{ import.rows_count }}
{% csrf_token %}
+
+ +
+

{% trans "Help" %}

+

+ {% blocktrans trimmed %} + The first line of your CSV file must be a header mapping columns to user's attributes identifier. + Each user attribute name can be followed by flags separated by spaces. + You can also import an external identifier to prevent creating duplicates when doing multiple import from the same source. + {% endblocktrans %} +

+

{% trans "Attributes" %}

+ + + + + + + + + {% for column in help_columns %} + + + + + {% endfor %} + +
{% trans "Label" %}{% trans "Identifier" %}
{{ column.label }}{{ column.name }}
+

{% trans "Flags" %}

+

{% blocktrans trimmed %}Each column can receive flags after its name, separated by spaces. Each modifier can be prefixed by no- to set its value to false.{% endblocktrans %}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "Flag" %}{% trans "Meaning" %}{% trans "Default value" %}
key + {% blocktrans trimmed %} + The column is an import key, it is used to match the row with an existing user. Only one column can be an import key. + {% endblocktrans %} + {% trans "False" %}
create + {% blocktrans trimmed %} + Values will be used when creating a new user. + {% endblocktrans %} + {% trans "True" %}
update + {% blocktrans trimmed %} + Values will be used when updating an existing user. + {% endblocktrans %} + {% trans "True" %}
unique + {% blocktrans trimmed %} + Values must be unique in the target organizational unit. + {% endblocktrans %} + {% trans "False" %} {% blocktrans trimmed %} + (default is True for the email and username columns if they are configured to be unique in the target organizational unit) + {% endblocktrans %} +
globally-unique + {% blocktrans trimmed %} + Values must be unique among all users. + {% endblocktrans %} + {% trans "False" %} {% blocktrans trimmed %} + (default is True for the email and username columns if they are configured to be globally unique) + {% endblocktrans %}
verified + {% blocktrans trimmed %} + Values are verified. + {% endblocktrans %} + {% trans "False" %} {% blocktrans %}(default is True for the email column){% endblocktrans %}
+ +

{% trans "External identifier" %}

+

+ {% blocktrans trimmed %} + You can also use two special columns _source_name and + _source_id. _source_name must be the name of the + source directory from which the users are exported, it must not + change between imports. _source_id is the unique identifier + from the source directory from which the users are extracted, it must + not change between imports and should never be reused for different + users. _source_id is automatically the key column, and you + cannot use another key column. + {% endblocktrans %} +

+

{% trans "Examples" %}

+

Importing verified first and last name of users keyed by email

+
+
email key,first_name verified,last_name verified
+john.doe@example.com,John,Doe
+
+
+

Importing email, family_reference, first and last name of users from application app1, ensuring family_reference is unique.

+
+
_source_name,_source_id,email,family_reference,first_name,last_name
+app1,1,john.doe@example.com,1234,John,Doe
+
+
+
{% endblock %} diff --git a/src/authentic2/manager/user_views.py b/src/authentic2/manager/user_views.py index b679d90b..2d19c8ae 100644 --- a/src/authentic2/manager/user_views.py +++ b/src/authentic2/manager/user_views.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import base64 import datetime import collections import operator @@ -52,6 +53,8 @@ from .resources import UserResource from .utils import get_ou_count, has_show_username from . import app_settings +User = get_user_model() + class UsersView(HideOUColumnMixin, BaseTableView): template_name = 'authentic2/manager/users.html' @@ -659,6 +662,39 @@ class UserImportsView(MediaMixin, PermissionMixin, FormView): ctx = super(UserImportsView, self).get_context_data(**kwargs) ctx['imports'] = sorted(user_import.UserImport.all(), key=operator.attrgetter('created'), reverse=True) + help_columns = [] + field_columns = ['username', 'email', 'first_name', 'last_name'] + key = 'username' + if not has_show_username(): + field_columns.remove('username') + key = 'email' + for field_column in field_columns: + field = User._meta.get_field(field_column) + if Attribute.objects.filter(name=field.name).exists(): + continue + help_columns.append({ + 'label': field.verbose_name, + 'name': field.name, + 'key': field.name == key, + }) + for attribute in Attribute.objects.all(): + kind = attribute.get_kind() + if not kind.get('csv_importable', True): + continue + help_columns.append({ + 'label': attribute.label, + 'name': attribute.name, + 'key': attribute.name == key, + }) + ctx['help_columns'] = help_columns + example_data = u','.join(column['name'] + (' key' if column['key'] else '') for column in help_columns) + '\n' + example_url = 'data:text/csv;base64,%s' % base64.b64encode(example_data.encode('utf-8')) + ctx['form'].fields['import_file'].help_text = format_html( + _('{0}. {1} {3}'), + ctx['form'].fields['import_file'].help_text, + _('ex.:'), + example_url, + _('users.csv')) return ctx user_imports = UserImportsView.as_view() -- 2.20.1