diff --git a/apps/assets/models.py b/apps/assets/models.py index 5d6d18096..be54b0286 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -60,38 +60,6 @@ class IDC(models.Model): continue -class AssetExtend(models.Model): - key = models.CharField(max_length=64, verbose_name=_('KEY')) - value = models.CharField(max_length=64, verbose_name=_('VALUE')) - created_by = models.CharField(max_length=32, blank=True, verbose_name=_("Created by")) - date_created = models.DateTimeField(auto_now_add=True, null=True) - comment = models.TextField(blank=True, verbose_name=_('Comment')) - - def __unicode__(self): - return '%(key)s: %(value)s' % {'key': self.key, 'value': self.value} - - @classmethod - def initial(cls): - for k, v in ( - (_('status'), _('In use')), - (_('status'), _('Out of use')), - (_('type'), _('Server')), - (_('type'), _('VM')), - (_('type'), _('Switch')), - (_('type'), _('Router')), - (_('type'), _('Firewall')), - (_('type'), _('Storage')), - (_('env'), _('Production')), - (_('env'), _('Development')), - (_('env'), _('Testing')), - ): - cls.objects.create(key=k, value=v, created_by='System') - - class Meta: - db_table = 'asset_extend' - unique_together = ('key', 'value') - - def private_key_validator(value): if not validate_ssh_private_key(value): raise ValidationError( @@ -298,18 +266,29 @@ class AssetGroup(models.Model): continue -def get_default_extend(key, value): - try: - return AssetExtend.objects.get_or_create(key=key, value=value)[0] - except: - return None - - def get_default_idc(): return IDC.initial() class Asset(models.Model): + STATUS_CHOICES = ( + ('In use', _('In use')), + ('Out of use', _('Out of use')), + ) + TYPE_CHOICES = ( + ('Server', _('Server')), + ('VM', _('VM')), + ('Switch', _('Switch')), + ('Router', _('Router')), + ('Firewall', _('Firewall')), + ('Storage', _("Storage")), + ) + ENV_CHOICES = ( + ('Prod', 'Production'), + ('Dev', 'Development'), + ('Test', 'Testing'), + ) + ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True) other_ip = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Other IP')) remote_card_ip = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('Remote card IP')) @@ -330,15 +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.ForeignKey(AssetExtend, null=True, blank=True, - related_name="status_asset", verbose_name=_('Asset status'),) - # default=functools.partial(get_default_extend, 'status', 'In use')) - type = models.ForeignKey(AssetExtend, blank=True,null=True, limit_choices_to={'key': 'type'}, - related_name="type_asset", verbose_name=_('Asset type'),) - # default=functools.partial(get_default_extend, 'type','Server')) - env = models.ForeignKey(AssetExtend, blank=True, null=True, limit_choices_to={'key': 'env'}, - related_name="env_asset", verbose_name=_('Asset environment'),) - # default=functools.partial(get_default_extend, 'env', 'Production')) + status = models.CharField(choices=STATUS_CHOICES, max_length=1, null=True, blank=True, + default='I', 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'),) 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')) @@ -404,7 +380,7 @@ class Tag(models.Model): def init_all_models(): - for cls in (AssetExtend, AssetGroup): + for cls in (AssetGroup,): cls.initial() @@ -414,5 +390,5 @@ def generate_fake(): def flush_all(): - for cls in (AssetGroup, AssetExtend, IDC, AdminUser, SystemUser, Asset): + for cls in (AssetGroup, IDC, AdminUser, SystemUser, Asset): cls.objects.all().delete() diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index 0bc7a70c8..2bef663b8 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.utils.translation import ugettext_lazy as _ from rest_framework import viewsets, serializers,generics -from .models import AssetGroup, Asset, IDC, AssetExtend, AdminUser, SystemUser +from .models import AssetGroup, Asset, IDC, AdminUser, SystemUser from common.mixins import IDInFilterMixin from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index c50361782..bee1c396d 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -18,10 +18,11 @@ {% block table_search %}
- - PDF - - Excel + + {% trans "Import" %} + + + {% trans "Export" %}
@@ -32,7 +33,7 @@
{% for tag in tag_list %} {% else %} class="tagBtn2 label label-default"> @@ -84,7 +85,7 @@ {% block custom_foot_js %} {% endblock %} \ No newline at end of file diff --git a/apps/assets/views.py b/apps/assets/views.py index c8bbc3911..2ed4b8c98 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -1,17 +1,29 @@ # coding:utf-8 from __future__ import absolute_import, unicode_literals +import json +import uuid + +from openpyxl import Workbook +from openpyxl.writer.excel import save_virtual_workbook +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.views.generic import TemplateView, ListView +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.contrib.messages.views import SuccessMessageMixin from django.views.generic.detail import DetailView, SingleObjectMixin from django.shortcuts import get_object_or_404, reverse, redirect +from django.http import HttpResponse, JsonResponse +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 common.utils import int_seq from .utils import CreateAssetTagsMiXin,UpdateAssetTagsMiXin -from .models import Asset, AssetGroup, IDC, AssetExtend, AdminUser, SystemUser, Tag +from .models import Asset, AssetGroup, IDC, AdminUser, SystemUser, Tag from .forms import * from .hands import AdminUserRequiredMixin @@ -693,3 +705,46 @@ class AssetTagDeleteView(AdminUserRequiredMixin, DeleteView): model = Tag success_url = reverse_lazy('assets:asset-tag-list') + +@method_decorator(csrf_exempt, name='dispatch') +class ExportAssetView(View): + @staticmethod + def asset_get_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') + + def get(self, request, *args, **kwargs): + spm = request.GET.get('spm', '') + assets_id = cache.get(spm) + if not assets_id and not isinstance(assets_id, list): + return HttpResponse('May be expired', status=404) + + assets = Asset.objects.filter(id__in=assets_id) + 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'] + ws.append(header) + + for asset in assets: + ws.append([getattr(asset, attr) for attr in header]) + + filename = 'users-{}.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 + + 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) + spm = uuid.uuid4().get_hex() + cache.set(spm, assets_id, 300) + url = reverse('users:export-user-csv') + '?spm=%s' % spm + return JsonResponse({'redirect': url}) diff --git a/apps/fixtures/init.json b/apps/fixtures/init.json index 107e8b6f8..8707430c9 100644 --- a/apps/fixtures/init.json +++ b/apps/fixtures/init.json @@ -1 +1 @@ -[{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-02T14:49:50Z", "created_by": "System"}}, {"model": "assets.assetextend", "pk": 1, "fields": {"key": "status", "value": "In use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 2, "fields": {"key": "status", "value": "Out of use", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 3, "fields": {"key": "type", "value": "Server", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 4, "fields": {"key": "type", "value": "VM", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 5, "fields": {"key": "type", "value": "Switch", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 6, "fields": {"key": "type", "value": "Router", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 7, "fields": {"key": "type", "value": "Firewall", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 8, "fields": {"key": "type", "value": "Storage", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 9, "fields": {"key": "env", "value": "Production", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 10, "fields": {"key": "env", "value": "Development", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetextend", "pk": 11, "fields": {"key": "env", "value": "Testing", "created_by": "System", "date_created": "2016-11-02T14:51:53Z", "comment": ""}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-02T14:51:53Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$30000$y45nsj5IDfVi$KUXoECb9rZJZ2ZosQSxi9anmj2oY5LAr1MdJby/xEzU=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-02T14:51:45Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": "", "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-10-16T14:51:45Z", "created_by": "System", "user_permissions": [], "groups": [1]}}] +[{"model": "users.usergroup", "pk": 1, "fields": {"is_discard": false, "discard_time": null, "name": "Default", "comment": "Default user group for all user", "date_created": "2016-11-25T06:50:28.410Z", "created_by": "System"}}, {"model": "assets.assetgroup", "pk": 1, "fields": {"name": "Default", "created_by": "", "date_created": "2016-11-25T06:50:28.627Z", "comment": "Default asset group", "system_users": []}}, {"model": "users.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$30000$RwSpXYAYQGbQ$PADpsQmM+cO5Y/R1CVSx3qNV4EbGIm2k+iMBXUtwvNc=", "last_login": null, "first_name": "", "last_name": "", "is_active": true, "date_joined": "2016-11-25T06:50:28.412Z", "username": "admin", "name": "Administrator", "email": "admin@jumpserver.org", "role": "Admin", "avatar": "", "wechat": "", "phone": null, "enable_otp": false, "secret_key_otp": "", "_private_key": "", "_public_key": "", "comment": "Administrator is the super user of system", "is_first_login": false, "date_expired": "2086-11-08T06:50:28.412Z", "created_by": "System", "user_permissions": [], "groups": [1]}}] \ No newline at end of file diff --git a/apps/users/api.py b/apps/users/api.py index 156eab025..b9ffa9de4 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -1,18 +1,13 @@ # ~*~ coding: utf-8 ~*~ # -import base64 -import json -from django.shortcuts import get_object_or_404 from django.core.cache import cache from django.conf import settings -from rest_framework import generics, status, viewsets +from rest_framework import generics, viewsets from rest_framework.response import Response from rest_framework.views import APIView -from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet -from rest_framework import authentication -import django_filters +from rest_framework_bulk import BulkModelViewSet from django_filters.rest_framework import DjangoFilterBackend from common.mixins import IDInFilterMixin @@ -78,7 +73,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): user.save() -class UserGroupViewSet(viewsets.ModelViewSet): +class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet): queryset = UserGroup.objects.all() serializer_class = serializers.UserGroupSerializer @@ -88,52 +83,7 @@ class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): serializer_class = serializers.UserGroupUpdateMemeberSerializer permission_classes = (IsSuperUser,) -# class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView): -# queryset = UserGroup.objects.all() -# serializer_class = serializers.GroupDetailSerializer -# -# def perform_update(self, serializer): -# users = serializer.validated_data.get('users') -# if users: -# group = self.get_object() -# Note: use `list` method to force hitting the db. - # group_users = list(group.users.all()) - # serializer.save() - # group.users.set(users + group_users) - # group.save() - # return - # serializer.save() - -# class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): -# queryset = User.objects.all() -# serializer_class = serializers.UserBulkUpdateSerializer -# permission_classes = (IsSuperUserOrTerminalUser,) -# -# def get(self, request, *args, **kwargs): -# return super(UserListUpdateApi, self).get(request, *args, **kwargs) - -# -# class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): -# queryset = UserGroup.objects.all() -# serializer_class = serializers.GroupBulkUpdateSerializer -# - -# class DeleteUserFromGroupApi(generics.DestroyAPIView): -# queryset = UserGroup.objects.all() -# serializer_class = serializers.GroupDetailSerializer -# -# def destroy(self, request, *args, **kwargs): -# group = self.get_object() -# self.perform_destroy(group, **kwargs) -# return Response(status=status.HTTP_204_NO_CONTENT) -# -# def perform_destroy(self, instance, **kwargs): -# user_id = kwargs.get('uid') -# user = get_object_or_404(User, id=user_id) -# instance.users.remove(user) -# -# class UserAuthApi(APIView): permission_classes = () expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html index fc88a4abc..407ec3b5d 100644 --- a/apps/users/templates/users/user_group_list.html +++ b/apps/users/templates/users/user_group_list.html @@ -47,8 +47,11 @@ $(document).ready(function() { $(td).html('' + innerHtml + ''); }}, {targets: 4, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('99991937', cellData); - var del_btn = '{% trans "Delete" %}'.replace('99991937', cellData); + var update_btn = '{% trans "Update" %}' + .replace('99991937', cellData); + var del_btn = '{% trans "Delete" %}' + .replace('99991937', cellData) + .replace('99991938', rowData.name); if (rowData.id === 1) { $(td).html(update_btn) } else { @@ -63,38 +66,10 @@ $(document).ready(function() { jumpserver.initDataTable(options); }).on('click', '.btn_delete_user_group', function(){ var $this = $(this); - function doDelete() { - var group_id = $this.data('gid'); - var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id); - var body = {}; - var success = function() { - var msg = "{% trans 'Group Deleted.' %}"; - swal("{% trans 'Group Delete' %}", msg, "success"); - $this.closest('tr.gradeX').remove(); - }; - var fail = function() { - var msg = "{% trans 'Group Deleting failed.' %}"; - swal("{% trans 'Group Delete' %}", msg, "error"); - }; - APIUpdateAttr({ - url: the_url, - body: JSON.stringify(body), - method: 'DELETE', - success: success, - error: fail - }); - } - swal({ - title: "{% trans 'Are you sure?' %}", - text: "{% trans 'This will delete the selected group, but will not remove any user in this group.' %}", - type: "warning", - showCancelButton: true, - confirmButtonColor: "#DD6B55", - confirmButtonText: "{% trans 'Confirm' %}", - closeOnConfirm: false - }, function() { - doDelete(); - }); + var group_id = $this.data('gid'); + var name = $this.data('name'); + var the_url = "{% url 'api-users:user-group-detail' pk=99991937 %}".replace('99991937', group_id); + objectDelete($this, name, the_url) }).on('click', '#btn_bulk_update', function(){ var action = $('#slct_bulk_update').val(); var $data_table = $('#group_list_table').DataTable() diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 59c79feba..1b7f76f5a 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -99,7 +99,7 @@ $(document).ready(function(){ users.push(obj.id) }); $.ajax({ - url: "{% url "users:export-user-csv" %}", + url: "{% url "users:export-user" %}", method: 'POST', data: JSON.stringify({users_id: users}), dataType: "json", diff --git a/apps/users/urls/views_urls.py b/apps/users/urls/views_urls.py index b51285800..df8db3a88 100644 --- a/apps/users/urls/views_urls.py +++ b/apps/users/urls/views_urls.py @@ -40,5 +40,5 @@ urlpatterns = [ name='user-group-asset-permission-create'), url(r'^user-group/(?P[0-9]+)/assets', views.UserGroupGrantedAssetView.as_view(), name='user-group-granted-asset'), - url(r'^export/user/csv/', views.ExportUserCsvView.as_view(), name='export-user-csv'), + url(r'^export/user/', views.ExportUserView.as_view(), name='export-user'), ] diff --git a/apps/users/views.py b/apps/users/views.py index f51bdd798..345b4d6c3 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -240,10 +240,6 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): return super(UserGroupDetailView, self).get_context_data(**kwargs) -class UserGroupDeleteView(DeleteView): - pass - - class UserForgotPasswordView(TemplateView): template_name = 'users/forgot_password.html' @@ -561,7 +557,7 @@ class BulkImportUserView(AdminUserRequiredMixin, JSONResponseMixin, FormView): @method_decorator(csrf_exempt, name='dispatch') -class ExportUserCsvView(View): +class ExportUserView(View): def get(self, request, *args, **kwargs): spm = request.GET.get('spm', '') users_id = cache.get(spm) diff --git a/utils/clean_migrations.sh b/utils/clean_migrations.sh new file mode 100755 index 000000000..2c3b82246 --- /dev/null +++ b/utils/clean_migrations.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# + +for app in users assets perms audits teminal ops;do + rm -f $app/migrations/000* +done diff --git a/utils/init_db.sh b/utils/init_db.sh new file mode 100755 index 000000000..4073ad001 --- /dev/null +++ b/utils/init_db.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# + +python ../apps/manage.py loaddata init diff --git a/utils/make_fake_json.sh b/utils/make_fake_json.sh new file mode 100644 index 000000000..657b36d07 --- /dev/null +++ b/utils/make_fake_json.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# + +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 diff --git a/utils/make_init_json.sh b/utils/make_init_json.sh new file mode 100644 index 000000000..0f3fb659f --- /dev/null +++ b/utils/make_init_json.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# + +python ../apps/manage.py shell << EOF +from users.models import * +init_all_models() + +from assets.models import * +init_all_models() +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 diff --git a/utils/make_migrations.sh b/utils/make_migrations.sh new file mode 100755 index 000000000..0704b9cc9 --- /dev/null +++ b/utils/make_migrations.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# + +python ../apps/manage.py makemigrations + +python ../apps/manage.py migrate