mirror of https://github.com/jumpserver/jumpserver
[Update] 去掉原来批量的view
parent
e4880a247f
commit
31d2f2a799
|
@ -9,8 +9,6 @@ urlpatterns = [
|
||||||
path('', views.AssetListView.as_view(), name='asset-index'),
|
path('', views.AssetListView.as_view(), name='asset-index'),
|
||||||
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
path('asset/', views.AssetListView.as_view(), name='asset-list'),
|
||||||
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
|
||||||
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
|
|
||||||
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
|
|
||||||
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
|
||||||
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
|
||||||
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
|
||||||
|
|
|
@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
|
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
|
||||||
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
|
||||||
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView',
|
'AssetDeleteView',
|
||||||
]
|
]
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
@ -229,150 +229,3 @@ class AssetDetailView(PermissionsMixin, DetailView):
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
|
||||||
class AssetExportView(PermissionsMixin, View):
|
|
||||||
permission_classes = [IsValidUser]
|
|
||||||
|
|
||||||
def get(self, request):
|
|
||||||
spm = request.GET.get('spm', '')
|
|
||||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
|
||||||
assets_id = cache.get(spm, assets_id_default)
|
|
||||||
fields = [
|
|
||||||
field for field in Asset._meta.fields
|
|
||||||
if field.name not in [
|
|
||||||
'date_created', 'org_id'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
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)
|
|
||||||
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
|
||||||
|
|
||||||
header = [field.verbose_name for field in fields]
|
|
||||||
writer.writerow(header)
|
|
||||||
|
|
||||||
for asset in assets:
|
|
||||||
data = [getattr(asset, field.name) for field in fields]
|
|
||||||
writer.writerow(data)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
assets_id = json.loads(request.body).get('assets_id', [])
|
|
||||||
node_id = json.loads(request.body).get('node_id', None)
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponse('Json object not valid', status=400)
|
|
||||||
|
|
||||||
if not assets_id:
|
|
||||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
|
||||||
assets = node.get_all_assets()
|
|
||||||
for asset in assets:
|
|
||||||
assets_id.append(asset.id)
|
|
||||||
|
|
||||||
spm = uuid.uuid4().hex
|
|
||||||
cache.set(spm, assets_id, 300)
|
|
||||||
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
|
|
||||||
return JsonResponse({'redirect': url})
|
|
||||||
|
|
||||||
|
|
||||||
class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView):
|
|
||||||
form_class = forms.FileForm
|
|
||||||
permission_classes = [IsOrgAdmin]
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
node_id = self.request.GET.get("node_id")
|
|
||||||
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
|
|
||||||
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)
|
|
||||||
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}
|
|
||||||
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)
|
|
||||||
|
|
||||||
created, updated, failed = [], [], []
|
|
||||||
assets = []
|
|
||||||
for row in csv_data[1:]:
|
|
||||||
if set(row) == {''}:
|
|
||||||
continue
|
|
||||||
|
|
||||||
asset_dict_raw = dict(zip(attr, row))
|
|
||||||
asset_dict = dict()
|
|
||||||
for k, v in asset_dict_raw.items():
|
|
||||||
v = v.strip()
|
|
||||||
if k == 'is_active':
|
|
||||||
v = False if v in ['False', 0, 'false'] else True
|
|
||||||
elif k == 'admin_user':
|
|
||||||
v = get_object_or_none(AdminUser, name=v)
|
|
||||||
elif k in ['port', 'cpu_count', 'cpu_cores']:
|
|
||||||
try:
|
|
||||||
v = int(v)
|
|
||||||
except ValueError:
|
|
||||||
v = ''
|
|
||||||
elif k == 'domain':
|
|
||||||
v = get_object_or_none(Domain, name=v)
|
|
||||||
elif k == 'platform':
|
|
||||||
v = v.lower().capitalize()
|
|
||||||
if v != '':
|
|
||||||
asset_dict[k] = v
|
|
||||||
|
|
||||||
asset = None
|
|
||||||
asset_id = asset_dict.pop('id', None)
|
|
||||||
if asset_id:
|
|
||||||
asset = get_object_or_none(Asset, id=asset_id)
|
|
||||||
if not asset:
|
|
||||||
try:
|
|
||||||
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
|
|
||||||
raise Exception(_('already exists'))
|
|
||||||
with transaction.atomic():
|
|
||||||
asset = Asset.objects.create(**asset_dict)
|
|
||||||
if node:
|
|
||||||
asset.nodes.set([node])
|
|
||||||
created.append(asset_dict['hostname'])
|
|
||||||
assets.append(asset)
|
|
||||||
except Exception as e:
|
|
||||||
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
|
|
||||||
else:
|
|
||||||
for k, v in asset_dict.items():
|
|
||||||
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)))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
|
@ -140,3 +140,14 @@ class NeedMFAVerify(permissions.BasePermission):
|
||||||
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
|
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class CanUpdateSuperUser(permissions.BasePermission):
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
if request.method in ['GET', 'OPTIONS']:
|
||||||
|
return True
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return True
|
||||||
|
if hasattr(obj, 'is_superuser') and obj.is_superuser:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
|
@ -373,7 +373,7 @@ defaults = {
|
||||||
'HTTP_BIND_HOST': '0.0.0.0',
|
'HTTP_BIND_HOST': '0.0.0.0',
|
||||||
'HTTP_LISTEN_PORT': 8080,
|
'HTTP_LISTEN_PORT': 8080,
|
||||||
'LOGIN_LOG_KEEP_DAYS': 90,
|
'LOGIN_LOG_KEEP_DAYS': 90,
|
||||||
'ASSETS_PERM_CACHE_TIME': 3600,
|
'ASSETS_PERM_CACHE_TIME': 3600*24,
|
||||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,7 @@ from ..utils import (
|
||||||
)
|
)
|
||||||
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
|
||||||
from .. import serializers, const
|
from .. import serializers, const
|
||||||
from ..mixins import (
|
from ..mixins import AssetsFilterMixin
|
||||||
AssetsFilterMixin,
|
|
||||||
)
|
|
||||||
from ..models import Action
|
from ..models import Action
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -155,13 +153,13 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||||
assets = util.get_assets()
|
assets = util.get_assets()
|
||||||
for k, v in assets.items():
|
for asset, system_users in assets.items():
|
||||||
system_users_granted = []
|
system_users_granted = []
|
||||||
for system_user, actions in v.items():
|
for system_user, actions in system_users.items():
|
||||||
system_user.actions = actions
|
system_user.actions = actions
|
||||||
system_users_granted.append(system_user)
|
system_users_granted.append(system_user)
|
||||||
k.system_users_granted = system_users_granted
|
asset.system_users_granted = system_users_granted
|
||||||
queryset.append(k)
|
queryset.append(system_users_granted)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
|
|
@ -224,6 +224,8 @@ class AssetPermissionCacheMixin:
|
||||||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||||
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
|
CACHE_POLICY_MAP = (('0', 'never'), ('1', 'using'), ('2', 'refresh'))
|
||||||
cache_policy = '1'
|
cache_policy = '1'
|
||||||
|
obj_id = ''
|
||||||
|
_filter_id = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_not_using_cache(cls, cache_policy):
|
def is_not_using_cache(cls, cache_policy):
|
||||||
|
@ -270,7 +272,6 @@ class AssetPermissionCacheMixin:
|
||||||
def get_assets_from_cache(self):
|
def get_assets_from_cache(self):
|
||||||
cached = cache.get(self.asset_key)
|
cached = cache.get(self.asset_key)
|
||||||
if not cached:
|
if not cached:
|
||||||
print("Refresh cache")
|
|
||||||
self.update_cache()
|
self.update_cache()
|
||||||
cached = cache.get(self.asset_key)
|
cached = cache.get(self.asset_key)
|
||||||
return cached
|
return cached
|
||||||
|
@ -320,7 +321,7 @@ class AssetPermissionCacheMixin:
|
||||||
def get_meta_cache_key(self):
|
def get_meta_cache_key(self):
|
||||||
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
|
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_{filter_id}'
|
||||||
key = cache_key.format(
|
key = cache_key.format(
|
||||||
obj_id=str(self.object.id), filter_id=self._filter_id
|
obj_id=self.obj_id, filter_id=self._filter_id
|
||||||
)
|
)
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
@ -345,7 +346,7 @@ class AssetPermissionCacheMixin:
|
||||||
|
|
||||||
def expire_cache_meta(self):
|
def expire_cache_meta(self):
|
||||||
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_*'
|
cache_key = self.CACHE_META_KEY_PREFIX + '{obj_id}_*'
|
||||||
key = cache_key.format(obj_id=str(self.object.id))
|
key = cache_key.format(obj_id=self.obj_id)
|
||||||
cache.delete_pattern(key)
|
cache.delete_pattern(key)
|
||||||
|
|
||||||
def update_cache(self):
|
def update_cache(self):
|
||||||
|
@ -378,6 +379,15 @@ class AssetPermissionCacheMixin:
|
||||||
key = cls.CACHE_KEY_PREFIX + '*'
|
key = cls.CACHE_KEY_PREFIX + '*'
|
||||||
cache.delete_pattern(key)
|
cache.delete_pattern(key)
|
||||||
|
|
||||||
|
def get_assets_without_cache(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_nodes_with_assets_without_cache(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_system_user_without_cache(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUtil(AssetPermissionCacheMixin):
|
class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
get_permissions_map = {
|
get_permissions_map = {
|
||||||
|
@ -472,8 +482,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
pattern.add(r'^{0}$|^{0}:'.format(node.key))
|
pattern.add(r'^{0}$|^{0}:'.format(node.key))
|
||||||
pattern = '|'.join(list(pattern))
|
pattern = '|'.join(list(pattern))
|
||||||
now = time.time()
|
|
||||||
print("Get node assets start")
|
|
||||||
if pattern:
|
if pattern:
|
||||||
assets = Asset.objects.filter(nodes__key__regex=pattern)\
|
assets = Asset.objects.filter(nodes__key__regex=pattern)\
|
||||||
.prefetch_related('nodes', "protocols")\
|
.prefetch_related('nodes', "protocols")\
|
||||||
|
@ -481,7 +489,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
|
||||||
.distinct()
|
.distinct()
|
||||||
else:
|
else:
|
||||||
assets = []
|
assets = []
|
||||||
print("Get node assets end, using: {}".format(time.time() - now))
|
|
||||||
self.tree.add_assets_without_system_users(assets)
|
self.tree.add_assets_without_system_users(assets)
|
||||||
assets = self.tree.get_assets()
|
assets = self.tree.get_assets()
|
||||||
self._assets = assets
|
self._assets = assets
|
||||||
|
|
|
@ -13,7 +13,8 @@ from rest_framework_bulk import BulkModelViewSet
|
||||||
from rest_framework.pagination import LimitOffsetPagination
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
|
|
||||||
from common.permissions import (
|
from common.permissions import (
|
||||||
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser
|
IsOrgAdmin, IsCurrentUserOrReadOnly, IsOrgAdminOrAppUser,
|
||||||
|
CanUpdateSuperUser,
|
||||||
)
|
)
|
||||||
from common.mixins import IDInCacheFilterMixin
|
from common.mixins import IDInCacheFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -37,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
search_fields = filter_fields
|
search_fields = filter_fields
|
||||||
queryset = User.objects.exclude(role=User.ROLE_APP)
|
queryset = User.objects.exclude(role=User.ROLE_APP)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = (IsOrgAdmin,)
|
permission_classes = (IsOrgAdmin, CanUpdateSuperUser)
|
||||||
pagination_class = LimitOffsetPagination
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
def send_created_signal(self, users):
|
def send_created_signal(self, users):
|
||||||
|
@ -70,28 +71,6 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
return not self.request.user.is_superuser and instance.is_superuser
|
return not self.request.user.is_superuser and instance.is_superuser
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
rewrite because limit org_admin destroy superuser
|
|
||||||
"""
|
|
||||||
instance = self.get_object()
|
|
||||||
if self._deny_permission(instance):
|
|
||||||
data = {'msg': _("You do not have permission.")}
|
|
||||||
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
return super().destroy(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
rewrite because limit org_admin update superuser
|
|
||||||
"""
|
|
||||||
instance = self.get_object()
|
|
||||||
if self._deny_permission(instance):
|
|
||||||
data = {'msg': _("You do not have permission.")}
|
|
||||||
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
|
|
||||||
|
|
||||||
return super().update(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def _bulk_deny_permission(self, instances):
|
def _bulk_deny_permission(self, instances):
|
||||||
deny_instances = [i for i in instances if self._deny_permission(i)]
|
deny_instances = [i for i in instances if self._deny_permission(i)]
|
||||||
if len(deny_instances) > 0:
|
if len(deny_instances) > 0:
|
||||||
|
@ -108,26 +87,12 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||||
"""
|
"""
|
||||||
rewrite because limit org_admin update superuser
|
rewrite because limit org_admin update superuser
|
||||||
"""
|
"""
|
||||||
partial = kwargs.pop('partial', False)
|
|
||||||
|
|
||||||
# restrict the update to the filtered queryset
|
# restrict the update to the filtered queryset
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
if self._bulk_deny_permission(queryset):
|
if self._bulk_deny_permission(queryset):
|
||||||
data = {'msg': _("You do not have permission.")}
|
data = {'msg': _("You do not have permission.")}
|
||||||
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
|
return Response(data=data, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
return super().bulk_update(request, *args, **kwargs)
|
||||||
serializer = self.get_serializer(
|
|
||||||
queryset, data=request.data, many=True, partial=partial,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
except Exception as e:
|
|
||||||
data = {'error': str(e)}
|
|
||||||
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
self.perform_bulk_update(serializer)
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
|
class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
|
||||||
|
|
|
@ -39,6 +39,14 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
'created_by': {'read_only': True}, 'source': {'read_only': True}
|
'created_by': {'read_only': True}, 'source': {'read_only': True}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def validate_role(self, value):
|
||||||
|
request = self.context.get('request')
|
||||||
|
if not request.user.is_superuser and value != User.ROLE_USER:
|
||||||
|
role_display = dict(User.ROLE_CHOICES)[User.ROLE_USER]
|
||||||
|
msg = _("Role limit to {}".format(role_display))
|
||||||
|
raise serializers.ValidationError(msg)
|
||||||
|
return value
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_password(value):
|
def validate_password(value):
|
||||||
from ..utils import check_password_rules
|
from ..utils import check_password_rules
|
||||||
|
|
|
@ -92,23 +92,23 @@ function initTable() {
|
||||||
}},
|
}},
|
||||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = "";
|
var update_btn = "";
|
||||||
if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) {
|
{#if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) {#}
|
||||||
update_btn = '<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>';
|
{# update_btn = '<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>';#}
|
||||||
}
|
{#}#}
|
||||||
else{
|
{#else{#}
|
||||||
update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
||||||
}
|
{#}#}
|
||||||
|
|
||||||
var del_btn = "";
|
var del_btn = "";
|
||||||
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) {
|
{#if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) {#}
|
||||||
del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'
|
{# del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'#}
|
||||||
.replace('{{ DEFAULT_PK }}', cellData)
|
{# .replace('{{ DEFAULT_PK }}', cellData)#}
|
||||||
.replace('99991938', rowData.name);
|
{# .replace('99991938', rowData.name);#}
|
||||||
} else {
|
{#} else {#}
|
||||||
del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Delete" %}</a>'
|
del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_user_delete" data-uid="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Delete" %}</a>'
|
||||||
.replace('{{ DEFAULT_PK }}', cellData)
|
.replace('{{ DEFAULT_PK }}', cellData)
|
||||||
.replace('99991938', rowData.name);
|
.replace('99991938', rowData.name);
|
||||||
}
|
{#}#}
|
||||||
$(td).html(update_btn + del_btn)
|
$(td).html(update_btn + del_btn)
|
||||||
}}],
|
}}],
|
||||||
ajax_url: '{% url "api-users:user-list" %}',
|
ajax_url: '{% url "api-users:user-list" %}',
|
||||||
|
|
|
@ -29,9 +29,7 @@ urlpatterns = [
|
||||||
|
|
||||||
# User view
|
# User view
|
||||||
path('user/', views.UserListView.as_view(), name='user-list'),
|
path('user/', views.UserListView.as_view(), name='user-list'),
|
||||||
path('user/export/', views.UserExportView.as_view(), name='user-export'),
|
|
||||||
path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'),
|
path('first-login/', views.UserFirstLoginView.as_view(), name='user-first-login'),
|
||||||
path('user/import/', views.UserBulkImportView.as_view(), name='user-import'),
|
|
||||||
path('user/create/', views.UserCreateView.as_view(), name='user-create'),
|
path('user/create/', views.UserCreateView.as_view(), name='user-create'),
|
||||||
path('user/<uuid:pk>/update/', views.UserUpdateView.as_view(), name='user-update'),
|
path('user/<uuid:pk>/update/', views.UserUpdateView.as_view(), name='user-update'),
|
||||||
path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
|
path('user/update/', views.UserBulkUpdateView.as_view(), name='user-bulk-update'),
|
||||||
|
|
|
@ -46,9 +46,7 @@ from ..signals import post_user_create
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserListView', 'UserCreateView', 'UserDetailView',
|
'UserListView', 'UserCreateView', 'UserDetailView',
|
||||||
'UserUpdateView',
|
'UserUpdateView', 'UserGrantedAssetView', 'UserProfileView',
|
||||||
'UserGrantedAssetView',
|
|
||||||
'UserExportView', 'UserBulkImportView', 'UserProfileView',
|
|
||||||
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
'UserProfileUpdateView', 'UserPasswordUpdateView',
|
||||||
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
|
'UserPublicKeyUpdateView', 'UserBulkUpdateView',
|
||||||
'UserPublicKeyGenerateView',
|
'UserPublicKeyGenerateView',
|
||||||
|
@ -223,147 +221,6 @@ class UserDetailView(PermissionsMixin, DetailView):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name='dispatch')
|
|
||||||
class UserExportView(View):
|
|
||||||
def get(self, request):
|
|
||||||
fields = [
|
|
||||||
User._meta.get_field(name)
|
|
||||||
for name in [
|
|
||||||
'id', 'name', 'username', 'email', 'role',
|
|
||||||
'wechat', 'phone', 'is_active', 'comment',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
spm = request.GET.get('spm', '')
|
|
||||||
users_id = cache.get(spm, [])
|
|
||||||
filename = 'users-{}.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)
|
|
||||||
users = User.objects.filter(id__in=users_id)
|
|
||||||
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
|
|
||||||
|
|
||||||
header = [field.verbose_name for field in fields]
|
|
||||||
header.append(_('User groups'))
|
|
||||||
writer.writerow(header)
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
groups = ','.join([group.name for group in user.groups.all()])
|
|
||||||
data = [getattr(user, field.name) for field in fields]
|
|
||||||
data.append(groups)
|
|
||||||
writer.writerow(data)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
try:
|
|
||||||
users_id = json.loads(request.body).get('users_id', [])
|
|
||||||
except ValueError:
|
|
||||||
return HttpResponse('Json object not valid', status=400)
|
|
||||||
spm = uuid.uuid4().hex
|
|
||||||
cache.set(spm, users_id, 300)
|
|
||||||
url = reverse('users:user-export') + '?spm=%s' % spm
|
|
||||||
return JsonResponse({'redirect': url})
|
|
||||||
|
|
||||||
|
|
||||||
class UserBulkImportView(PermissionsMixin, JSONResponseMixin, FormView):
|
|
||||||
form_class = forms.FileForm
|
|
||||||
permission_classes = [IsOrgAdmin]
|
|
||||||
|
|
||||||
def form_invalid(self, form):
|
|
||||||
try:
|
|
||||||
error = form.errors.values()[-1][-1]
|
|
||||||
except Exception as e:
|
|
||||||
error = _('Invalid file.')
|
|
||||||
data = {
|
|
||||||
'success': False,
|
|
||||||
'msg': error
|
|
||||||
}
|
|
||||||
return self.render_json_response(data)
|
|
||||||
|
|
||||||
# todo: need be patch, method to long
|
|
||||||
def form_valid(self, form):
|
|
||||||
f = form.cleaned_data['file']
|
|
||||||
det_result = chardet.detect(f.read())
|
|
||||||
f.seek(0) # reset file seek index
|
|
||||||
data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
|
|
||||||
csv_file = StringIO(data)
|
|
||||||
reader = csv.reader(csv_file)
|
|
||||||
csv_data = [row for row in reader]
|
|
||||||
header_ = csv_data[0]
|
|
||||||
fields = [
|
|
||||||
User._meta.get_field(name)
|
|
||||||
for name in [
|
|
||||||
'id', 'name', 'username', 'email', 'role',
|
|
||||||
'wechat', 'phone', 'is_active', 'comment',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
mapping_reverse = {field.verbose_name: field.name for field in fields}
|
|
||||||
mapping_reverse[_('User 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'}
|
|
||||||
return self.render_json_response(data)
|
|
||||||
|
|
||||||
created, updated, failed = [], [], []
|
|
||||||
for row in csv_data[1:]:
|
|
||||||
if set(row) == {''}:
|
|
||||||
continue
|
|
||||||
user_dict = dict(zip(attr, row))
|
|
||||||
id_ = user_dict.pop('id')
|
|
||||||
for k, v in user_dict.items():
|
|
||||||
if k in ['is_active']:
|
|
||||||
if v.lower() == 'false':
|
|
||||||
v = False
|
|
||||||
else:
|
|
||||||
v = bool(v)
|
|
||||||
elif k == 'groups':
|
|
||||||
groups_name = v.split(',')
|
|
||||||
v = UserGroup.objects.filter(name__in=groups_name)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
user_dict[k] = v
|
|
||||||
user = get_object_or_none(User, id=id_) if id_ and is_uuid(id_) else None
|
|
||||||
if not user:
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
groups = user_dict.pop('groups')
|
|
||||||
user = User.objects.create(**user_dict)
|
|
||||||
user.groups.set(groups)
|
|
||||||
created.append(user_dict['username'])
|
|
||||||
post_user_create.send(self.__class__, user=user)
|
|
||||||
except Exception as e:
|
|
||||||
failed.append('%s: %s' % (user_dict['username'], str(e)))
|
|
||||||
else:
|
|
||||||
for k, v in user_dict.items():
|
|
||||||
if k == 'groups':
|
|
||||||
user.groups.set(v)
|
|
||||||
continue
|
|
||||||
if v:
|
|
||||||
setattr(user, k, v)
|
|
||||||
try:
|
|
||||||
user.save()
|
|
||||||
updated.append(user_dict['username'])
|
|
||||||
except Exception as e:
|
|
||||||
failed.append('%s: %s' % (user_dict['username'], str(e)))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class UserGrantedAssetView(PermissionsMixin, DetailView):
|
class UserGrantedAssetView(PermissionsMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'users/user_granted_asset.html'
|
template_name = 'users/user_granted_asset.html'
|
||||||
|
|
Loading…
Reference in New Issue