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):
|
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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
@ -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 "文件错误"
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in New Issue