提交 3aea9941 编写于 作者: baltery's avatar baltery

asset import

上级 c1c9c7b6
......@@ -309,4 +309,8 @@ class AssetTagForm(forms.ModelForm):
}
help_texts = {
'name': '* required',
}
\ No newline at end of file
}
class FileForm(forms.Form):
file = forms.FileField()
\ No newline at end of file
......@@ -309,12 +309,12 @@ class Asset(models.Model):
cabinet_no = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Cabinet number'))
cabinet_pos = models.IntegerField(null=True, blank=True, verbose_name=_('Cabinet position'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
status = models.CharField(choices=STATUS_CHOICES, max_length=1, null=True, blank=True,
default='I', verbose_name=_('Asset status'))
status = models.CharField(choices=STATUS_CHOICES, max_length=8, null=True, blank=True,
default='In use', verbose_name=_('Asset status'))
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
default='Server', verbose_name=_('Asset type'),)
env = models.CharField(choices=ENV_CHOICES, max_length=8, blank=True, null=True,
default='P', verbose_name=_('Asset environment'),)
default='Prod', verbose_name=_('Asset environment'),)
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
......
......@@ -43,7 +43,6 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
# system_users = SystemUserSerializer(many=True, read_only=True)
# admin_user = AdminUserSerializer(many=False, read_only=True)
hardware = serializers.SerializerMethodField()
type_display = serializers.SerializerMethodField()
class Meta(object):
model = Asset
......@@ -51,15 +50,16 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod
def get_hardware(obj):
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
@staticmethod
def get_type_display(obj):
if obj.type:
return obj.type.value
if obj.cpu:
return '%s %s %s' % (obj.cpu, obj.memory, obj.disk)
else:
return ''
def get_field_names(self, declared_fields, info):
fields = super(AssetSerializer, self).get_field_names(declared_fields, info)
fields.extend(['get_type_display', 'get_env_display'])
return fields
class AssetGrantedSerializer(serializers.ModelSerializer):
system_users = SystemUserSerializer(many=True, read_only=True)
......
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_import_modal{% endblock %}
{% block modal_title%}{% trans "Import asset" %}{% endblock %}
{% block modal_body %}
<p class="text-success">{% trans "Download template or use export excel format" %}</p>
<form method="post" action="{% url 'assets:asset-import' %}" id="fm_asset_import" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label class="control-label" for="id_assets">{% trans "Template" %}</label>
<a href="{{ MEDIA_URL }}files/asset_import_template.xlsx" style="display: block">{% trans 'Download' %}</a>
</div>
<div class="form-group">
<label class="control-label" for="id_users">{% trans "Asset excel file" %}</label>
<input id="id_assets" type="file" name="file" />
</div>
</form>
<p>
<p class="text-success" id="id_created"></p>
<p id="id_created_detail"></p>
<p class="text-warning" id="id_updated"></p>
<p id="id_updated_detail"></p>
<p class="text-danger" id="id_failed"></p>
<p id="id_failed_detail"></p>
</p>
{% endblock %}
{% block modal_confirm_id %}btn_asset_import{% endblock %}
......@@ -96,7 +96,7 @@
</tr>
<tr>
<td>{% trans 'Asset status' %}:</td>
<td><b>{{ asset.status }}</b></td>
<td><b>{{ asset.get_status_display() }}</b></td>
</tr>
<tr>
<td>{% trans 'Is active' %}:</td>
......
......@@ -48,7 +48,6 @@
{% block table_container %}
<div class="uc pull-left m-l-5 m-r-5"><a href="{% url "assets:asset-create" %}" class="btn btn-sm btn-primary"> {% trans "Create asset" %} </a></div>
<div class="uc pull-left"><a href="javascript:void(0);" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#asset_import_modal"> {% trans "Import asset" %} </a></div>
<table class="table table-striped table-bordered table-hover " id="asset_list_table" >
<thead>
<tr>
......@@ -81,6 +80,7 @@
</div>
</div>
</div>
{% include 'assets/_asset_import_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
......@@ -100,7 +100,7 @@
}else{
oDiv.style.display = "none";
}
}; //onload;
} //onload;
$(document).ready(function(){
var options = {
......@@ -132,31 +132,51 @@
],
ajax_url: '{% url "api-assets:asset-list" %}',
columns: [{data: "id"}, {data: "hostname" }, {data: "ip" }, {data: "port" },
{data: "type_display" }, {data: "env"}, {data: "hardware"}, {data: "is_active" },
{data: "is_active"}, {data: "id" }],
{data: "get_type_display" }, {data: "get_env_display"}, {data: "hardware"},
{data: "is_active" }, {data: "is_active"}, {data: "id" }],
op_html: $('#actions').html()
};
var table = jumpserver.initDataTable(options);
$('.btn_export').click(function () {
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
var assets = [];
var rows = table.rows('.selected').data();
$.each(rows, function (index, obj) {
assets.push(obj.id)
});
$.ajax({
url: "{% url "assets:asset-export" %}",
method: 'POST',
data: JSON.stringify({assets_id: assets}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
}
})
});
$.ajax({
url: "{% url "assets:export-assets-xlsx" %}",
method: 'POST',
data: JSON.stringify({users_id: users}),
dataType: "json",
success: function (data, textStatus) {
window.open(data.redirect)
},
error: function () {
toastr.error('Export failed');
$('#btn_asset_import').click(function() {
var $form = $('#fm_asset_import');
$form.find('.help-block').remove();
function success (data) {
if (data.valid === false) {
$('<span />', {class: 'help-block text-danger'}).html(data.msg).insertAfter($('#id_users'));
} else {
$('#id_created').html(data.created_info);
$('#id_created_detail').html(data.created.join(', '));
$('#id_updated').html(data.updated_info);
$('#id_updated_detail').html(data.updated.join(', '));
$('#id_failed').html(data.failed_info);
$('#id_failed_detail').html(data.failed.join(', '));
var $data_table = $('#asset_list_table').DataTable();
$data_table.ajax.reload();
}
})
});
}
$form.ajaxSubmit({success: success});
})
});
</script>
......
......@@ -9,6 +9,8 @@ urlpatterns = [
url(r'^$', views.AssetListView.as_view(), name='asset-index'),
url(r'^asset/$', views.AssetListView.as_view(), name='asset-list'),
url(r'^asset/create/$', views.AssetCreateView.as_view(), name='asset-create'),
url(r'^asset/export/$', views.AssetExportView.as_view(), name='asset-export'),
url(r'^asset/import/$', views.BulkImportAssetView.as_view(), name='asset-import'),
url(r'^asset/(?P<pk>[0-9]+)/$', views.AssetDetailView.as_view(), name='asset-detail'),
url(r'^asset/(?P<pk>[0-9]+)/update/$', views.AssetUpdateView.as_view(), name='asset-update'),
url(r'^asset/(?P<pk>[0-9]+)/delete/$', views.AssetDeleteView.as_view(), name='asset-delete'),
......
......@@ -9,6 +9,7 @@ from openpyxl import load_workbook
from django.utils.translation import ugettext as _
from django.conf import settings
from django.db.models import Q
from django.db import IntegrityError
from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy
......@@ -21,10 +22,11 @@ from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.utils import timezone
from common.utils import int_seq
from .utils import CreateAssetTagsMiXin,UpdateAssetTagsMiXin
from .models import Asset, AssetGroup, IDC, AdminUser, SystemUser, Tag
from .forms import *
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none
from .utils import CreateAssetTagsMiXin, UpdateAssetTagsMiXin
from . import forms
from .models import Asset, AssetGroup, AdminUser, IDC, SystemUser, Tag
from .hands import AdminUserRequiredMixin
......@@ -45,7 +47,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView):
model = Asset
tag_type = 'asset'
form_class = AssetCreateForm
form_class = forms.AssetCreateForm
template_name = 'assets/asset_create.html'
success_url = reverse_lazy('assets:asset-list')
......@@ -71,7 +73,7 @@ class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView):
class AssetModalCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, ListView):
model = Asset
form_class = AssetCreateForm
form_class = forms.AssetCreateForm
template_name = 'assets/asset_modal_update.html'
success_url = reverse_lazy('assets:asset-list')
......@@ -99,7 +101,7 @@ class AssetModalCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, ListVie
class AssetUpdateView(AdminUserRequiredMixin, UpdateAssetTagsMiXin, UpdateView):
model = Asset
form_class = AssetCreateForm
form_class = forms.AssetCreateForm
template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list')
new_form = ''
......@@ -226,7 +228,7 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
class AssetGroupCreateView(AdminUserRequiredMixin, CreateView):
model = AssetGroup
form_class = AssetGroupForm
form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list')
#ordering = '-id'
......@@ -287,7 +289,7 @@ class AssetGroupDetailView(AdminUserRequiredMixin, DetailView):
class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
model = AssetGroup
form_class = AssetGroupForm
form_class = forms.AssetGroupForm
template_name = 'assets/asset_group_create.html'
success_url = reverse_lazy('assets:asset-group-list')
......@@ -329,7 +331,7 @@ class IDCListView(AdminUserRequiredMixin, TemplateView):
class IDCCreateView(AdminUserRequiredMixin, CreateView):
model = IDC
form_class = IDCForm
form_class = forms.IDCForm
template_name = 'assets/idc_create_update.html'
success_url = reverse_lazy('assets:idc-list')
......@@ -351,7 +353,7 @@ class IDCCreateView(AdminUserRequiredMixin, CreateView):
class IDCUpdateView(AdminUserRequiredMixin, UpdateView):
model = IDC
form_class = IDCForm
form_class = forms.IDCForm
template_name = 'assets/idc_create_update.html'
context_object_name = 'idc'
success_url = reverse_lazy('assets:idc-list')
......@@ -420,7 +422,7 @@ class AdminUserListView(AdminUserRequiredMixin, TemplateView):
class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = AdminUser
form_class = AdminUserForm
form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list')
......@@ -446,7 +448,7 @@ class AdminUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
class AdminUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = AdminUser
form_class = AdminUserForm
form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html'
def get_context_data(self, **kwargs):
......@@ -504,7 +506,7 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView):
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
model = SystemUser
form_class = SystemUserForm
form_class = forms.SystemUserForm
template_name = 'assets/system_user_create_update.html'
success_url = reverse_lazy('assets:system-user-list')
......@@ -528,7 +530,7 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
class SystemUserUpdateView(AdminUserRequiredMixin, UpdateView):
model = SystemUser
form_class = SystemUserForm
form_class = forms.SystemUserForm
template_name = 'assets/system_user_create_update.html'
def get_context_data(self, **kwargs):
......@@ -630,7 +632,7 @@ class TagsListView(AdminUserRequiredMixin, ListView):
class AssetTagCreateView(AdminUserRequiredMixin, CreateView):
model = Tag
form_class = AssetTagForm
form_class = forms.AssetTagForm
template_name = 'assets/asset_tag_create.html'
success_url = reverse_lazy('assets:asset-tag-list')
#ordering = '-id'
......@@ -679,7 +681,7 @@ class AssetTagDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
class AssetTagUpdateView(AdminUserRequiredMixin, UpdateView):
model = Tag
form_class = AssetTagForm
form_class = forms.AssetTagForm
template_name = 'assets/asset_tag_create.html'
success_url = reverse_lazy('assets:asset-tag-list')
......@@ -707,13 +709,15 @@ class AssetTagDeleteView(AdminUserRequiredMixin, DeleteView):
@method_decorator(csrf_exempt, name='dispatch')
class ExportAssetView(View):
class AssetExportView(View):
@staticmethod
def asset_get_attr(asset, attr):
def get_asset_attr(asset, attr):
if attr in ['admin_user', 'idc']:
return getattr(asset, attr).name
if attr in ['status', 'tyoe', 'env']:
return getattr(asset, 'get_{}_display')
elif attr in ['status', 'type', 'env']:
return getattr(asset, 'get_{}_display'.format(attr))()
else:
return getattr(asset, attr)
def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '')
......@@ -725,16 +729,15 @@ class ExportAssetView(View):
wb = Workbook()
ws = wb.active
ws.title = 'Asset'
header = ['hostname', 'ip', 'port', 'admin_user', 'system_users', 'idc',
'cpu', 'memory', 'disk', 'mac_address', 'other_ip', 'remote_card_ip',
'os', 'cabinet_no', 'cabinet_pos', 'number', 'status', 'type', 'env',
'sn', 'comment']
header = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk',
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no',
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment']
ws.append(header)
for asset in assets:
ws.append([getattr(asset, attr) for attr in header])
ws.append([self.get_asset_attr(asset, attr) for attr in header])
filename = 'users-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
filename = 'assets-{}.xlsx'.format(timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
response = HttpResponse(save_virtual_workbook(wb), content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
return response
......@@ -742,9 +745,89 @@ class ExportAssetView(View):
def post(self, request, *args, **kwargs):
try:
assets_id = json.loads(request.body).get('assets_id', [])
print(assets_id)
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex()
cache.set(spm, assets_id, 300)
url = reverse('users:export-user-csv') + '?spm=%s' % spm
url = reverse('assets:asset-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm
def form_valid(self, form):
try:
wb = load_workbook(form.cleaned_data['file'])
ws = wb.get_active_sheet()
except Exception as e:
print(e)
data = {'valid': False, 'msg': 'Not a valid Excel file'}
return self.render_json_response(data)
rows = ws.rows
header_all = ['hostname', 'ip', 'port', 'admin_user', 'idc', 'cpu', 'memory', 'disk',
'mac_address', 'other_ip', 'remote_card_ip', 'os', 'cabinet_no',
'cabinet_pos', 'number', 'status', 'type', 'env', 'sn', 'comment']
header_min = ['hostname', 'ip', 'port', 'admin_user', 'comment']
header = [col.value for col in next(rows)]
if header not in header_all and header_min not in header:
data = {'valid': False, 'msg': 'Must be same format as template or export file'}
return self.render_json_response(data)
created = []
updated = []
failed = []
for row in rows:
asset_dict = dict(zip(header, [col.value for col in row]))
if asset_dict.get('admin_user', None):
admin_user = get_object_or_none(AdminUser, name=asset_dict['admin_user'])
asset_dict['admin_user'] = admin_user
if asset_dict.get('idc'):
idc = get_object_or_none(IDC, name=asset_dict['idc'])
asset_dict['idc'] = idc
if asset_dict.get('type'):
asset_display_type_map = dict(zip(dict(Asset.TYPE_CHOICES).values, dict(Asset.TYPE_CHOICES).keys()))
asset_type = asset_display_type_map.get(asset_dict['type'], 'Server')
asset_dict['type'] = asset_type
if asset_dict.get('status'):
asset_display_status_map = dict(zip(dict(Asset.STATUS_CHOICES).values,
dict(Asset.STATUS_CHOICES).keys()))
asset_status = asset_display_status_map.get(asset_dict['status'], 'In use')
asset_dict['status'] = asset_status
if asset_dict.get('env'):
asset_display_env_map = dict(zip(dict(Asset.ENV_CHOICES).values(),
dict(Asset.ENV_CHOICES).keys()))
asset_env = asset_display_env_map.get(asset_dict['env'], 'Prod')
asset_dict['env'] = asset_env
try:
Asset.objects.create(**asset_dict)
created.append(asset_dict['ip'])
except IntegrityError as e:
asset = Asset.objects.filter(ip=asset_dict['ip'], port=asset_dict['port'])
if not asset:
failed.append(asset_dict['ip'])
continue
asset.update(**asset_dict)
updated.append(asset_dict['ip'])
except TypeError as e:
print(e)
failed.append(asset_dict['ip'])
data = {
'created': created,
'created_info': 'Created {}'.format(len(created)),
'updated': updated,
'updated_info': 'Updated {}'.format(len(updated)),
'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)),
'valid': True,
'msg': 'Created: {}. Updated: {}, Error: {}'.format(len(created), len(updated), len(failed))
}
return self.render_json_response(data)
此差异已折叠。
......@@ -99,7 +99,7 @@ $(document).ready(function(){
users.push(obj.id)
});
$.ajax({
url: "{% url "users:export-user" %}",
url: "{% url 'users:user-export' %}",
method: 'POST',
data: JSON.stringify({users_id: users}),
dataType: "json",
......
......@@ -23,6 +23,7 @@ urlpatterns = [
name='user-asset-permission-create'),
url(r'^user/(?P<pk>[0-9]+)/assets', views.UserGrantedAssetView.as_view(), name='user-granted-asset'),
url(r'^user/(?P<pk>[0-9]+)/login-history', views.UserDetailView.as_view(), name='user-login-history'),
url(r'^user/export/', views.UserExportView.as_view(), name='user-export'),
url(r'^first-login/$', views.UserFirstLoginView.as_view(), name='user-first-login'),
url(r'^user/import/$', views.BulkImportUserView.as_view(), name='user-import'),
# url(r'^user/(?P<pk>[0-9]+)/assets-perm$', views.UserDetailView.as_view(), name='user-detail'),
......@@ -40,5 +41,4 @@ urlpatterns = [
name='user-group-asset-permission-create'),
url(r'^user-group/(?P<pk>[0-9]+)/assets', views.UserGroupGrantedAssetView.as_view(),
name='user-group-granted-asset'),
url(r'^export/user/', views.ExportUserView.as_view(), name='export-user'),
]
......@@ -557,7 +557,7 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
@method_decorator(csrf_exempt, name='dispatch')
class ExportUserView(View):
class UserExportView(View):
def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '')
users_id = cache.get(spm)
......@@ -588,6 +588,6 @@ class ExportUserView(View):
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().get_hex()
cache.set(spm, users_id, 300)
url = reverse('users:export-user-csv') + '?spm=%s' % spm
url = reverse('users:user-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
......@@ -2,5 +2,5 @@
#
for app in users assets perms audits teminal ops;do
rm -f $app/migrations/000*
rm -f ../apps/$app/migrations/000*
done
#!/bin/bash
#
python ../apps/manage.py shell << EOF
from users.models import *
generate_fake()
from assets.models import *
generate_fake()
EOF
python ../apps/manage.py dbshell << EOF
delete from django_content_type;
delete from auth_permission;
EOF
python ../apps/manage.py dumpdata > ../apps/fixtures/init.json
python ../apps/manage.py dumpdata > ../apps/fixtures/fake.json
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册