Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
31d2f2a7
Commit
31d2f2a7
authored
Jul 02, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 去掉原来批量的view
parent
e4880a24
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
76 additions
and
381 deletions
+76
-381
views_urls.py
apps/assets/urls/views_urls.py
+0
-2
asset.py
apps/assets/views/asset.py
+1
-148
permissions.py
apps/common/permissions.py
+11
-0
conf.py
apps/jumpserver/conf.py
+1
-1
user_permission.py
apps/perms/api/user_permission.py
+5
-7
asset_permission.py
apps/perms/utils/asset_permission.py
+13
-6
user.py
apps/users/api/user.py
+4
-39
v1.py
apps/users/serializers/v1.py
+8
-0
user_list.html
apps/users/templates/users/user_list.html
+32
-32
views_urls.py
apps/users/urls/views_urls.py
+0
-2
user.py
apps/users/views/user.py
+1
-144
No files found.
apps/assets/urls/views_urls.py
View file @
31d2f2a7
...
...
@@ -9,8 +9,6 @@ urlpatterns = [
path
(
''
,
views
.
AssetListView
.
as_view
(),
name
=
'asset-index'
),
path
(
'asset/'
,
views
.
AssetListView
.
as_view
(),
name
=
'asset-list'
),
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>/update/'
,
views
.
AssetUpdateView
.
as_view
(),
name
=
'asset-update'
),
path
(
'asset/<uuid:pk>/delete/'
,
views
.
AssetDeleteView
.
as_view
(),
name
=
'asset-delete'
),
...
...
apps/assets/views/asset.py
View file @
31d2f2a7
...
...
@@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
__all__
=
[
'AssetListView'
,
'AssetCreateView'
,
'AssetUpdateView'
,
'AssetUserListView'
,
'UserAssetListView'
,
'AssetBulkUpdateView'
,
'AssetDetailView'
,
'AssetDeleteView'
,
'AssetExportView'
,
'BulkImportAssetView'
,
'AssetDeleteView'
,
]
logger
=
get_logger
(
__file__
)
...
...
@@ -229,150 +229,3 @@ class AssetDetailView(PermissionsMixin, DetailView):
}
kwargs
.
update
(
context
)
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
)
apps/common/permissions.py
View file @
31d2f2a7
...
...
@@ -140,3 +140,14 @@ class NeedMFAVerify(permissions.BasePermission):
if
time
.
time
()
-
mfa_verify_time
<
settings
.
SECURITY_MFA_VERIFY_TTL
:
return
True
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
apps/jumpserver/conf.py
View file @
31d2f2a7
...
...
@@ -373,7 +373,7 @@ defaults = {
'HTTP_BIND_HOST'
:
'0.0.0.0'
,
'HTTP_LISTEN_PORT'
:
8080
,
'LOGIN_LOG_KEEP_DAYS'
:
90
,
'ASSETS_PERM_CACHE_TIME'
:
3600
,
'ASSETS_PERM_CACHE_TIME'
:
3600
*
24
,
'SECURITY_MFA_VERIFY_TTL'
:
3600
,
}
...
...
apps/perms/api/user_permission.py
View file @
31d2f2a7
...
...
@@ -21,9 +21,7 @@ from ..utils import (
)
from
..hands
import
User
,
Asset
,
Node
,
SystemUser
,
NodeSerializer
from
..
import
serializers
,
const
from
..mixins
import
(
AssetsFilterMixin
,
)
from
..mixins
import
AssetsFilterMixin
from
..models
import
Action
logger
=
get_logger
(
__name__
)
...
...
@@ -155,13 +153,13 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
assets
=
util
.
get_assets
()
for
k
,
v
in
assets
.
items
():
for
asset
,
system_users
in
assets
.
items
():
system_users_granted
=
[]
for
system_user
,
actions
in
v
.
items
():
for
system_user
,
actions
in
system_users
.
items
():
system_user
.
actions
=
actions
system_users_granted
.
append
(
system_user
)
k
.
system_users_granted
=
system_users_granted
queryset
.
append
(
k
)
asset
.
system_users_granted
=
system_users_granted
queryset
.
append
(
system_users_granted
)
return
queryset
def
get_permissions
(
self
):
...
...
apps/perms/utils/asset_permission.py
View file @
31d2f2a7
...
...
@@ -224,6 +224,8 @@ class AssetPermissionCacheMixin:
CACHE_TIME
=
settings
.
ASSETS_PERM_CACHE_TIME
CACHE_POLICY_MAP
=
((
'0'
,
'never'
),
(
'1'
,
'using'
),
(
'2'
,
'refresh'
))
cache_policy
=
'1'
obj_id
=
''
_filter_id
=
None
@classmethod
def
is_not_using_cache
(
cls
,
cache_policy
):
...
...
@@ -270,7 +272,6 @@ class AssetPermissionCacheMixin:
def
get_assets_from_cache
(
self
):
cached
=
cache
.
get
(
self
.
asset_key
)
if
not
cached
:
print
(
"Refresh cache"
)
self
.
update_cache
()
cached
=
cache
.
get
(
self
.
asset_key
)
return
cached
...
...
@@ -320,7 +321,7 @@ class AssetPermissionCacheMixin:
def
get_meta_cache_key
(
self
):
cache_key
=
self
.
CACHE_META_KEY_PREFIX
+
'{obj_id}_{filter_id}'
key
=
cache_key
.
format
(
obj_id
=
s
tr
(
self
.
object
.
id
)
,
filter_id
=
self
.
_filter_id
obj_id
=
s
elf
.
obj_id
,
filter_id
=
self
.
_filter_id
)
return
key
...
...
@@ -345,7 +346,7 @@ class AssetPermissionCacheMixin:
def
expire_cache_meta
(
self
):
cache_key
=
self
.
CACHE_META_KEY_PREFIX
+
'{obj_id}_*'
key
=
cache_key
.
format
(
obj_id
=
s
tr
(
self
.
object
.
id
)
)
key
=
cache_key
.
format
(
obj_id
=
s
elf
.
obj_id
)
cache
.
delete_pattern
(
key
)
def
update_cache
(
self
):
...
...
@@ -378,6 +379,15 @@ class AssetPermissionCacheMixin:
key
=
cls
.
CACHE_KEY_PREFIX
+
'*'
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
):
get_permissions_map
=
{
...
...
@@ -472,8 +482,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
for
node
in
nodes
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
node
.
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
now
=
time
.
time
()
print
(
"Get node assets start"
)
if
pattern
:
assets
=
Asset
.
objects
.
filter
(
nodes__key__regex
=
pattern
)
\
.
prefetch_related
(
'nodes'
,
"protocols"
)
\
...
...
@@ -481,7 +489,6 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
.
distinct
()
else
:
assets
=
[]
print
(
"Get node assets end, using: {}"
.
format
(
time
.
time
()
-
now
))
self
.
tree
.
add_assets_without_system_users
(
assets
)
assets
=
self
.
tree
.
get_assets
()
self
.
_assets
=
assets
...
...
apps/users/api/user.py
View file @
31d2f2a7
...
...
@@ -13,7 +13,8 @@ from rest_framework_bulk import BulkModelViewSet
from
rest_framework.pagination
import
LimitOffsetPagination
from
common.permissions
import
(
IsOrgAdmin
,
IsCurrentUserOrReadOnly
,
IsOrgAdminOrAppUser
IsOrgAdmin
,
IsCurrentUserOrReadOnly
,
IsOrgAdminOrAppUser
,
CanUpdateSuperUser
,
)
from
common.mixins
import
IDInCacheFilterMixin
from
common.utils
import
get_logger
...
...
@@ -37,7 +38,7 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
search_fields
=
filter_fields
queryset
=
User
.
objects
.
exclude
(
role
=
User
.
ROLE_APP
)
serializer_class
=
UserSerializer
permission_classes
=
(
IsOrgAdmin
,)
permission_classes
=
(
IsOrgAdmin
,
CanUpdateSuperUser
)
pagination_class
=
LimitOffsetPagination
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
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
):
deny_instances
=
[
i
for
i
in
instances
if
self
.
_deny_permission
(
i
)]
if
len
(
deny_instances
)
>
0
:
...
...
@@ -108,26 +87,12 @@ class UserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
"""
rewrite because limit org_admin update superuser
"""
partial
=
kwargs
.
pop
(
'partial'
,
False
)
# restrict the update to the filtered queryset
queryset
=
self
.
filter_queryset
(
self
.
get_queryset
())
if
self
.
_bulk_deny_permission
(
queryset
):
data
=
{
'msg'
:
_
(
"You do not have permission."
)}
return
Response
(
data
=
data
,
status
=
status
.
HTTP_403_FORBIDDEN
)
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
)
return
super
()
.
bulk_update
(
request
,
*
args
,
**
kwargs
)
class
UserChangePasswordApi
(
generics
.
RetrieveUpdateAPIView
):
...
...
apps/users/serializers/v1.py
View file @
31d2f2a7
...
...
@@ -39,6 +39,14 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
'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
def
validate_password
(
value
):
from
..utils
import
check_password_rules
...
...
apps/users/templates/users/user_list.html
View file @
31d2f2a7
...
...
@@ -2,27 +2,27 @@
{% load i18n static %}
{% block table_search %}
<div
class=
""
style=
"float: right"
>
<div
class=
" btn-group"
>
<button
data-toggle=
"dropdown"
class=
"btn btn-default btn-sm dropdown-toggle"
>
CSV
<span
class=
"caret"
></span></button>
<ul
class=
"dropdown-menu"
>
<li>
<a
class=
" btn_export"
tabindex=
"0"
>
<span>
{% trans "Export" %}
</span>
</a>
</li>
<li>
<a
class=
" btn_import"
data-toggle=
"modal"
data-target=
"#import_modal"
tabindex=
"0"
>
<span>
{% trans "Import" %}
</span>
</a>
</li>
<li>
<a
class=
" btn_update"
data-toggle=
"modal"
data-target=
"#update_modal"
tabindex=
"0"
>
<span>
{% trans "Update" %}
</span>
</a>
</li>
</ul>
</div>
</div>
<div
class=
" btn-group"
>
<button
data-toggle=
"dropdown"
class=
"btn btn-default btn-sm dropdown-toggle"
>
CSV
<span
class=
"caret"
></span></button>
<ul
class=
"dropdown-menu"
>
<li>
<a
class=
" btn_export"
tabindex=
"0"
>
<span>
{% trans "Export" %}
</span>
</a>
</li>
<li>
<a
class=
" btn_import"
data-toggle=
"modal"
data-target=
"#import_modal"
tabindex=
"0"
>
<span>
{% trans "Import" %}
</span>
</a>
</li>
<li>
<a
class=
" btn_update"
data-toggle=
"modal"
data-target=
"#update_modal"
tabindex=
"0"
>
<span>
{% trans "Update" %}
</span>
</a>
</li>
</ul>
</div>
</div>
{% endblock %}
{% block table_container %}
<div
class=
"uc pull-left m-r-5"
><a
href=
"{% url "
users:user-create
"
%}"
class=
"btn btn-sm btn-primary"
>
{% trans "Create user" %}
</a></div>
...
...
@@ -92,23 +92,23 @@ function initTable() {
}},
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
""
;
if
(
rowData
.
role
===
'Admin'
&&
(
'{{ request.user.role }}'
!==
'Admin'
))
{
update_btn
=
'<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>'
;
}
else
{
{
#
if
(
rowData
.
role
===
'Admin'
&&
(
'{{ request.user.role }}'
!==
'Admin'
))
{
#
}
{
#
update_btn
=
'<a class="btn btn-xs disabled btn-info">{% trans "Update" %}</a>'
;
#
}
{
#
}
#
}
{
#
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
);
}
{
#
}
#
}
var
del_btn
=
""
;
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>'
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
)
.
replace
(
'99991938'
,
rowData
.
name
);
}
else
{
{
#
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>'
#
}
{
#
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
)
#
}
{
#
.
replace
(
'99991938'
,
rowData
.
name
);
#
}
{
#
}
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>'
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
)
.
replace
(
'99991938'
,
rowData
.
name
);
}
{
#
}
#
}
$
(
td
).
html
(
update_btn
+
del_btn
)
}}],
ajax_url
:
'{% url "api-users:user-list" %}'
,
...
...
apps/users/urls/views_urls.py
View file @
31d2f2a7
...
...
@@ -29,9 +29,7 @@ urlpatterns = [
# User view
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
(
'user/import/'
,
views
.
UserBulkImportView
.
as_view
(),
name
=
'user-import'
),
path
(
'user/create/'
,
views
.
UserCreateView
.
as_view
(),
name
=
'user-create'
),
path
(
'user/<uuid:pk>/update/'
,
views
.
UserUpdateView
.
as_view
(),
name
=
'user-update'
),
path
(
'user/update/'
,
views
.
UserBulkUpdateView
.
as_view
(),
name
=
'user-bulk-update'
),
...
...
apps/users/views/user.py
View file @
31d2f2a7
...
...
@@ -46,9 +46,7 @@ from ..signals import post_user_create
__all__
=
[
'UserListView'
,
'UserCreateView'
,
'UserDetailView'
,
'UserUpdateView'
,
'UserGrantedAssetView'
,
'UserExportView'
,
'UserBulkImportView'
,
'UserProfileView'
,
'UserUpdateView'
,
'UserGrantedAssetView'
,
'UserProfileView'
,
'UserProfileUpdateView'
,
'UserPasswordUpdateView'
,
'UserPublicKeyUpdateView'
,
'UserBulkUpdateView'
,
'UserPublicKeyGenerateView'
,
...
...
@@ -223,147 +221,6 @@ class UserDetailView(PermissionsMixin, DetailView):
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
):
model
=
User
template_name
=
'users/user_granted_asset.html'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment