[Change] Using csv replace xlsx

pull/417/head
ibuler 2017-04-09 00:45:28 +08:00
parent bcc065eb8f
commit 5418d0b44c
16 changed files with 251 additions and 248 deletions

View File

@ -86,7 +86,6 @@ class Asset(models.Model):
def __unicode__(self): def __unicode__(self):
return '%s <%s: %s>' % (self.hostname, self.ip, self.port) return '%s <%s: %s>' % (self.hostname, self.ip, self.port)
__str__ = __unicode__ __str__ = __unicode__
@property @property

View File

@ -23,6 +23,7 @@ class AssetGroup(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
__str__ = __unicode__
class Meta: class Meta:
ordering = ['name'] ordering = ['name']

View File

@ -36,6 +36,7 @@ class IDC(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
__str__ = __unicode__
@classmethod @classmethod
def initial(cls): def initial(cls):

View File

@ -50,7 +50,6 @@ class AdminUser(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
__str__ = __unicode__ __str__ = __unicode__
@property @property

View File

@ -7,11 +7,14 @@
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label> <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>
<div class="form-group"> <div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset excel file" %}</label> <label class="control-label" for="id_users">{% trans "Asset excel file" %}</label>
<input id="id_assets" type="file" name="file" /> <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> </div>
</form> </form>
<p> <p>

View File

@ -1,14 +1,14 @@
# coding:utf-8 # coding:utf-8
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import csv
import json import json
import uuid 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.conf import settings
from django.db import IntegrityError from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from django.views.generic import TemplateView, ListView, View from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -206,45 +206,42 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(View): 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): def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
assets_id = cache.get(spm) assets_id = cache.get(spm, [Asset.objects.first().id])
if not assets_id and not isinstance(assets_id, list): print(assets_id)
return HttpResponse('May be expired', status=404) 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) assets = Asset.objects.filter(id__in=assets_id)
wb = Workbook() writer = csv.writer(response, dialect='excel',
ws = wb.active quoting=csv.QUOTE_MINIMAL)
ws.title = 'Asset'
header = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'memory', 'disk', header = [field.verbose_name for field in fields]
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no', header.append(_('Asset groups'))
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment'] writer.writerow(header)
ws.append(header)
for asset in assets: for asset in assets:
ws.append([self.get_asset_attr(asset, attr) for attr in header]) groups = ','.join([group.name for group in asset.groups.all()])
data = [getattr(asset, field.name) for field in fields]
filename = 'assets-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) data.append(groups)
response = HttpResponse(save_virtual_workbook(wb), content_type='applications/vnd.ms-excel') writer.writerow(data)
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response return response
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
try: try:
assets_id = json.loads(request.body).get('assets_id', []) assets_id = json.loads(request.body).get('assets_id', [])
print(assets_id)
except ValueError: except ValueError:
return HttpResponse('Json object not valid', status=400) return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex() spm = uuid.uuid4().hex
cache.set(spm, assets_id, 300) cache.set(spm, assets_id, 300)
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url}) return JsonResponse({'redirect': url})
@ -254,67 +251,74 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm form_class = forms.FileForm
def form_valid(self, form): def form_valid(self, form):
try: file = form.cleaned_data['file']
wb = load_workbook(form.cleaned_data['file']) data = file.read().decode('utf-8').strip(
ws = wb.get_active_sheet() codecs.BOM_UTF8.decode('utf-8'))
except Exception as e: csv_file = StringIO(data)
print(e) reader = csv.reader(csv_file)
data = {'valid': False, 'msg': 'Not a valid Excel 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) return self.render_json_response(data)
rows = ws.rows created, updated, failed = [], [], []
header_all = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk', for row in csv_data[1:]:
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no', if set(row) == {''}:
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment'] continue
header_min = ['hostname', 'ip', 'port', 'admin_user', 'comment'] asset_dict = dict(zip(attr, row))
header = [col.value for col in next(rows)] id_ = asset_dict.pop('id', 0)
if not set(header).issubset(set(header_all)) and not set(header).issuperset(set(header_min)): asset = get_object_or_none(Asset, id=id_)
data = {'valid': False, 'msg': 'Must be same format as template or export file'} for k, v in asset_dict.items():
return self.render_json_response(data) if k == 'idc':
v = get_object_or_none(IDC, name=v)
created = [] elif k == 'is_active':
updated = [] v = bool(v)
failed = [] elif k == 'admin_user':
for row in rows: v = get_object_or_none(AdminUser, name=v)
asset_dict = dict(zip(header, [col.value for col in row])) elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
if asset_dict.get('admin_user', None): try:
admin_user = get_object_or_none(AdminUser, name=asset_dict['admin_user']) v = int(v)
asset_dict['admin_user'] = admin_user except ValueError:
v = 0
if asset_dict.get('idc'): elif k == 'groups':
idc = get_object_or_none(IDC, name=asset_dict['idc']) groups_name = v.split(',')
asset_dict['idc'] = idc v = AssetGroup.objects.filter(name__in=groups_name)
else:
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'])
continue continue
asset.update(**asset_dict) asset_dict[k] = v
updated.append(asset_dict['ip'])
except TypeError as e: if not asset:
print(e) try:
failed.append(asset_dict['ip']) 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 = { data = {
'created': created, 'created': created,
@ -324,7 +328,8 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
'failed': failed, 'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)), 'failed_info': 'Failed {}'.format(len(failed)),
'valid': True, '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) return self.render_json_response(data)

View File

@ -260,7 +260,7 @@ MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/') + '/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/') + '/'
# Use django-bootstrap-form to format template, input max width arg # 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 # Init data or generate fake data source for development
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ] FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<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 #: assets/templates/assets/system_user_list.html:21 perms/models.py:40
#: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/asset_permission_detail.html:98
#: users/models/group.py:19 users/models/user.py:43 #: 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_detail.html:70
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:118 #: users/templates/users/user_profile.html:118
@ -134,7 +134,7 @@ msgstr "在线session"
#: assets/templates/assets/asset_list.html:74 perms/models.py:33 #: assets/templates/assets/asset_list.html:74 perms/models.py:33
#: perms/templates/perms/asset_permission_create_update.html:47 #: perms/templates/perms/asset_permission_create_update.html:47
#: users/templates/users/_select_user_modal.html:18 #: 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_list.html:29
#: users/templates/users/user_profile.html:63 #: users/templates/users/user_profile.html:63
msgid "Active" msgid "Active"
@ -234,8 +234,8 @@ msgstr "其它"
#: assets/templates/assets/system_user_detail.html:137 #: assets/templates/assets/system_user_detail.html:137
#: perms/templates/perms/asset_permission_create_update.html:67 #: perms/templates/perms/asset_permission_create_update.html:67
#: users/templates/users/_user.html:49 #: users/templates/users/_user.html:49
#: users/templates/users/user_detail.html:158 #: users/templates/users/user_detail.html:162
#: users/templates/users/user_detail.html:166 #: users/templates/users/user_detail.html:170
#: users/templates/users/user_password_update.html:58 #: users/templates/users/user_password_update.html:58
#: users/templates/users/user_profile.html:139 #: users/templates/users/user_profile.html:139
#: users/templates/users/user_profile.html:147 #: users/templates/users/user_profile.html:147
@ -259,7 +259,7 @@ msgstr "重置"
#: audits/templates/audits/proxy_log_online_list.html:124 #: audits/templates/audits/proxy_log_online_list.html:124
#: perms/templates/perms/asset_permission_create_update.html:68 #: perms/templates/perms/asset_permission_create_update.html:68
#: users/templates/users/_user.html:50 #: 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/forgot_password.html:44
#: users/templates/users/user_asset_permission.html:100 #: users/templates/users/user_asset_permission.html:100
#: users/templates/users/user_group_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/idc_detail.html:93
#: assets/templates/assets/system_user_detail.html:94 perms/models.py:37 #: assets/templates/assets/system_user_detail.html:94 perms/models.py:37
#: perms/templates/perms/asset_permission_detail.html:94 #: 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" msgid "Created by"
msgstr "创建者" msgstr "创建者"
@ -733,22 +733,17 @@ msgstr "选择系统用户"
msgid "Import asset" msgid "Import asset"
msgstr "导入资产" msgstr "导入资产"
#: assets/templates/assets/_asset_import_modal.html:6 #: assets/templates/assets/_asset_import_modal.html:9
#: 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
#: users/templates/users/_user_import_modal.html:10 #: users/templates/users/_user_import_modal.html:10
msgid "Template" msgid "Template"
msgstr "模板" 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 #: users/templates/users/_user_import_modal.html:11
msgid "Download" msgid "Download"
msgstr "下载" msgstr "下载"
#: assets/templates/assets/_asset_import_modal.html:14 #: assets/templates/assets/_asset_import_modal.html:13
msgid "Asset excel file" msgid "Asset excel file"
msgstr "资产excel" msgstr "资产excel"
@ -888,13 +883,13 @@ msgid "Platform"
msgstr "" msgstr ""
#: assets/templates/assets/asset_detail.html:152 #: 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 #: users/templates/users/user_profile.html:88
msgid "Date joined" msgid "Date joined"
msgstr "创建日期" msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:168 #: 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 #: users/templates/users/user_profile.html:130
msgid "Quick modify" msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
@ -929,9 +924,9 @@ msgstr "添加到资产组"
#: assets/templates/assets/system_user_asset.html:96 #: assets/templates/assets/system_user_asset.html:96
#: assets/templates/assets/system_user_list.html:102 #: assets/templates/assets/system_user_list.html:102
#: assets/templates/assets/user_asset_list.html:167 templates/_modal.html:16 #: 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:341
#: users/templates/users/user_detail.html:362 #: users/templates/users/user_detail.html:366
#: users/templates/users/user_detail.html:385 #: users/templates/users/user_detail.html:389
#: users/templates/users/user_group_create_update.html:45 #: users/templates/users/user_group_create_update.html:45
#: users/templates/users/user_group_list.html:92 #: users/templates/users/user_group_list.html:92
#: users/templates/users/user_list.html:171 #: users/templates/users/user_list.html:171
@ -940,8 +935,8 @@ msgid "Confirm"
msgstr "确认" msgstr "确认"
#: assets/templates/assets/asset_detail.html:374 #: assets/templates/assets/asset_detail.html:374
#: users/templates/users/user_detail.html:271 #: users/templates/users/user_detail.html:275
#: users/templates/users/user_detail.html:284 #: users/templates/users/user_detail.html:288
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
@ -985,8 +980,8 @@ msgstr "批量更新"
#: assets/templates/assets/idc_list.html:94 #: assets/templates/assets/idc_list.html:94
#: assets/templates/assets/system_user_list.html:97 #: assets/templates/assets/system_user_list.html:97
#: assets/templates/assets/user_asset_list.html:162 #: assets/templates/assets/user_asset_list.html:162
#: users/templates/users/user_detail.html:332 #: users/templates/users/user_detail.html:336
#: users/templates/users/user_detail.html:357 #: users/templates/users/user_detail.html:361
#: users/templates/users/user_group_list.html:87 #: users/templates/users/user_group_list.html:87
#: users/templates/users/user_list.html:166 #: users/templates/users/user_list.html:166
msgid "Are you sure?" msgid "Are you sure?"
@ -1149,10 +1144,8 @@ msgstr "IDC删除失败"
#: assets/templates/assets/system_user_asset.html:20 #: assets/templates/assets/system_user_asset.html:20
#: assets/templates/assets/system_user_detail.html:21 #: assets/templates/assets/system_user_detail.html:21
#, fuzzy
#| msgid "Attach Asset"
msgid "Attached assets" msgid "Attached assets"
msgstr "关联资产" msgstr "关联的资产"
#: assets/templates/assets/system_user_asset.html:28 #: assets/templates/assets/system_user_asset.html:28
msgid "Assets of " msgid "Assets of "
@ -1179,7 +1172,6 @@ msgid "Attach AssetGroup"
msgstr "添加到资产组" msgstr "添加到资产组"
#: assets/templates/assets/system_user_detail.html:79 #: assets/templates/assets/system_user_detail.html:79
#: templates/_header_bar.html:41
msgid "Home" msgid "Home"
msgstr "" msgstr ""
@ -1494,7 +1486,7 @@ msgid "Select user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: perms/models.py:35 perms/templates/perms/asset_permission_detail.html:86 #: 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 #: users/templates/users/user_profile.html:96
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
@ -1527,7 +1519,7 @@ msgstr "添加资产组"
#: perms/templates/perms/asset_permission_asset.html:146 #: perms/templates/perms/asset_permission_asset.html:146
#: perms/templates/perms/asset_permission_detail.html:170 #: perms/templates/perms/asset_permission_detail.html:170
#: users/templates/users/user_detail.html:194 #: users/templates/users/user_detail.html:198
msgid "Join" msgid "Join"
msgstr "加入" msgstr "加入"
@ -1570,7 +1562,7 @@ msgstr "创建授权规则"
#: perms/templates/perms/asset_permission_list.html:13 templates/_nav.html:13 #: 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/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 #: users/templates/users/user_list.html:28
msgid "User group" msgid "User group"
msgstr "用户组" msgstr "用户组"
@ -1659,18 +1651,18 @@ msgstr "注销登录"
msgid "Login" msgid "Login"
msgstr "登录" msgstr "登录"
#: templates/_header_bar.html:41 templates/_nav.html:4
msgid "Dashboard"
msgstr "仪表盘"
#: templates/_modal.html:15 #: templates/_modal.html:15
msgid "Close" msgid "Close"
msgstr "关闭" msgstr "关闭"
#: templates/_nav.html:4
msgid "Dashboard"
msgstr "仪表盘"
#: templates/_nav.html:9 users/templates/users/user_group_create_update.html:28 #: 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: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/group.py:105 users/views/login.py:185 users/views/user.py:58
#: users/views/user.py:70 users/views/user.py:106 users/views/user.py:118 #: users/views/user.py:74 users/views/user.py:110 users/views/user.py:122
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
@ -1734,22 +1726,20 @@ msgid ""
" " " "
msgstr "" msgstr ""
"\n" "\n"
" 你的信息不完整,请点击 <a href=" " 你的信息不完整,请点击 <a href=\"%(first_login_url)s\"> 链接 "
"\"%(first_login_url)s\"> 链接 </a>to 补充完整\n" "</a> 补充完整\n"
" " " "
#: templates/base.html:37 #: templates/base.html:37
#, python-format
msgid "" msgid ""
"\n" "\n"
" Your ssh-public-key has been expired. Please click <a href=" " 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 "" msgstr ""
"\n" "\n"
" 你的SSH key已经过期,点击 <a href=" " 你的SSH key已经过期,点击 <a href=\"%(user_pubkey_update)s\"> 链接 "
"\"%(profile_url)s\"> 链接 </a>更新 \n" "</a>更新 \n"
" " " "
#: templates/captcha/image.html:3 #: templates/captcha/image.html:3
@ -1815,7 +1805,7 @@ msgstr ""
msgid "Invalid token or cache refreshed." msgid "Invalid token or cache refreshed."
msgstr "" 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" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
@ -1876,7 +1866,8 @@ msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:39 users/templates/users/_user.html:36 #: 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" msgid "Enable OTP"
msgstr "二次验证" msgstr "二次验证"
@ -1917,6 +1908,10 @@ msgstr "组"
msgid "Import user" msgid "Import user"
msgstr "导入" 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 #: users/templates/users/_user_import_modal.html:14
msgid "Users excel file" msgid "Users excel file"
msgstr "用户excel" msgstr "用户excel"
@ -1934,11 +1929,11 @@ msgstr "首次登陆"
msgid "Step" msgid "Step"
msgstr "Step" msgstr "Step"
#: users/templates/users/first_login.html:57 #: users/templates/users/first_login.html:59
msgid "First step" msgid "First step"
msgstr "第一步" msgstr "第一步"
#: users/templates/users/first_login.html:58 #: users/templates/users/first_login.html:60
msgid "Prev step" msgid "Prev step"
msgstr "上一步" msgstr "上一步"
@ -1968,8 +1963,8 @@ msgid "Captcha invalid"
msgstr "验证码错误" msgstr "验证码错误"
#: users/templates/users/reset_password.html:45 #: users/templates/users/reset_password.html:45
#: users/templates/users/user_detail.html:155 #: users/templates/users/user_detail.html:159
#: users/templates/users/user_detail.html:323 #: users/templates/users/user_detail.html:327
#: users/templates/users/user_profile.html:136 users/utils.py:71 #: users/templates/users/user_profile.html:136 users/utils.py:71
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
@ -1987,7 +1982,7 @@ msgstr "设置"
#: users/templates/users/user_granted_asset.html:18 #: users/templates/users/user_granted_asset.html:18
#: users/templates/users/user_group_asset_permission.html:18 #: users/templates/users/user_group_asset_permission.html:18
#: users/templates/users/user_group_granted_asset.html:18 #: users/templates/users/user_group_granted_asset.html:18
#: users/views/user.py:119 #: users/views/user.py:123
msgid "User detail" msgid "User detail"
msgstr "用户详情" msgstr "用户详情"
@ -2015,7 +2010,7 @@ msgid "Revoke Successfully!"
msgstr "回收成功" msgstr "回收成功"
#: users/templates/users/user_create.html:4 #: 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" msgid "Create user"
msgstr "创建用户" msgstr "创建用户"
@ -2023,47 +2018,47 @@ msgstr "创建用户"
msgid "Reset link will be generated and sent to the user. " msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户" msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:105 #: users/templates/users/user_detail.html:109
#: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:92
msgid "Last login" msgid "Last login"
msgstr "最后登录" msgstr "最后登录"
#: users/templates/users/user_detail.html:163 #: users/templates/users/user_detail.html:167
msgid "Reset ssh key" msgid "Reset ssh key"
msgstr "重置密钥" 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." msgid "An e-mail has been sent to the user\\'s mailbox."
msgstr "已发送邮件到用户邮箱" msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:333 #: users/templates/users/user_detail.html:337
msgid "" msgid ""
"This will reset the user's password. A password-reset email will be sent to " "This will reset the user's password. A password-reset email will be sent to "
"the user\\'s mailbox." "the user\\'s mailbox."
msgstr "" msgstr ""
#: users/templates/users/user_detail.html:347 #: users/templates/users/user_detail.html:351
msgid "" msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform " "The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key." "the user to update his new ssh public key."
msgstr "" msgstr ""
#: users/templates/users/user_detail.html:348 #: users/templates/users/user_detail.html:352
#: users/templates/users/user_profile.html:144 #: users/templates/users/user_profile.html:144
msgid "Reset SSH public key" msgid "Reset SSH public key"
msgstr "重置SSH密钥" msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:358 #: users/templates/users/user_detail.html:362
msgid "This will reset the user\\" msgid "This will reset the user\\"
msgstr "重置" msgstr "重置"
#: users/templates/users/user_detail.html:375 #: users/templates/users/user_detail.html:379
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:170
msgid "Successfully updated the SSH public key." msgid "Successfully updated the SSH public key."
msgstr "" msgstr ""
#: users/templates/users/user_detail.html:376
#: users/templates/users/user_detail.html:380 #: 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:171
#: users/templates/users/user_profile.html:176 #: users/templates/users/user_profile.html:176
msgid "User SSH Public Key Update" msgid "User SSH Public Key Update"
@ -2071,23 +2066,17 @@ msgstr "ssh密钥"
#: users/templates/users/user_granted_asset.html:32 #: users/templates/users/user_granted_asset.html:32
#: users/templates/users/user_group_granted_asset.html:32 #: users/templates/users/user_group_granted_asset.html:32
#, fuzzy
#| msgid "Asset group list"
msgid "Assets granted of " msgid "Assets granted of "
msgstr "资产组列表" msgstr "授权资产"
#: users/templates/users/user_granted_asset.html:68 #: users/templates/users/user_granted_asset.html:68
#: users/templates/users/user_group_granted_asset.html:68 #: users/templates/users/user_group_granted_asset.html:68
#, fuzzy
#| msgid "Asset group list"
msgid "Asset groups granted of " msgid "Asset groups granted of "
msgstr "资产组列表" msgstr "授权资产组"
#: users/templates/users/user_group_asset_permission.html:71 #: users/templates/users/user_group_asset_permission.html:71
#, fuzzy
#| msgid "Create perm"
msgid "Quick create permission for user group" msgid "Quick create permission for user group"
msgstr "创建权限" msgstr "快速授权"
#: users/templates/users/user_group_create_update.html:30 #: users/templates/users/user_group_create_update.html:30
msgid "Select User" msgid "Select User"
@ -2194,7 +2183,7 @@ msgstr "指纹"
msgid "Update public key" msgid "Update public key"
msgstr "更新密钥" 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" msgid "Update user"
msgstr "编辑用户" msgstr "编辑用户"
@ -2336,12 +2325,6 @@ msgstr "用户组列表"
msgid "Update user group" msgid "Update user group"
msgstr "编辑用户组" msgstr "编辑用户组"
#: users/views/group.py:106
#, fuzzy
#| msgid "User group detail"
msgid "User Group Detail"
msgstr "资产组详情"
#: users/views/login.py:76 #: users/views/login.py:76
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
@ -2383,16 +2366,15 @@ msgstr "密码不一致"
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"
#: users/views/user.py:55 #: users/views/user.py:59
msgid "User list" msgid "User list"
msgstr "用户列表" msgstr "用户列表"
#: users/views/user.py:66 users/views/user.py:334 #: users/views/user.py:70 users/views/user.py:344
#, python-brace-format #, python-brace-format
msgid "Create user <a href=\"{url}\">{name}</a> successfully." msgid "Create user <a href=\"{url}\">{name}</a> successfully."
msgstr "创建用户 <a href=\"{url}\">{name}</a> 成功" msgstr "创建用户 <a href=\"{url}\">{name}</a> 成功"
#: users/views/user.py:175 #: users/views/user.py:188
msgid "Invalid file." msgid "Invalid file."
msgstr "文件错误" msgstr "文件错误"

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% load bootstrap %} {% load bootstrap3 %}
{% block custom_head_css_js %} {% block custom_head_css_js %}
<link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet"> <link href="{% static "css/plugins/select2/select2.min.css" %}" rel="stylesheet">
<script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script> <script src="{% static "js/plugins/select2/select2.full.min.js" %}"></script>

View File

@ -33,9 +33,9 @@
{% block update_public_key_message %} {% block update_public_key_message %}
{% if user.is_authenticated and not user.is_public_key_valid %} {% if user.is_authenticated and not user.is_public_key_valid %}
<div class="alert alert-danger" style="margin: 20px auto 0px"> <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 %} {% 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 %} {% endblocktrans %}
</div> </div>
{% endif %} {% endif %}

View File

@ -44,10 +44,12 @@
{% if wizard.form.forms %} {% if wizard.form.forms %}
{{ wizard.form.management_form }} {{ wizard.form.management_form }}
{% for form in wizard.form.forms %} {% for form in wizard.form.forms %}
{{ form|bootstrap }} {% bootstrap_form form %}
{# {{ form|bootstrap }}#}
{% endfor %} {% endfor %}
{% else %} {% else %}
{{ wizard.form|bootstrap }} {# {{ wizard.form|bootstrap }}#}
{% bootstrap_form wizard.form %}
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -89,6 +89,10 @@
<td>{% trans 'Role' %}:</td> <td>{% trans 'Role' %}:</td>
<td><b>{{ user_object.get_role_display }}</b></td> <td><b>{{ user_object.get_role_display }}</b></td>
</tr> </tr>
<tr>
<td>{% trans 'Enable OTP' %}:</td>
<td><b>{{ user_object.enable_otp|yesno:"Yes,No,Unknown"}}</b></td>
</tr>
<tr> <tr>
<td>{% trans 'Date expired' %}:</td> <td>{% trans 'Date expired' %}:</td>
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td> <td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>

View File

@ -61,6 +61,7 @@
<label>{{ user.public_key_obj.hash_md5 }}</label> <label>{{ user.public_key_obj.hash_md5 }}</label>
</div> </div>
</div> </div>
<div class="hr-line-dashed"></div>
<h3>{% trans 'Update public key' %}</h3> <h3>{% trans 'Update public key' %}</h3>
{% bootstrap_field form.public_key layout="horizontal" %} {% bootstrap_field form.public_key layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>

View File

@ -103,7 +103,7 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
users = User.objects.exclude(id__in=self.object.users.all()) users = User.objects.exclude(id__in=self.object.users.all())
context = { context = {
'app': _('Users'), 'app': _('Users'),
'action': _('User Group Detail'), 'action': _('User group detail'),
'users': users, 'users': users,
} }
kwargs.update(context) kwargs.update(context)

View File

@ -4,10 +4,9 @@ from __future__ import unicode_literals
import json import json
import uuid import uuid
import csv
from openpyxl import load_workbook import codecs
from openpyxl import Workbook from io import StringIO
from openpyxl.writer.excel import save_virtual_workbook
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
@ -31,7 +30,7 @@ from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, user_add_success_next from ..utils import AdminUserRequiredMixin, user_add_success_next
from common.mixins import JSONResponseMixin 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 from perms.models import AssetPermission
__all__ = ['UserListView', 'UserCreateView', 'UserDetailView', __all__ = ['UserListView', 'UserCreateView', 'UserDetailView',
@ -123,34 +122,44 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
return super(UserDetailView, self).get_context_data(**kwargs) 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') @method_decorator(csrf_exempt, name='dispatch')
class UserExportView(View): class UserExportView(View):
def get(self, request): def get(self, request):
mapping = [
(k, _(v)) for k, v in USER_ATTR_MAPPING
]
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
users_id = cache.get(spm) users_id = cache.get(spm, ['1'])
if not users_id and not isinstance(users_id, list): filename = 'users-{}.csv'.format(
return HttpResponse('May be expired', status=404) 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) users = User.objects.filter(id__in=users_id)
print(users) writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
wb = Workbook()
ws = wb.active header = [v for k, v in mapping]
ws.title = 'User' writer.writerow(header)
header = ["name", 'username', 'email', 'groups',
"role", "phone", "wechat", "comment"]
ws.append(header)
for user in users: for user in users:
ws.append([user.name, user.username, user.email, groups = ','.join([group.name for group in user.groups.all()])
','.join([group.name for group in user.groups.all()]), writer.writerow([
user.role, user.phone, user.wechat, user.comment]) 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 return response
def post(self, request): def post(self, request):
@ -158,7 +167,7 @@ class UserExportView(View):
users_id = json.loads(request.body).get('users_id', []) users_id = json.loads(request.body).get('users_id', [])
except ValueError: except ValueError:
return HttpResponse('Json object not valid', status=400) return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex() spm = uuid.uuid4().hex
cache.set(spm, users_id, 300) cache.set(spm, users_id, 300)
url = reverse('users:user-export') + '?spm=%s' % spm url = reverse('users:user-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url}) return JsonResponse({'redirect': url})
@ -179,54 +188,51 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
return self.render_json_response(data) return self.render_json_response(data)
def form_valid(self, form): def form_valid(self, form):
try: file = form.cleaned_data['file']
wb = load_workbook(form.cleaned_data['file']) data = file.read().decode('utf-8').strip(codecs.BOM_UTF8.decode('utf-8'))
ws = wb.get_active_sheet() csv_file = StringIO(data)
except Exception as e: reader = csv.reader(csv_file)
print(e) csv_data = [row for row in reader]
data = {'valid': False, 'msg': 'Not a valid Excel file'} 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) return self.render_json_response(data)
rows = ws.rows created, updated, failed = [], [], []
header_need = ["name", 'username', 'email', 'groups', for row in csv_data[1:]:
"role", "phone", "wechat", "comment"] if set(row) == {''}:
header = [col.value for col in next(rows)] continue
print(header) user_dict = dict(zip(user_attr, row))
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]))
groups_name = user_dict.pop('groups') groups_name = user_dict.pop('groups')
if groups_name: if groups_name:
groups_name = groups_name.split(',') groups_name = groups_name.split(',')
groups = UserGroup.objects.filter(name__in=groups_name) groups = UserGroup.objects.filter(name__in=groups_name)
else: else:
groups = None groups = None
try: username = user_dict['username']
user = User.objects.create(**user_dict) user = get_object_or_none(User, username=username)
user_add_success_next(user) if not user:
created.append(user_dict['username']) try:
except User.IntegrityError as e: user = User.objects.create(**user_dict)
user = User.objects.filter(username=user_dict['username']) created.append(user_dict['username'])
if not user: user_add_success_next(user)
failed.append(user_dict['username']) except Exception as e:
continue failed.append('%s: %s' % (user_dict['username'], str(e)))
user.update(**user_dict) else:
user = user[0] for k, v in user_dict.items():
updated.append(user_dict['username']) if v:
except TypeError as e: setattr(user, k, v)
print(e) try:
failed.append(user_dict['username']) user.save()
user = None updated.append(user_dict['username'])
except Exception as e:
failed.append('%s: %s' % (user_dict['username'], str(e)))
if user and groups: if user and groups:
user.groups.add(*tuple(groups)) user.groups = groups
user.save() user.save()
data = { data = {