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 %}
@@ -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