Commit 5418d0b4 authored by ibuler's avatar ibuler

[Change] Using csv replace xlsx

parent bcc065eb
...@@ -86,7 +86,6 @@ class Asset(models.Model): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 @@ ...@@ -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>
......
# 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): ...@@ -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',
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no',
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment']
ws.append(header)
for asset in assets: header = [field.verbose_name for field in fields]
ws.append([self.get_asset_attr(asset, attr) for attr in header]) header.append(_('Asset groups'))
writer.writerow(header)
filename = 'assets-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')) for asset in assets:
response = HttpResponse(save_virtual_workbook(wb), content_type='applications/vnd.ms-excel') groups = ','.join([group.name for group in asset.groups.all()])
response['Content-Disposition'] = 'attachment; filename="%s"' % filename data = [getattr(asset, field.name) for field in fields]
data.append(groups)
writer.writerow(data)
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): ...@@ -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]
return self.render_json_response(data) fields = [
field for field in Asset._meta.fields
rows = ws.rows if field.name not in [
header_all = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk', 'date_created'
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no', ]
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment'] ]
header_min = ['hostname', 'ip', 'port', 'admin_user', 'comment'] header_ = csv_data[0]
header = [col.value for col in next(rows)] mapping_reverse = {field.verbose_name: field.name for field in fields}
if not set(header).issubset(set(header_all)) and not set(header).issuperset(set(header_min)): mapping_reverse[_('Asset groups')] = 'groups'
data = {'valid': False, 'msg': 'Must be same format as template or export file'} 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)
created = [] created, updated, failed = [], [], []
updated = [] for row in csv_data[1:]:
failed = [] if set(row) == {''}:
for row in rows: continue
asset_dict = dict(zip(header, [col.value for col in row])) asset_dict = dict(zip(attr, row))
if asset_dict.get('admin_user', None): id_ = asset_dict.pop('id', 0)
admin_user = get_object_or_none(AdminUser, name=asset_dict['admin_user']) asset = get_object_or_none(Asset, id=id_)
asset_dict['admin_user'] = admin_user for k, v in asset_dict.items():
if k == 'idc':
if asset_dict.get('idc'): v = get_object_or_none(IDC, name=v)
idc = get_object_or_none(IDC, name=asset_dict['idc']) elif k == 'is_active':
asset_dict['idc'] = idc v = bool(v)
elif k == 'admin_user':
if asset_dict.get('type'): v = get_object_or_none(AdminUser, name=v)
asset_display_type_map = dict(zip(dict(Asset.TYPE_CHOICES).values(), dict(Asset.TYPE_CHOICES).keys())) elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
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: try:
Asset.objects.create(**asset_dict) v = int(v)
created.append(asset_dict['ip']) except ValueError:
except IntegrityError as e: v = 0
asset = Asset.objects.filter(ip=asset_dict['ip'], port=asset_dict['port']) elif k == 'groups':
groups_name = v.split(',')
v = AssetGroup.objects.filter(name__in=groups_name)
else:
continue
asset_dict[k] = v
if not asset: if not asset:
failed.append(asset_dict['ip']) try:
groups = asset_dict.pop('groups')
asset = Asset.objects.create(**asset_dict)
asset.groups.set(groups)
created.append(asset_dict['hostname'])
except IndexError as e:
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
else:
for k, v in asset_dict.items():
if k == 'groups':
asset.groups.set(v)
continue continue
asset.update(**asset_dict) if v:
updated.append(asset_dict['ip']) setattr(asset, k, v)
except TypeError as e: try:
print(e) asset.save()
failed.append(asset_dict['ip']) 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): ...@@ -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/' ...@@ -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'), ]
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -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 "登录地址" ...@@ -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" ...@@ -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 "其它" ...@@ -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 "重置" ...@@ -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 "主机名原始" ...@@ -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 "选择系统用户" ...@@ -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" ...@@ -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 "添加到资产组" ...@@ -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" ...@@ -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 "批量更新" ...@@ -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删除失败" ...@@ -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" ...@@ -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" ...@@ -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 "添加资产组" ...@@ -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 "创建授权规则" ...@@ -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 "注销登录" ...@@ -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 "" ...@@ -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 "" ...@@ -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" ...@@ -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 "组" ...@@ -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 "首次登陆" ...@@ -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" ...@@ -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 "设置" ...@@ -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!" ...@@ -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 "创建用户" ...@@ -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密钥" ...@@ -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 "指纹" ...@@ -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 "用户组列表" ...@@ -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 "密码不一致" ...@@ -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 "文件错误"
{% 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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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): ...@@ -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 ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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]
return self.render_json_response(data) mapping_reverse = {_(v): k for k, v in USER_ATTR_MAPPING}
user_attr = [mapping_reverse.get(n, None) for n in header_]
rows = ws.rows if None in user_attr:
header_need = ["name", 'username', 'email', 'groups', data = {'valid': False,
"role", "phone", "wechat", "comment"] 'msg': 'Must be same format as '
header = [col.value for col in next(rows)]
print(header)
if header != header_need:
data = {'valid': False, 'msg': 'Must be same format as '
'template or export file'} 'template or export file'}
return self.render_json_response(data) return self.render_json_response(data)
created = [] created, updated, failed = [], [], []
updated = [] for row in csv_data[1:]:
failed = [] if set(row) == {''}:
for row in rows: continue
user_dict = dict(zip(header, [col.value for col in row])) user_dict = dict(zip(user_attr, 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
username = user_dict['username']
user = get_object_or_none(User, username=username)
if not user:
try: try:
user = User.objects.create(**user_dict) user = User.objects.create(**user_dict)
user_add_success_next(user)
created.append(user_dict['username']) created.append(user_dict['username'])
except User.IntegrityError as e: user_add_success_next(user)
user = User.objects.filter(username=user_dict['username']) except Exception as e:
if not user: failed.append('%s: %s' % (user_dict['username'], str(e)))
failed.append(user_dict['username']) else:
continue for k, v in user_dict.items():
user.update(**user_dict) if v:
user = user[0] setattr(user, k, v)
try:
user.save()
updated.append(user_dict['username']) updated.append(user_dict['username'])
except TypeError as e: except Exception as e:
print(e) failed.append('%s: %s' % (user_dict['username'], str(e)))
failed.append(user_dict['username'])
user = None
if user and groups: if user and groups:
user.groups.add(*tuple(groups)) user.groups = groups
user.save() user.save()
data = { data = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment