From 5418d0b44c5dc7a6f950f03e22f73917a87c6ca0 Mon Sep 17 00:00:00 2001 From: ibuler Date: Sun, 9 Apr 2017 00:45:28 +0800 Subject: [PATCH] [Change] Using csv replace xlsx --- apps/assets/models/asset.py | 1 - apps/assets/models/group.py | 1 + apps/assets/models/idc.py | 1 + apps/assets/models/user.py | 1 - .../templates/assets/_asset_import_modal.html | 5 +- apps/assets/views/asset.py | 187 +++++++++--------- apps/jumpserver/settings.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 23944 -> 24201 bytes apps/locale/zh/LC_MESSAGES/django.po | 144 ++++++-------- apps/templates/_base_create_update.html | 2 +- apps/templates/base.html | 4 +- apps/users/templates/users/first_login.html | 8 +- apps/users/templates/users/user_detail.html | 4 + .../templates/users/user_pubkey_update.html | 1 + apps/users/views/group.py | 2 +- apps/users/views/user.py | 136 +++++++------ 16 files changed, 251 insertions(+), 248 deletions(-) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index 1a66e2d38..499ad2cf0 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -86,7 +86,6 @@ class Asset(models.Model): def __unicode__(self): return '%s <%s: %s>' % (self.hostname, self.ip, self.port) - __str__ = __unicode__ @property diff --git a/apps/assets/models/group.py b/apps/assets/models/group.py index c7e72acfe..b31b80871 100644 --- a/apps/assets/models/group.py +++ b/apps/assets/models/group.py @@ -23,6 +23,7 @@ class AssetGroup(models.Model): def __unicode__(self): return self.name + __str__ = __unicode__ class Meta: ordering = ['name'] diff --git a/apps/assets/models/idc.py b/apps/assets/models/idc.py index fbbde2c98..d79d897fc 100644 --- a/apps/assets/models/idc.py +++ b/apps/assets/models/idc.py @@ -36,6 +36,7 @@ class IDC(models.Model): def __unicode__(self): return self.name + __str__ = __unicode__ @classmethod def initial(cls): diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 04a4282d8..1504f9c2f 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -50,7 +50,6 @@ class AdminUser(models.Model): def __unicode__(self): return self.name - __str__ = __unicode__ @property diff --git a/apps/assets/templates/assets/_asset_import_modal.html b/apps/assets/templates/assets/_asset_import_modal.html index d226ace94..8ef73e531 100644 --- a/apps/assets/templates/assets/_asset_import_modal.html +++ b/apps/assets/templates/assets/_asset_import_modal.html @@ -7,11 +7,14 @@ {% csrf_token %}
- {% trans 'Download' %} + {% trans 'Download' %}
+ + {% trans 'If set id, will use this id update asset existed' %} +

diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index 3844e2fd8..893de66a6 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -1,14 +1,14 @@ # coding:utf-8 from __future__ import absolute_import, unicode_literals + +import csv import json import uuid +import codecs +from io import StringIO -from openpyxl import Workbook -from openpyxl.writer.excel import save_virtual_workbook -from openpyxl import load_workbook from django.conf import settings -from django.db import IntegrityError -from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView, ListView, View from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.urls import reverse_lazy @@ -206,45 +206,42 @@ class AssetModalListView(AdminUserRequiredMixin, ListView): @method_decorator(csrf_exempt, name='dispatch') class AssetExportView(View): - @staticmethod - def get_asset_attr(asset, attr): - if attr in ['admin_user', 'idc']: - return getattr(asset, attr) - elif attr in ['status', 'type', 'env']: - return getattr(asset, 'get_{}_display'.format(attr))() - else: - return getattr(asset, attr) - def get(self, request, *args, **kwargs): spm = request.GET.get('spm', '') - assets_id = cache.get(spm) - if not assets_id and not isinstance(assets_id, list): - return HttpResponse('May be expired', status=404) - + assets_id = cache.get(spm, [Asset.objects.first().id]) + print(assets_id) + fields = [ + field for field in Asset._meta.fields + if field.name not in [ + 'date_created' + ] + ] + filename = 'assets-{}.csv'.format( + timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="%s"' % filename + response.write(codecs.BOM_UTF8) assets = Asset.objects.filter(id__in=assets_id) - wb = Workbook() - ws = wb.active - ws.title = 'Asset' - header = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'memory', 'disk', - 'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no', - 'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment'] - ws.append(header) + writer = csv.writer(response, dialect='excel', + quoting=csv.QUOTE_MINIMAL) + + header = [field.verbose_name for field in fields] + header.append(_('Asset groups')) + writer.writerow(header) for asset in assets: - ws.append([self.get_asset_attr(asset, attr) for attr in header]) - - filename = 'assets-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) - response = HttpResponse(save_virtual_workbook(wb), content_type='applications/vnd.ms-excel') - response['Content-Disposition'] = 'attachment; filename="%s"' % filename + groups = ','.join([group.name for group in asset.groups.all()]) + data = [getattr(asset, field.name) for field in fields] + data.append(groups) + writer.writerow(data) return response def post(self, request, *args, **kwargs): try: assets_id = json.loads(request.body).get('assets_id', []) - print(assets_id) except ValueError: return HttpResponse('Json object not valid', status=400) - spm = uuid.uuid4().get_hex() + spm = uuid.uuid4().hex cache.set(spm, assets_id, 300) url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm return JsonResponse({'redirect': url}) @@ -254,67 +251,74 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView): form_class = forms.FileForm def form_valid(self, form): - try: - wb = load_workbook(form.cleaned_data['file']) - ws = wb.get_active_sheet() - except Exception as e: - print(e) - data = {'valid': False, 'msg': 'Not a valid Excel file'} + file = form.cleaned_data['file'] + data = file.read().decode('utf-8').strip( + codecs.BOM_UTF8.decode('utf-8')) + csv_file = StringIO(data) + reader = csv.reader(csv_file) + csv_data = [row for row in reader] + fields = [ + field for field in Asset._meta.fields + if field.name not in [ + 'date_created' + ] + ] + header_ = csv_data[0] + mapping_reverse = {field.verbose_name: field.name for field in fields} + mapping_reverse[_('Asset groups')] = 'groups' + attr = [mapping_reverse.get(n, None) for n in header_] + if None in attr: + data = {'valid': False, + 'msg': 'Must be same format as ' + 'template or export file'} return self.render_json_response(data) - rows = ws.rows - header_all = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk', - 'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no', - 'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment'] - header_min = ['hostname', 'ip', 'port', 'admin_user', 'comment'] - header = [col.value for col in next(rows)] - if not set(header).issubset(set(header_all)) and not set(header).issuperset(set(header_min)): - data = {'valid': False, 'msg': 'Must be same format as template or export file'} - return self.render_json_response(data) - - created = [] - updated = [] - failed = [] - for row in rows: - asset_dict = dict(zip(header, [col.value for col in row])) - if asset_dict.get('admin_user', None): - admin_user = get_object_or_none(AdminUser, name=asset_dict['admin_user']) - asset_dict['admin_user'] = admin_user - - if asset_dict.get('idc'): - idc = get_object_or_none(IDC, name=asset_dict['idc']) - asset_dict['idc'] = idc - - if asset_dict.get('type'): - asset_display_type_map = dict(zip(dict(Asset.TYPE_CHOICES).values(), dict(Asset.TYPE_CHOICES).keys())) - asset_type = asset_display_type_map.get(asset_dict['type'], 'Server') - asset_dict['type'] = asset_type - - if asset_dict.get('status'): - asset_display_status_map = dict(zip(dict(Asset.STATUS_CHOICES).values(), - dict(Asset.STATUS_CHOICES).keys())) - asset_status = asset_display_status_map.get(asset_dict['status'], 'In use') - asset_dict['status'] = asset_status - - if asset_dict.get('env'): - asset_display_env_map = dict(zip(dict(Asset.ENV_CHOICES).values(), - dict(Asset.ENV_CHOICES).keys())) - asset_env = asset_display_env_map.get(asset_dict['env'], 'Prod') - asset_dict['env'] = asset_env - - try: - Asset.objects.create(**asset_dict) - created.append(asset_dict['ip']) - except IntegrityError as e: - asset = Asset.objects.filter(ip=asset_dict['ip'], port=asset_dict['port']) - if not asset: - failed.append(asset_dict['ip']) + created, updated, failed = [], [], [] + for row in csv_data[1:]: + if set(row) == {''}: + continue + asset_dict = dict(zip(attr, row)) + id_ = asset_dict.pop('id', 0) + asset = get_object_or_none(Asset, id=id_) + for k, v in asset_dict.items(): + if k == 'idc': + v = get_object_or_none(IDC, name=v) + elif k == 'is_active': + v = bool(v) + elif k == 'admin_user': + v = get_object_or_none(AdminUser, name=v) + elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']: + try: + v = int(v) + except ValueError: + v = 0 + elif k == 'groups': + groups_name = v.split(',') + v = AssetGroup.objects.filter(name__in=groups_name) + else: continue - asset.update(**asset_dict) - updated.append(asset_dict['ip']) - except TypeError as e: - print(e) - failed.append(asset_dict['ip']) + asset_dict[k] = v + + if not asset: + try: + groups = asset_dict.pop('groups') + asset = Asset.objects.create(**asset_dict) + asset.groups.set(groups) + created.append(asset_dict['hostname']) + except IndexError as e: + failed.append('%s: %s' % (asset_dict['hostname'], str(e))) + else: + for k, v in asset_dict.items(): + if k == 'groups': + asset.groups.set(v) + continue + if v: + setattr(asset, k, v) + try: + asset.save() + updated.append(asset_dict['hostname']) + except Exception as e: + failed.append('%s: %s' % (asset_dict['hostname'], str(e))) data = { 'created': created, @@ -324,7 +328,8 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView): 'failed': failed, 'failed_info': 'Failed {}'.format(len(failed)), 'valid': True, - 'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed)) + 'msg': 'Created: {}. Updated: {}, Error: {}'.format( + len(created), len(updated), len(failed)) } return self.render_json_response(data) diff --git a/apps/jumpserver/settings.py b/apps/jumpserver/settings.py index 83ead6169..a81ab5ab0 100644 --- a/apps/jumpserver/settings.py +++ b/apps/jumpserver/settings.py @@ -260,7 +260,7 @@ MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/') + '/' # Use django-bootstrap-form to format template, input max width arg -BOOTSTRAP_COLUMN_COUNT = 11 +# BOOTSTRAP_COLUMN_COUNT = 11 # Init data or generate fake data source for development FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1c72f8960a549c7ade63303498d86921980028fb..4772033de1d2522a184013e577380070a21a032c 100644 GIT binary patch delta 8995 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 E1GG}f5&!@I delta 8808 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\n" "Language-Team: Jumpserver team\n" @@ -78,7 +78,7 @@ msgstr "登录地址" #: assets/templates/assets/system_user_list.html:21 perms/models.py:40 #: perms/templates/perms/asset_permission_detail.html:98 #: users/models/group.py:19 users/models/user.py:43 -#: users/templates/users/user_detail.html:109 +#: users/templates/users/user_detail.html:113 #: users/templates/users/user_group_detail.html:70 #: users/templates/users/user_group_list.html:14 #: users/templates/users/user_profile.html:118 @@ -134,7 +134,7 @@ msgstr "在线session" #: assets/templates/assets/asset_list.html:74 perms/models.py:33 #: perms/templates/perms/asset_permission_create_update.html:47 #: users/templates/users/_select_user_modal.html:18 -#: users/templates/users/user_detail.html:126 +#: users/templates/users/user_detail.html:130 #: users/templates/users/user_list.html:29 #: users/templates/users/user_profile.html:63 msgid "Active" @@ -234,8 +234,8 @@ msgstr "其它" #: assets/templates/assets/system_user_detail.html:137 #: perms/templates/perms/asset_permission_create_update.html:67 #: users/templates/users/_user.html:49 -#: users/templates/users/user_detail.html:158 -#: users/templates/users/user_detail.html:166 +#: users/templates/users/user_detail.html:162 +#: users/templates/users/user_detail.html:170 #: users/templates/users/user_password_update.html:58 #: users/templates/users/user_profile.html:139 #: users/templates/users/user_profile.html:147 @@ -259,7 +259,7 @@ msgstr "重置" #: audits/templates/audits/proxy_log_online_list.html:124 #: perms/templates/perms/asset_permission_create_update.html:68 #: users/templates/users/_user.html:50 -#: users/templates/users/first_login.html:60 +#: users/templates/users/first_login.html:62 #: users/templates/users/forgot_password.html:44 #: users/templates/users/user_asset_permission.html:100 #: users/templates/users/user_group_asset_permission.html:100 @@ -569,7 +569,7 @@ msgstr "主机名原始" #: assets/templates/assets/idc_detail.html:93 #: assets/templates/assets/system_user_detail.html:94 perms/models.py:37 #: perms/templates/perms/asset_permission_detail.html:94 -#: users/models/user.py:47 users/templates/users/user_detail.html:97 +#: users/models/user.py:47 users/templates/users/user_detail.html:101 msgid "Created by" msgstr "创建者" @@ -733,22 +733,17 @@ msgstr "选择系统用户" msgid "Import asset" msgstr "导入资产" -#: assets/templates/assets/_asset_import_modal.html:6 -#: users/templates/users/_user_import_modal.html:6 -msgid "Download template or use export excel format" -msgstr "下载模板" - -#: assets/templates/assets/_asset_import_modal.html:10 +#: assets/templates/assets/_asset_import_modal.html:9 #: users/templates/users/_user_import_modal.html:10 msgid "Template" msgstr "模板" -#: assets/templates/assets/_asset_import_modal.html:11 +#: assets/templates/assets/_asset_import_modal.html:10 #: users/templates/users/_user_import_modal.html:11 msgid "Download" msgstr "下载" -#: assets/templates/assets/_asset_import_modal.html:14 +#: assets/templates/assets/_asset_import_modal.html:13 msgid "Asset excel file" msgstr "资产excel" @@ -888,13 +883,13 @@ msgid "Platform" msgstr "" #: assets/templates/assets/asset_detail.html:152 -#: users/templates/users/user_detail.html:101 +#: users/templates/users/user_detail.html:105 #: users/templates/users/user_profile.html:88 msgid "Date joined" msgstr "创建日期" #: assets/templates/assets/asset_detail.html:168 -#: users/templates/users/user_detail.html:120 +#: users/templates/users/user_detail.html:124 #: users/templates/users/user_profile.html:130 msgid "Quick modify" msgstr "快速修改" @@ -929,9 +924,9 @@ msgstr "添加到资产组" #: assets/templates/assets/system_user_asset.html:96 #: assets/templates/assets/system_user_list.html:102 #: assets/templates/assets/user_asset_list.html:167 templates/_modal.html:16 -#: users/templates/users/user_detail.html:337 -#: users/templates/users/user_detail.html:362 -#: users/templates/users/user_detail.html:385 +#: users/templates/users/user_detail.html:341 +#: users/templates/users/user_detail.html:366 +#: users/templates/users/user_detail.html:389 #: users/templates/users/user_group_create_update.html:45 #: users/templates/users/user_group_list.html:92 #: users/templates/users/user_list.html:171 @@ -940,8 +935,8 @@ msgid "Confirm" msgstr "确认" #: assets/templates/assets/asset_detail.html:374 -#: users/templates/users/user_detail.html:271 -#: users/templates/users/user_detail.html:284 +#: users/templates/users/user_detail.html:275 +#: users/templates/users/user_detail.html:288 msgid "Update successfully!" msgstr "更新成功" @@ -985,8 +980,8 @@ msgstr "批量更新" #: assets/templates/assets/idc_list.html:94 #: assets/templates/assets/system_user_list.html:97 #: assets/templates/assets/user_asset_list.html:162 -#: users/templates/users/user_detail.html:332 -#: users/templates/users/user_detail.html:357 +#: users/templates/users/user_detail.html:336 +#: users/templates/users/user_detail.html:361 #: users/templates/users/user_group_list.html:87 #: users/templates/users/user_list.html:166 msgid "Are you sure?" @@ -1149,10 +1144,8 @@ msgstr "IDC删除失败" #: assets/templates/assets/system_user_asset.html:20 #: assets/templates/assets/system_user_detail.html:21 -#, fuzzy -#| msgid "Attach Asset" msgid "Attached assets" -msgstr "关联资产" +msgstr "关联的资产" #: assets/templates/assets/system_user_asset.html:28 msgid "Assets of " @@ -1179,7 +1172,6 @@ msgid "Attach AssetGroup" msgstr "添加到资产组" #: assets/templates/assets/system_user_detail.html:79 -#: templates/_header_bar.html:41 msgid "Home" msgstr "" @@ -1494,7 +1486,7 @@ msgid "Select user groups" msgstr "添加到用户组" #: perms/models.py:35 perms/templates/perms/asset_permission_detail.html:86 -#: users/models/user.py:46 users/templates/users/user_detail.html:93 +#: users/models/user.py:46 users/templates/users/user_detail.html:97 #: users/templates/users/user_profile.html:96 msgid "Date expired" msgstr "失效日期" @@ -1527,7 +1519,7 @@ msgstr "添加资产组" #: perms/templates/perms/asset_permission_asset.html:146 #: perms/templates/perms/asset_permission_detail.html:170 -#: users/templates/users/user_detail.html:194 +#: users/templates/users/user_detail.html:198 msgid "Join" msgstr "加入" @@ -1570,7 +1562,7 @@ msgstr "创建授权规则" #: perms/templates/perms/asset_permission_list.html:13 templates/_nav.html:13 #: users/models/user.py:34 users/templates/users/_select_user_modal.html:16 -#: users/templates/users/user_detail.html:177 +#: users/templates/users/user_detail.html:181 #: users/templates/users/user_list.html:28 msgid "User group" msgstr "用户组" @@ -1659,18 +1651,18 @@ msgstr "注销登录" msgid "Login" msgstr "登录" +#: templates/_header_bar.html:41 templates/_nav.html:4 +msgid "Dashboard" +msgstr "仪表盘" + #: templates/_modal.html:15 msgid "Close" msgstr "关闭" -#: templates/_nav.html:4 -msgid "Dashboard" -msgstr "仪表盘" - #: templates/_nav.html:9 users/templates/users/user_group_create_update.html:28 #: users/views/group.py:31 users/views/group.py:45 users/views/group.py:80 -#: users/views/group.py:105 users/views/login.py:185 users/views/user.py:54 -#: users/views/user.py:70 users/views/user.py:106 users/views/user.py:118 +#: users/views/group.py:105 users/views/login.py:185 users/views/user.py:58 +#: users/views/user.py:74 users/views/user.py:110 users/views/user.py:122 msgid "Users" msgstr "用户管理" @@ -1734,22 +1726,20 @@ msgid "" " " msgstr "" "\n" -" 你的信息不完整,请点击 链接 to 补充完整\n" +" 你的信息不完整,请点击 链接 " +" 补充完整\n" " " - #: templates/base.html:37 -#, python-format msgid "" "\n" " Your ssh-public-key has been expired. Please click this link to update your ssh-public-key.\n" +"\"%(user_pubkey_update)s\"> this link to update your ssh-public-key.\n" " " msgstr "" "\n" -" 你的SSH key已经过期,点击 链接 更新 \n" +" 你的SSH key已经过期,点击 链接 " +"更新 \n" " " #: templates/captcha/image.html:3 @@ -1815,7 +1805,7 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "" -#: users/forms.py:36 users/templates/users/user_detail.html:185 +#: users/forms.py:36 users/templates/users/user_detail.html:189 msgid "Join user groups" msgstr "添加到用户组" @@ -1876,7 +1866,8 @@ msgid "Wechat" msgstr "微信" #: users/models/user.py:39 users/templates/users/_user.html:36 -#: users/templates/users/user_detail.html:140 +#: users/templates/users/user_detail.html:93 +#: users/templates/users/user_detail.html:144 msgid "Enable OTP" msgstr "二次验证" @@ -1917,6 +1908,10 @@ msgstr "组" msgid "Import user" msgstr "导入" +#: users/templates/users/_user_import_modal.html:6 +msgid "Download template or use export excel format" +msgstr "下载模板" + #: users/templates/users/_user_import_modal.html:14 msgid "Users excel file" msgstr "用户excel" @@ -1934,11 +1929,11 @@ msgstr "首次登陆" msgid "Step" msgstr "Step" -#: users/templates/users/first_login.html:57 +#: users/templates/users/first_login.html:59 msgid "First step" msgstr "第一步" -#: users/templates/users/first_login.html:58 +#: users/templates/users/first_login.html:60 msgid "Prev step" msgstr "上一步" @@ -1968,8 +1963,8 @@ msgid "Captcha invalid" msgstr "验证码错误" #: users/templates/users/reset_password.html:45 -#: users/templates/users/user_detail.html:155 -#: users/templates/users/user_detail.html:323 +#: users/templates/users/user_detail.html:159 +#: users/templates/users/user_detail.html:327 #: users/templates/users/user_profile.html:136 users/utils.py:71 msgid "Reset password" msgstr "重置密码" @@ -1987,7 +1982,7 @@ msgstr "设置" #: users/templates/users/user_granted_asset.html:18 #: users/templates/users/user_group_asset_permission.html:18 #: users/templates/users/user_group_granted_asset.html:18 -#: users/views/user.py:119 +#: users/views/user.py:123 msgid "User detail" msgstr "用户详情" @@ -2015,7 +2010,7 @@ msgid "Revoke Successfully!" msgstr "回收成功" #: users/templates/users/user_create.html:4 -#: users/templates/users/user_list.html:16 users/views/user.py:70 +#: users/templates/users/user_list.html:16 users/views/user.py:74 msgid "Create user" msgstr "创建用户" @@ -2023,47 +2018,47 @@ msgstr "创建用户" msgid "Reset link will be generated and sent to the user. " msgstr "生成重置密码连接,通过邮件发送给用户" -#: users/templates/users/user_detail.html:105 +#: users/templates/users/user_detail.html:109 #: users/templates/users/user_profile.html:92 msgid "Last login" msgstr "最后登录" -#: users/templates/users/user_detail.html:163 +#: users/templates/users/user_detail.html:167 msgid "Reset ssh key" msgstr "重置密钥" -#: users/templates/users/user_detail.html:322 +#: users/templates/users/user_detail.html:326 msgid "An e-mail has been sent to the user\\'s mailbox." msgstr "已发送邮件到用户邮箱" -#: users/templates/users/user_detail.html:333 +#: users/templates/users/user_detail.html:337 msgid "" "This will reset the user's password. A password-reset email will be sent to " "the user\\'s mailbox." msgstr "" -#: users/templates/users/user_detail.html:347 +#: users/templates/users/user_detail.html:351 msgid "" "The reset-ssh-public-key E-mail has been sent successfully. Please inform " "the user to update his new ssh public key." msgstr "" -#: users/templates/users/user_detail.html:348 +#: users/templates/users/user_detail.html:352 #: users/templates/users/user_profile.html:144 msgid "Reset SSH public key" msgstr "重置SSH密钥" -#: users/templates/users/user_detail.html:358 +#: users/templates/users/user_detail.html:362 msgid "This will reset the user\\" msgstr "重置" -#: users/templates/users/user_detail.html:375 +#: users/templates/users/user_detail.html:379 #: users/templates/users/user_profile.html:170 msgid "Successfully updated the SSH public key." msgstr "" -#: users/templates/users/user_detail.html:376 #: users/templates/users/user_detail.html:380 +#: users/templates/users/user_detail.html:384 #: users/templates/users/user_profile.html:171 #: users/templates/users/user_profile.html:176 msgid "User SSH Public Key Update" @@ -2071,23 +2066,17 @@ msgstr "ssh密钥" #: users/templates/users/user_granted_asset.html:32 #: users/templates/users/user_group_granted_asset.html:32 -#, fuzzy -#| msgid "Asset group list" msgid "Assets granted of " -msgstr "资产组列表" +msgstr "授权资产" #: users/templates/users/user_granted_asset.html:68 #: users/templates/users/user_group_granted_asset.html:68 -#, fuzzy -#| msgid "Asset group list" msgid "Asset groups granted of " -msgstr "资产组列表" +msgstr "授权资产组" #: users/templates/users/user_group_asset_permission.html:71 -#, fuzzy -#| msgid "Create perm" msgid "Quick create permission for user group" -msgstr "创建权限" +msgstr "快速授权" #: users/templates/users/user_group_create_update.html:30 msgid "Select User" @@ -2194,7 +2183,7 @@ msgstr "指纹" msgid "Update public key" msgstr "更新密钥" -#: users/templates/users/user_update.html:3 users/views/user.py:106 +#: users/templates/users/user_update.html:3 users/views/user.py:110 msgid "Update user" msgstr "编辑用户" @@ -2336,12 +2325,6 @@ msgstr "用户组列表" msgid "Update user group" msgstr "编辑用户组" -#: users/views/group.py:106 -#, fuzzy -#| msgid "User group detail" -msgid "User Group Detail" -msgstr "资产组详情" - #: users/views/login.py:76 msgid "Logout success" msgstr "退出登录成功" @@ -2383,16 +2366,15 @@ msgstr "密码不一致" msgid "First login" msgstr "首次登陆" -#: users/views/user.py:55 +#: users/views/user.py:59 msgid "User list" msgstr "用户列表" -#: users/views/user.py:66 users/views/user.py:334 +#: users/views/user.py:70 users/views/user.py:344 #, python-brace-format msgid "Create user {name} successfully." msgstr "创建用户 {name} 成功" -#: users/views/user.py:175 +#: users/views/user.py:188 msgid "Invalid file." msgstr "文件错误" - diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html index c011feb71..fdc5269e2 100644 --- a/apps/templates/_base_create_update.html +++ b/apps/templates/_base_create_update.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load i18n %} {% load static %} -{% load bootstrap %} +{% load bootstrap3 %} {% block custom_head_css_js %} diff --git a/apps/templates/base.html b/apps/templates/base.html index 61882ae1a..b53dfe98a 100644 --- a/apps/templates/base.html +++ b/apps/templates/base.html @@ -33,9 +33,9 @@ {% block update_public_key_message %} {% if user.is_authenticated and not user.is_public_key_valid %}

- {% url 'users:user-profile' as profile_url %} + {% url 'users:user-pubkey-update' as user_pubkey_update %} {% blocktrans %} - Your ssh-public-key has been expired. Please click this link to update your ssh-public-key. + Your ssh-public-key has been expired. Please click this link to update your ssh-public-key. {% endblocktrans %}
{% endif %} diff --git a/apps/users/templates/users/first_login.html b/apps/users/templates/users/first_login.html index 402a9a5f4..99aff6ccf 100644 --- a/apps/users/templates/users/first_login.html +++ b/apps/users/templates/users/first_login.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load static %} {% load i18n %} -{% load bootstrap3 %} +{% load bootstrap3 %} {% block custom_head_css_js %} @@ -44,10 +44,12 @@ {% if wizard.form.forms %} {{ wizard.form.management_form }} {% for form in wizard.form.forms %} - {{ form|bootstrap }} + {% bootstrap_form form %} +{# {{ form|bootstrap }}#} {% endfor %} {% else %} - {{ wizard.form|bootstrap }} +{# {{ wizard.form|bootstrap }}#} + {% bootstrap_form wizard.form %} {% endif %} diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index 290c24498..d2617389f 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -89,6 +89,10 @@ {% trans 'Role' %}: {{ user_object.get_role_display }} + + {% trans 'Enable OTP' %}: + {{ user_object.enable_otp|yesno:"Yes,No,Unknown"}} + {% trans 'Date expired' %}: {{ user_object.date_expired|date:"Y-m-j H:i:s" }} diff --git a/apps/users/templates/users/user_pubkey_update.html b/apps/users/templates/users/user_pubkey_update.html index de10272c7..ee621eb8d 100644 --- a/apps/users/templates/users/user_pubkey_update.html +++ b/apps/users/templates/users/user_pubkey_update.html @@ -61,6 +61,7 @@ +

{% trans 'Update public key' %}

{% bootstrap_field form.public_key layout="horizontal" %}
diff --git a/apps/users/views/group.py b/apps/users/views/group.py index dda420606..12697fece 100644 --- a/apps/users/views/group.py +++ b/apps/users/views/group.py @@ -103,7 +103,7 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): users = User.objects.exclude(id__in=self.object.users.all()) context = { 'app': _('Users'), - 'action': _('User Group Detail'), + 'action': _('User group detail'), 'users': users, } kwargs.update(context) diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 8e0d881e9..e5c2a25c9 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -4,10 +4,9 @@ from __future__ import unicode_literals import json import uuid - -from openpyxl import load_workbook -from openpyxl import Workbook -from openpyxl.writer.excel import save_virtual_workbook +import csv +import codecs +from io import StringIO from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.messages.views import SuccessMessageMixin @@ -31,7 +30,7 @@ from .. import forms from ..models import User, UserGroup from ..utils import AdminUserRequiredMixin, user_add_success_next from common.mixins import JSONResponseMixin -from common.utils import get_logger +from common.utils import get_logger, get_object_or_none from perms.models import AssetPermission __all__ = ['UserListView', 'UserCreateView', 'UserDetailView', @@ -123,34 +122,44 @@ class UserDetailView(AdminUserRequiredMixin, DetailView): return super(UserDetailView, self).get_context_data(**kwargs) +USER_ATTR_MAPPING = ( + ('name', 'Name'), + ('username', 'Username'), + ('email', 'Email'), + ('groups', 'User groups'), + ('role', 'Role'), + ('phone', 'Phone'), + ('wechat', 'Wechat'), + ('comment', 'Comment'), +) + + @method_decorator(csrf_exempt, name='dispatch') class UserExportView(View): def get(self, request): + mapping = [ + (k, _(v)) for k, v in USER_ATTR_MAPPING + ] spm = request.GET.get('spm', '') - users_id = cache.get(spm) - if not users_id and not isinstance(users_id, list): - return HttpResponse('May be expired', status=404) - + users_id = cache.get(spm, ['1']) + filename = 'users-{}.csv'.format( + timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="%s"' % filename + response.write(codecs.BOM_UTF8) users = User.objects.filter(id__in=users_id) - print(users) - wb = Workbook() - ws = wb.active - ws.title = 'User' - header = ["name", 'username', 'email', 'groups', - "role", "phone", "wechat", "comment"] - ws.append(header) + writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL) + + header = [v for k, v in mapping] + writer.writerow(header) for user in users: - ws.append([user.name, user.username, user.email, - ','.join([group.name for group in user.groups.all()]), - user.role, user.phone, user.wechat, user.comment]) + groups = ','.join([group.name for group in user.groups.all()]) + writer.writerow([ + user.name, user.username, user.email, groups, + user.role, user.phone, user.wechat, user.comment + ]) - filename = 'users-{}.xlsx'.format( - timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) - response = HttpResponse(save_virtual_workbook(wb), - content_type='applications/vnd.ms-excel') - response[ - 'Content-Disposition'] = 'attachment; filename="%s"' % filename return response def post(self, request): @@ -158,7 +167,7 @@ class UserExportView(View): users_id = json.loads(request.body).get('users_id', []) except ValueError: return HttpResponse('Json object not valid', status=400) - spm = uuid.uuid4().get_hex() + spm = uuid.uuid4().hex cache.set(spm, users_id, 300) url = reverse('users:user-export') + '?spm=%s' % spm return JsonResponse({'redirect': url}) @@ -179,54 +188,51 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView): return self.render_json_response(data) def form_valid(self, form): - try: - wb = load_workbook(form.cleaned_data['file']) - ws = wb.get_active_sheet() - except Exception as e: - print(e) - data = {'valid': False, 'msg': 'Not a valid Excel file'} + file = form.cleaned_data['file'] + data = file.read().decode('utf-8').strip(codecs.BOM_UTF8.decode('utf-8')) + csv_file = StringIO(data) + reader = csv.reader(csv_file) + csv_data = [row for row in reader] + header_ = csv_data[0] + mapping_reverse = {_(v): k for k, v in USER_ATTR_MAPPING} + user_attr = [mapping_reverse.get(n, None) for n in header_] + if None in user_attr: + data = {'valid': False, + 'msg': 'Must be same format as ' + 'template or export file'} return self.render_json_response(data) - rows = ws.rows - header_need = ["name", 'username', 'email', 'groups', - "role", "phone", "wechat", "comment"] - header = [col.value for col in next(rows)] - print(header) - if header != header_need: - data = {'valid': False, 'msg': 'Must be same format as ' - 'template or export file'} - return self.render_json_response(data) - - created = [] - updated = [] - failed = [] - for row in rows: - user_dict = dict(zip(header, [col.value for col in row])) + created, updated, failed = [], [], [] + for row in csv_data[1:]: + if set(row) == {''}: + continue + user_dict = dict(zip(user_attr, row)) groups_name = user_dict.pop('groups') if groups_name: groups_name = groups_name.split(',') groups = UserGroup.objects.filter(name__in=groups_name) else: groups = None - try: - user = User.objects.create(**user_dict) - user_add_success_next(user) - created.append(user_dict['username']) - except User.IntegrityError as e: - user = User.objects.filter(username=user_dict['username']) - if not user: - failed.append(user_dict['username']) - continue - user.update(**user_dict) - user = user[0] - updated.append(user_dict['username']) - except TypeError as e: - print(e) - failed.append(user_dict['username']) - user = None - + username = user_dict['username'] + user = get_object_or_none(User, username=username) + if not user: + try: + user = User.objects.create(**user_dict) + created.append(user_dict['username']) + user_add_success_next(user) + except Exception as e: + failed.append('%s: %s' % (user_dict['username'], str(e))) + else: + for k, v in user_dict.items(): + if v: + setattr(user, k, v) + try: + user.save() + updated.append(user_dict['username']) + except Exception as e: + failed.append('%s: %s' % (user_dict['username'], str(e))) if user and groups: - user.groups.add(*tuple(groups)) + user.groups = groups user.save() data = {