asset.py 12.2 KB
Newer Older
baltery's avatar
baltery 已提交
1 2
# coding:utf-8
from __future__ import absolute_import, unicode_literals
baltery's avatar
baltery 已提交
3 4

import csv
baltery's avatar
baltery 已提交
5 6
import json
import uuid
baltery's avatar
baltery 已提交
7
import codecs
C
Caijun 已提交
8
import chardet
baltery's avatar
baltery 已提交
9
from io import StringIO
baltery's avatar
baltery 已提交
10
from collections import defaultdict
baltery's avatar
baltery 已提交
11 12

from django.conf import settings
baltery's avatar
baltery 已提交
13
from django.utils.translation import ugettext_lazy as _
baltery's avatar
baltery 已提交
14 15 16 17 18 19 20 21 22 23
from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
baltery's avatar
baltery 已提交
24
from django.shortcuts import get_object_or_404, redirect, reverse
baltery's avatar
baltery 已提交
25 26 27 28 29 30 31 32 33 34

from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, IDC, SystemUser
from ..hands import AdminUserRequiredMixin
from ..tasks import update_assets_hardware_info


__all__ = ['AssetListView', 'AssetCreateView', 'AssetUpdateView',
baltery's avatar
baltery 已提交
35
           'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
baltery's avatar
baltery 已提交
36
           'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
baltery's avatar
baltery 已提交
37
           'BulkImportAssetView',
baltery's avatar
baltery 已提交
38 39 40 41 42 43 44 45 46
           ]


class AssetListView(AdminUserRequiredMixin, TemplateView):
    template_name = 'assets/asset_list.html'

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Assets',
baltery's avatar
baltery 已提交
47
            'action': 'Asset list',
baltery's avatar
baltery 已提交
48 49
            'groups': AssetGroup.objects.all(),
            'system_users': SystemUser.objects.all(),
baltery's avatar
baltery 已提交
50
            # 'form': forms.AssetBulkUpdateForm(),
baltery's avatar
baltery 已提交
51 52 53 54 55 56 57 58 59 60 61
        }
        kwargs.update(context)
        return super(AssetListView, self).get_context_data(**kwargs)


class UserAssetListView(LoginRequiredMixin, TemplateView):
    template_name = 'assets/user_asset_list.html'

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Assets',
baltery's avatar
baltery 已提交
62
            'action': 'Asset list',
baltery's avatar
baltery 已提交
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
            'system_users': SystemUser.objects.all(),
        }
        kwargs.update(context)
        return super(UserAssetListView, self).get_context_data(**kwargs)


class AssetCreateView(AdminUserRequiredMixin, CreateView):
    model = Asset
    form_class = forms.AssetCreateForm
    template_name = 'assets/asset_create.html'
    success_url = reverse_lazy('assets:asset-list')

    def form_valid(self, form):
        self.asset = asset = form.save()
        asset.created_by = self.request.user.username or 'Admin'
        asset.date_created = timezone.now()
        asset.save()
        return super(AssetCreateView, self).form_valid(form)

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Assets',
            'action': 'Create asset',
        }
        kwargs.update(context)
        return super(AssetCreateView, self).get_context_data(**kwargs)

    def get_success_url(self):
baltery's avatar
baltery 已提交
91
        update_assets_hardware_info.delay([self.asset._to_secret_json()])
baltery's avatar
baltery 已提交
92 93 94
        return super(AssetCreateView, self).get_success_url()


baltery's avatar
baltery 已提交
95 96
class AssetModalListView(AdminUserRequiredMixin, ListView):
    paginate_by = settings.CONFIG.DISPLAY_PER_PAGE
baltery's avatar
baltery 已提交
97
    model = Asset
baltery's avatar
baltery 已提交
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    context_object_name = 'asset_modal_list'
    template_name = 'assets/asset_modal_list.html'

    def get_context_data(self, **kwargs):
        assets = Asset.objects.all()
        assets_id = self.request.GET.get('assets_id', '')
        assets_id_list = [i for i in assets_id.split(',') if i.isdigit()]
        context = {
            'all_assets': assets_id_list,
            'assets': assets
        }
        kwargs.update(context)
        return super(AssetModalListView, self).get_context_data(**kwargs)


class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
    model = Asset
    form_class = forms.AssetBulkUpdateForm
    template_name = 'assets/asset_bulk_update.html'
baltery's avatar
baltery 已提交
117 118
    success_url = reverse_lazy('assets:asset-list')

baltery's avatar
baltery 已提交
119 120 121
    def get(self, request, *args, **kwargs):
        assets_id = self.request.GET.get('assets_id', '')
        self.assets_id_list = [int(i) for i in assets_id.split(',') if i.isdigit()]
baltery's avatar
baltery 已提交
122 123 124 125 126 127 128 129 130

        if kwargs.get('form'):
            self.form = kwargs['form']
        elif assets_id:
            self.form = self.form_class(
                initial={'assets': self.assets_id_list}
            )
        else:
            self.form = self.form_class()
baltery's avatar
baltery 已提交
131 132 133
        return super(AssetBulkUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
baltery's avatar
baltery 已提交
134 135 136 137
        form = self.form_class(request.POST)
        if form.is_valid():
            form.save()
            return redirect(self.success_url)
baltery's avatar
baltery 已提交
138
        else:
baltery's avatar
baltery 已提交
139
            return self.get(request, form=form, *args, **kwargs)
baltery's avatar
baltery 已提交
140 141

    def get_context_data(self, **kwargs):
baltery's avatar
baltery 已提交
142
        # assets_list = Asset.objects.filter(id__in=self.assets_id_list)
baltery's avatar
baltery 已提交
143 144
        context = {
            'app': 'Assets',
baltery's avatar
baltery 已提交
145 146 147 148
            'action': 'Bulk update asset',
            'form': self.form,
            'assets_selected': self.assets_id_list,
            'assets': Asset.objects.all(),
baltery's avatar
baltery 已提交
149 150
        }
        kwargs.update(context)
baltery's avatar
baltery 已提交
151
        return super(AssetBulkUpdateView, self).get_context_data(**kwargs)
baltery's avatar
baltery 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192


class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
    model = Asset
    form_class = forms.AssetUpdateForm
    template_name = 'assets/asset_update.html'
    success_url = reverse_lazy('assets:asset-list')

    def get_context_data(self, **kwargs):
        context = {
            'app': 'Assets',
            'action': 'Update asset',
        }
        kwargs.update(context)
        return super(AssetUpdateView, self).get_context_data(**kwargs)

    def form_invalid(self, form):
        print(form.errors)
        return super(AssetUpdateView, self).form_invalid(form)


class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
    model = Asset
    template_name = 'assets/delete_confirm.html'
    success_url = reverse_lazy('assets:asset-list')


class AssetDetailView(DetailView):
    model = Asset
    context_object_name = 'asset'
    template_name = 'assets/asset_detail.html'

    def get_context_data(self, **kwargs):
        asset_groups = self.object.groups.all()
        system_users = self.object.system_users.all()
        context = {
            'app': 'Assets',
            'action': 'Asset detail',
            'asset_groups_remain': [asset_group for asset_group in AssetGroup.objects.all()
                                    if asset_group not in asset_groups],
            'asset_groups': asset_groups,
baltery's avatar
baltery 已提交
193
            'system_users_all': SystemUser.objects.all(),
baltery's avatar
baltery 已提交
194 195 196 197 198 199 200 201 202 203
            'system_users': system_users,
        }
        kwargs.update(context)
        return super(AssetDetailView, self).get_context_data(**kwargs)


@method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(View):
    def get(self, request, *args, **kwargs):
        spm = request.GET.get('spm', '')
baltery's avatar
baltery 已提交
204 205 206 207 208 209 210 211 212 213 214 215
        assets_id = cache.get(spm, [Asset.objects.first().id])
        fields = [
            field for field in Asset._meta.fields
            if field.name not in [
                'date_created'
            ]
        ]
        filename = 'assets-{}.csv'.format(
            timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
        response.write(codecs.BOM_UTF8)
baltery's avatar
baltery 已提交
216
        assets = Asset.objects.filter(id__in=assets_id)
baltery's avatar
baltery 已提交
217 218
        writer = csv.writer(response, dialect='excel',
                            quoting=csv.QUOTE_MINIMAL)
baltery's avatar
baltery 已提交
219

baltery's avatar
baltery 已提交
220 221 222
        header = [field.verbose_name for field in fields]
        header.append(_('Asset groups'))
        writer.writerow(header)
baltery's avatar
baltery 已提交
223

baltery's avatar
baltery 已提交
224 225 226 227 228
        for asset in assets:
            groups = ','.join([group.name for group in asset.groups.all()])
            data = [getattr(asset, field.name) for field in fields]
            data.append(groups)
            writer.writerow(data)
baltery's avatar
baltery 已提交
229 230 231 232 233 234 235
        return response

    def post(self, request, *args, **kwargs):
        try:
            assets_id = json.loads(request.body).get('assets_id', [])
        except ValueError:
            return HttpResponse('Json object not valid', status=400)
baltery's avatar
baltery 已提交
236
        spm = uuid.uuid4().hex
baltery's avatar
baltery 已提交
237 238 239 240 241 242 243 244 245
        cache.set(spm, assets_id, 300)
        url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
        return JsonResponse({'redirect': url})


class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
    form_class = forms.FileForm

    def form_valid(self, form):
C
Caijun 已提交
246 247 248 249 250
        f = form.cleaned_data['file']
        det_result = chardet.detect(f.read())
        f.seek(0)  # reset file seek index
        file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
        csv_file = StringIO(file_data)
baltery's avatar
baltery 已提交
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        reader = csv.reader(csv_file)
        csv_data = [row for row in reader]
        fields = [
            field for field in Asset._meta.fields
            if field.name not in [
                'date_created'
            ]
        ]
        header_ = csv_data[0]
        mapping_reverse = {field.verbose_name: field.name for field in fields}
        mapping_reverse[_('Asset groups')] = 'groups'
        attr = [mapping_reverse.get(n, None) for n in header_]
        if None in attr:
            data = {'valid': False,
                    'msg': 'Must be same format as '
                           'template or export file'}
baltery's avatar
baltery 已提交
267 268
            return self.render_json_response(data)

baltery's avatar
baltery 已提交
269
        created, updated, failed = [], [], []
baltery's avatar
baltery 已提交
270
        assets = []
baltery's avatar
baltery 已提交
271 272 273
        for row in csv_data[1:]:
            if set(row) == {''}:
                continue
C
Caijun 已提交
274

baltery's avatar
baltery 已提交
275 276
            asset_dict = dict(zip(attr, row))
            id_ = asset_dict.pop('id', 0)
C
Caijun 已提交
277 278 279 280 281 282

            try:
                id_ = int(id_)
            except ValueError:
                id_ = 0

baltery's avatar
baltery 已提交
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
            asset = get_object_or_none(Asset, id=id_)
            for k, v in asset_dict.items():
                if k == 'idc':
                    v = get_object_or_none(IDC, name=v)
                elif k == 'is_active':
                    v = bool(v)
                elif k == 'admin_user':
                    v = get_object_or_none(AdminUser, name=v)
                elif k in ['port', 'cabinet_pos', 'cpu_count', 'cpu_cores']:
                    try:
                        v = int(v)
                    except ValueError:
                        v = 0
                elif k == 'groups':
                    groups_name = v.split(',')
                    v = AssetGroup.objects.filter(name__in=groups_name)
                else:
baltery's avatar
baltery 已提交
300
                    continue
baltery's avatar
baltery 已提交
301 302 303 304 305
                asset_dict[k] = v

            if not asset:
                try:
                    groups = asset_dict.pop('groups')
C
Caijun 已提交
306 307
                    if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
                        raise Exception(_('already exists'))
baltery's avatar
baltery 已提交
308 309 310
                    asset = Asset.objects.create(**asset_dict)
                    asset.groups.set(groups)
                    created.append(asset_dict['hostname'])
baltery's avatar
baltery 已提交
311
                    assets.append(asset)
C
Caijun 已提交
312
                except Exception as e:
baltery's avatar
baltery 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325
                    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)))
baltery's avatar
baltery 已提交
326

baltery's avatar
baltery 已提交
327
        if assets:
baltery's avatar
baltery 已提交
328
            update_assets_hardware_info.delay([asset._to_secret_json() for asset in assets])
baltery's avatar
baltery 已提交
329

C
Caijun 已提交
330

baltery's avatar
baltery 已提交
331 332 333 334 335 336 337 338
        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,
baltery's avatar
baltery 已提交
339 340
            'msg': 'Created: {}. Updated: {}, Error: {}'.format(
                len(created), len(updated), len(failed))
baltery's avatar
baltery 已提交
341
        }
baltery's avatar
baltery 已提交
342 343 344
        return self.render_json_response(data)