mirror of https://github.com/jumpserver/jumpserver
[Change] Using csv replace xlsx
parent
bcc065eb8f
commit
5418d0b44c
|
@ -86,7 +86,6 @@ class Asset(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
|
|
|
@ -23,6 +23,7 @@ class AssetGroup(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
|
|
@ -36,6 +36,7 @@ class IDC(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
__str__ = __unicode__
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
|
|
|
@ -50,7 +50,6 @@ class AdminUser(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
@property
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
|
||||
<a href="{{ MEDIA_URL }}files/asset_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
|
||||
<a href="{% url 'assets:asset-export' %}" style="display: block">{% trans 'Download' %}</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="id_users">{% trans "Asset excel file" %}</label>
|
||||
<input id="id_assets" type="file" name="file" />
|
||||
<span class="help-block">
|
||||
{% trans 'If set id, will use this id update asset existed' %}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'), ]
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-04-07 17:03+0800\n"
|
||||
"POT-Creation-Date: 2017-04-08 21:58+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: Jumpserver team<ibuler@qq.com>\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"
|
||||
" 你的信息不完整,请点击 <a href="
|
||||
"\"%(first_login_url)s\"> 链接 </a>to 补充完整\n"
|
||||
" 你的信息不完整,请点击 <a href=\"%(first_login_url)s\"> 链接 "
|
||||
"</a> 补充完整\n"
|
||||
" "
|
||||
|
||||
|
||||
#: templates/base.html:37
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Your ssh-public-key has been expired. Please click <a href="
|
||||
"\"%(profile_url)s\"> this link </a>to update your ssh-public-key.\n"
|
||||
"\"%(user_pubkey_update)s\"> this link </a>to update your ssh-public-key.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 你的SSH key已经过期,点击 <a href="
|
||||
"\"%(profile_url)s\"> 链接 </a>更新 \n"
|
||||
" 你的SSH key已经过期,点击 <a href=\"%(user_pubkey_update)s\"> 链接 "
|
||||
"</a>更新 \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 <a href=\"{url}\">{name}</a> successfully."
|
||||
msgstr "创建用户 <a href=\"{url}\">{name}</a> 成功"
|
||||
|
||||
#: users/views/user.py:175
|
||||
#: users/views/user.py:188
|
||||
msgid "Invalid file."
|
||||
msgstr "文件错误"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load bootstrap %}
|
||||
{% load bootstrap3 %}
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>
|
||||
|
|
|
@ -33,9 +33,9 @@
|
|||
{% block update_public_key_message %}
|
||||
{% if user.is_authenticated and not user.is_public_key_valid %}
|
||||
<div class="alert alert-danger" style="margin: 20px auto 0px">
|
||||
{% 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 <a href="{{ profile_url }}"> this link </a>to update your ssh-public-key.
|
||||
Your ssh-public-key has been expired. Please click <a href="{{ user_pubkey_update }}"> this link </a>to update your ssh-public-key.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -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 %}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -89,6 +89,10 @@
|
|||
<td>{% trans 'Role' %}:</td>
|
||||
<td><b>{{ user_object.get_role_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Enable OTP' %}:</td>
|
||||
<td><b>{{ user_object.enable_otp|yesno:"Yes,No,Unknown"}}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date expired' %}:</td>
|
||||
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
<label>{{ user.public_key_obj.hash_md5 }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Update public key' %}</h3>
|
||||
{% bootstrap_field form.public_key layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue