Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
gjl2004yn
jumpserver
提交
f8e248f0
J
jumpserver
项目概览
gjl2004yn
/
jumpserver
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jumpserver
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
f8e248f0
编写于
7月 09, 2020
作者:
X
xinwen
提交者:
baltery
7月 28, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat(ticket): 调整申请资产工单
上级
b3317304
变更
20
展开全部
显示空白变更内容
内联
并排
Showing
20 changed file
with
512 addition
and
204 deletion
+512
-204
apps/common/drf/api.py
apps/common/drf/api.py
+13
-3
apps/common/mixins/api.py
apps/common/mixins/api.py
+11
-0
apps/common/utils/common.py
apps/common/utils/common.py
+2
-0
apps/common/utils/timezone.py
apps/common/utils/timezone.py
+33
-0
apps/jumpserver/settings/custom.py
apps/jumpserver/settings/custom.py
+2
-0
apps/locale/zh/LC_MESSAGES/django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
apps/locale/zh/LC_MESSAGES/django.po
apps/locale/zh/LC_MESSAGES/django.po
+132
-108
apps/orgs/mixins/models.py
apps/orgs/mixins/models.py
+0
-1
apps/tickets/api/request_asset_perm.py
apps/tickets/api/request_asset_perm.py
+43
-48
apps/tickets/api/ticket.py
apps/tickets/api/ticket.py
+1
-1
apps/tickets/exceptions.py
apps/tickets/exceptions.py
+5
-1
apps/tickets/migrations/0002_auto_20200728_1146.py
apps/tickets/migrations/0002_auto_20200728_1146.py
+30
-0
apps/tickets/mixins.py
apps/tickets/mixins.py
+4
-3
apps/tickets/models/ticket.py
apps/tickets/models/ticket.py
+9
-4
apps/tickets/serializers/request_asset_perm.py
apps/tickets/serializers/request_asset_perm.py
+115
-27
apps/tickets/tests.py
apps/tickets/tests.py
+88
-2
apps/tickets/urls/api_urls.py
apps/tickets/urls/api_urls.py
+1
-1
apps/tickets/utils.py
apps/tickets/utils.py
+11
-4
apps/users/models/user.py
apps/users/models/user.py
+5
-0
apps/users/serializers/user.py
apps/users/serializers/user.py
+7
-1
未找到文件。
apps/common/drf/api.py
浏览文件 @
f8e248f0
from
rest_framework.viewsets
import
GenericViewSet
,
ModelViewSet
from
..mixins.api
import
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
from
..mixins.api
import
(
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
,
PaginatedResponseMixin
)
class
JmsGenericViewSet
(
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
,
GenericViewSet
):
class
JmsGenericViewSet
(
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
,
PaginatedResponseMixin
,
GenericViewSet
):
pass
class
JMSModelViewSet
(
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
,
ModelViewSet
):
class
JMSModelViewSet
(
SerializerMixin2
,
QuerySetMixin
,
ExtraFilterFieldsMixin
,
PaginatedResponseMixin
,
ModelViewSet
):
pass
apps/common/mixins/api.py
浏览文件 @
f8e248f0
...
...
@@ -67,6 +67,17 @@ class ExtraFilterFieldsMixin:
return
queryset
class
PaginatedResponseMixin
:
def
get_paginated_response_with_query_set
(
self
,
queryset
):
page
=
self
.
paginate_queryset
(
queryset
)
if
page
is
not
None
:
serializer
=
self
.
get_serializer
(
page
,
many
=
True
)
return
self
.
get_paginated_response
(
serializer
.
data
)
serializer
=
self
.
get_serializer
(
queryset
,
many
=
True
)
return
Response
(
serializer
.
data
)
class
CommonApiMixin
(
SerializerMixin
,
ExtraFilterFieldsMixin
):
pass
...
...
apps/common/utils/common.py
浏览文件 @
f8e248f0
...
...
@@ -11,6 +11,8 @@ import time
import
ipaddress
import
psutil
from
.timezone
import
dt_formater
UUID_PATTERN
=
re
.
compile
(
r
'\w{8}(-\w{4}){3}-\w{12}'
)
ipip_db
=
None
...
...
apps/common/utils/timezone.py
0 → 100644
浏览文件 @
f8e248f0
import
datetime
import
pytz
from
django.utils
import
timezone
as
dj_timezone
from
rest_framework.fields
import
DateTimeField
max
=
datetime
.
datetime
.
max
.
replace
(
tzinfo
=
datetime
.
timezone
.
utc
)
def
astimezone
(
dt
:
datetime
.
datetime
,
tzinfo
:
pytz
.
tzinfo
.
DstTzInfo
):
assert
dj_timezone
.
is_aware
(
dt
)
return
tzinfo
.
normalize
(
dt
.
astimezone
(
tzinfo
))
def
as_china_cst
(
dt
:
datetime
.
datetime
):
return
astimezone
(
dt
,
pytz
.
timezone
(
'Asia/Shanghai'
))
def
as_current_tz
(
dt
:
datetime
.
datetime
):
return
astimezone
(
dt
,
dj_timezone
.
get_current_timezone
())
def
utcnow
():
return
dj_timezone
.
now
()
def
now
():
return
as_current_tz
(
utcnow
())
_rest_dt_field
=
DateTimeField
()
dt_parser
=
_rest_dt_field
.
to_internal_value
dt_formater
=
_rest_dt_field
.
to_representation
apps/jumpserver/settings/custom.py
浏览文件 @
f8e248f0
...
...
@@ -96,3 +96,5 @@ XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
LOGO_URLS
=
DYNAMIC
.
LOGO_URLS
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
=
CONFIG
.
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
DATETIME_DISPLAY_FORMAT
=
'%Y-%m-%d %H:%M:%S'
apps/locale/zh/LC_MESSAGES/django.mo
浏览文件 @
f8e248f0
无法预览此类型文件
apps/locale/zh/LC_MESSAGES/django.po
浏览文件 @
f8e248f0
此差异已折叠。
点击以展开。
apps/orgs/mixins/models.py
浏览文件 @
f8e248f0
...
...
@@ -62,7 +62,6 @@ class OrgModelMixin(models.Model):
org
=
get_current_org
()
if
org
is
None
:
return
super
().
save
(
*
args
,
**
kwargs
)
if
org
.
is_real
()
or
org
.
is_system
():
self
.
org_id
=
org
.
id
elif
org
.
is_default
():
...
...
apps/tickets/api/request_asset_perm.py
浏览文件 @
f8e248f0
from
collections
import
namedtuple
from
django.db.transaction
import
atomic
from
django.db.models
import
F
from
django.db.models
import
Q
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework.decorators
import
action
from
rest_framework.response
import
Response
from
rest_framework.request
import
Request
from
orgs.models
import
Organization
,
ROLE
as
ORG_ROLE
from
users.models.user
import
User
from
common.const.http
import
POST
,
GET
from
common.drf.api
import
JMSModelViewSet
from
common.permissions
import
IsValidUser
from
common.utils.django
import
get_object_or_none
from
common.utils.timezone
import
dt_parser
from
common.drf.serializers
import
EmptySerializer
from
perms.models.asset_permission
import
AssetPermission
,
Asset
from
assets.models.user
import
SystemUser
from
..exceptions
import
(
ConfirmedAssetsChanged
,
ConfirmedSystemUserChanged
,
TicketClosed
,
TicketAction
Yet
,
NotHaveConfirmedAssets
,
TicketClosed
,
TicketAction
Already
,
NotHaveConfirmedAssets
,
NotHaveConfirmedSystemUser
)
from
..
import
serializers
...
...
@@ -25,15 +26,15 @@ from ..permissions import IsAssignee
class
RequestAssetPermTicketViewSet
(
JMSModelViewSet
):
queryset
=
Ticket
.
objects
.
filter
(
type
=
Ticket
.
TYPE_REQUEST_ASSET_PERM
)
queryset
=
Ticket
.
o
rigin_o
bjects
.
filter
(
type
=
Ticket
.
TYPE_REQUEST_ASSET_PERM
)
serializer_classes
=
{
'default'
:
serializers
.
RequestAssetPermTicketSerializer
,
'approve'
:
EmptySerializer
,
'reject'
:
EmptySerializer
,
'assignees'
:
serializers
.
Org
AssigneeSerializer
,
'assignees'
:
serializers
.
AssigneeSerializer
,
}
permission_classes
=
(
IsValidUser
,)
filter_fields
=
[
'status'
,
'title'
,
'action'
,
'user_display'
]
filter_fields
=
[
'status'
,
'title'
,
'action'
,
'user_display'
,
'org_id'
]
search_fields
=
[
'user_display'
,
'title'
]
def
_check_can_set_action
(
self
,
instance
,
action
):
...
...
@@ -41,49 +42,39 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
raise
TicketClosed
(
detail
=
_
(
'Ticket closed'
))
if
instance
.
action
==
action
:
action_display
=
dict
(
instance
.
ACTION_CHOICES
).
get
(
action
)
raise
TicketAction
Yet
(
detail
=
_
(
'Ticket has %s'
)
%
action_display
)
raise
TicketAction
Already
(
detail
=
_
(
'Ticket has %s'
)
%
action_display
)
@
action
(
detail
=
False
,
methods
=
[
GET
],
permission_classes
=
[
IsValidUser
])
def
assignees
(
self
,
request
,
*
args
,
**
kwargs
):
org_mapper
=
{}
UserTuple
=
namedtuple
(
'UserTuple'
,
(
'id'
,
'name'
,
'username'
))
def
assignees
(
self
,
request
:
Request
,
*
args
,
**
kwargs
):
user
=
request
.
user
superusers
=
User
.
objects
.
filter
(
role
=
User
.
ROLE
.
ADMIN
)
admins_with_org
=
User
.
objects
.
filter
(
related_admin_orgs__users
=
user
).
annotate
(
org_id
=
F
(
'related_admin_orgs__id'
),
org_name
=
F
(
'related_admin_orgs__name'
)
)
org_id
=
request
.
query_params
.
get
(
'org_id'
,
Organization
.
DEFAULT_ID
)
for
user
in
admins_with_org
:
org_id
=
user
.
org_id
q
=
Q
(
role
=
User
.
ROLE
.
ADMIN
)
if
org_id
!=
Organization
.
DEFAULT_ID
:
q
|=
Q
(
m2m_org_members__role
=
ORG_ROLE
.
ADMIN
,
orgs__id
=
org_id
,
orgs__members
=
user
)
org_admins
=
User
.
objects
.
filter
(
q
).
distinct
()
if
org_id
not
in
org_mapper
:
org_mapper
[
org_id
]
=
{
'org_name'
:
user
.
org_name
,
'org_admins'
:
set
()
# 去重
}
org_mapper
[
org_id
][
'org_admins'
].
add
(
UserTuple
(
user
.
id
,
user
.
name
,
user
.
username
))
return
self
.
get_paginated_response_with_query_set
(
org_admins
)
result
=
[
{
'org_name'
:
_
(
'Superuser'
),
'org_admins'
:
set
(
UserTuple
(
user
.
id
,
user
.
name
,
user
.
username
)
for
user
in
superusers
)
}
]
def
_get_extra_comment
(
self
,
instance
):
meta
=
instance
.
meta
ips
=
', '
.
join
(
meta
.
get
(
'ips'
,
[]))
confirmed_assets
=
', '
.
join
(
meta
.
get
(
'confirmed_assets'
,
[]))
for
org
in
org_mapper
.
values
():
result
.
append
(
org
)
serializer_class
=
self
.
get_serializer_class
()
serilizer
=
serializer_class
(
instance
=
result
,
many
=
True
)
return
Response
(
data
=
serilizer
.
data
)
return
f
'''
{
_
(
'IP group'
)
}
:
{
ips
}
{
_
(
'Hostname'
)
}
:
{
meta
.
get
(
'hostname'
,
''
)
}
{
_
(
'System user'
)
}
:
{
meta
.
get
(
'system_user'
,
''
)
}
{
_
(
'Confirmed assets'
)
}
:
{
confirmed_assets
}
{
_
(
'Confirmed system user'
)
}
:
{
meta
.
get
(
'confirmed_system_user'
,
''
)
}
'''
@
action
(
detail
=
True
,
methods
=
[
POST
],
permission_classes
=
[
IsAssignee
,
IsValidUser
])
def
reject
(
self
,
request
,
*
args
,
**
kwargs
):
instance
=
self
.
get_object
()
action
=
instance
.
ACTION_REJECT
self
.
_check_can_set_action
(
instance
,
action
)
instance
.
perform_action
(
action
,
request
.
user
)
instance
.
perform_action
(
action
,
request
.
user
,
self
.
_get_extra_comment
(
instance
)
)
return
Response
()
@
action
(
detail
=
True
,
methods
=
[
POST
],
permission_classes
=
[
IsAssignee
,
IsValidUser
])
...
...
@@ -109,29 +100,33 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
if
system_user
is
None
:
raise
ConfirmedSystemUserChanged
(
detail
=
_
(
'Confirmed system-user changed'
))
self
.
_create_asset_permission
(
instance
,
assets
,
system_user
)
self
.
_create_asset_permission
(
instance
,
assets
,
system_user
,
request
.
user
)
return
Response
({
'detail'
:
_
(
'Succeed'
)})
def
_create_asset_permission
(
self
,
instance
:
Ticket
,
assets
,
system_user
):
def
_create_asset_permission
(
self
,
instance
:
Ticket
,
assets
,
system_user
,
user
):
meta
=
instance
.
meta
request
=
self
.
request
ap_kwargs
=
{
'name'
:
meta
.
get
(
'name'
,
''
),
'name'
:
_
(
'From request ticket: {} {}'
).
format
(
instance
.
user_display
,
instance
.
id
),
'created_by'
:
self
.
request
.
user
.
username
,
'comment'
:
_
(
'{} request assets, approved by {}'
).
format
(
instance
.
user_display
,
instance
.
assignee
_display
)
instance
.
assignees
_display
)
}
date_start
=
meta
.
get
(
'date_start'
)
date_expired
=
meta
.
get
(
'date_expired'
)
date_start
=
dt_parser
(
meta
.
get
(
'date_start'
)
)
date_expired
=
dt_parser
(
meta
.
get
(
'date_expired'
)
)
if
date_start
:
ap_kwargs
[
'date_start'
]
=
date_start
if
date_expired
:
ap_kwargs
[
'date_expired'
]
=
date_expired
with
atomic
():
instance
.
perform_action
(
instance
.
ACTION_APPROVE
,
request
.
user
)
instance
.
perform_action
(
instance
.
ACTION_APPROVE
,
request
.
user
,
self
.
_get_extra_comment
(
instance
))
ap
=
AssetPermission
.
objects
.
create
(
**
ap_kwargs
)
ap
.
system_users
.
add
(
system_user
)
ap
.
assets
.
add
(
*
assets
)
ap
.
users
.
add
(
user
)
return
ap
apps/tickets/api/ticket.py
浏览文件 @
f8e248f0
...
...
@@ -11,7 +11,7 @@ from .. import serializers, models, mixins
class
TicketViewSet
(
mixins
.
TicketMixin
,
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
TicketSerializer
queryset
=
models
.
Ticket
.
objects
.
all
()
queryset
=
models
.
Ticket
.
o
rigin_o
bjects
.
all
()
permission_classes
=
(
IsValidUser
,)
filter_fields
=
[
'status'
,
'title'
,
'action'
,
'user_display'
]
search_fields
=
[
'user_display'
,
'title'
]
...
...
apps/tickets/exceptions.py
浏览文件 @
f8e248f0
...
...
@@ -21,5 +21,9 @@ class TicketClosed(JMSException):
pass
class
TicketActionYet
(
JMSException
):
class
TicketActionAlready
(
JMSException
):
pass
class
OrgIdRequiredException
(
JMSException
):
pass
apps/tickets/migrations/0002_auto_2020072
3_1232
.py
→
apps/tickets/migrations/0002_auto_2020072
8_1146
.py
浏览文件 @
f8e248f0
# Generated by Django 2.2.10 on 2020-07-2
3 04:32
# Generated by Django 2.2.10 on 2020-07-2
8 03:46
from
django.db
import
migrations
,
models
import
django.db.models.manager
class
Migration
(
migrations
.
Migration
):
...
...
@@ -10,6 +11,17 @@ class Migration(migrations.Migration):
]
operations
=
[
migrations
.
AlterModelManagers
(
name
=
'ticket'
,
managers
=
[
(
'origin_objects'
,
django
.
db
.
models
.
manager
.
Manager
()),
],
),
migrations
.
AddField
(
model_name
=
'ticket'
,
name
=
'org_id'
,
field
=
models
.
CharField
(
blank
=
True
,
db_index
=
True
,
default
=
''
,
max_length
=
36
,
verbose_name
=
'Organization'
),
),
migrations
.
AlterField
(
model_name
=
'ticket'
,
name
=
'type'
,
...
...
apps/tickets/mixins.py
浏览文件 @
f8e248f0
...
...
@@ -6,11 +6,12 @@ from .models import Ticket
class
TicketMixin
:
def
get_queryset
(
self
):
queryset
=
super
().
get_queryset
()
assign
=
self
.
request
.
GET
.
get
(
'assign'
,
None
)
if
assign
is
None
:
queryset
=
Ticket
.
get_related_tickets
(
self
.
request
.
user
)
queryset
=
Ticket
.
get_related_tickets
(
self
.
request
.
user
,
queryset
)
elif
assign
in
[
'1'
]:
queryset
=
Ticket
.
get_assigned_tickets
(
self
.
request
.
user
)
queryset
=
Ticket
.
get_assigned_tickets
(
self
.
request
.
user
,
queryset
)
else
:
queryset
=
Ticket
.
get_my_tickets
(
self
.
request
.
user
)
queryset
=
Ticket
.
get_my_tickets
(
self
.
request
.
user
,
queryset
)
return
queryset
apps/tickets/models/ticket.py
浏览文件 @
f8e248f0
...
...
@@ -7,11 +7,12 @@ from django.utils.translation import ugettext_lazy as _
from
common.mixins.models
import
CommonModelMixin
from
common.fields.model
import
JsonDictTextField
from
orgs.mixins.models
import
OrgModelMixin
__all__
=
[
'Ticket'
,
'Comment'
]
class
Ticket
(
CommonModelMixin
):
class
Ticket
(
OrgModelMixin
,
CommonModelMixin
):
STATUS_OPEN
=
'open'
STATUS_CLOSED
=
'closed'
STATUS_CHOICES
=
(
...
...
@@ -46,6 +47,8 @@ class Ticket(CommonModelMixin):
status
=
models
.
CharField
(
choices
=
STATUS_CHOICES
,
max_length
=
16
,
default
=
'open'
)
action
=
models
.
CharField
(
choices
=
ACTION_CHOICES
,
max_length
=
16
,
default
=
''
,
blank
=
True
)
origin_objects
=
models
.
Manager
()
def
__str__
(
self
):
return
'{}: {}'
.
format
(
self
.
user_display
,
self
.
title
)
...
...
@@ -79,13 +82,15 @@ class Ticket(CommonModelMixin):
self
.
status
=
status
self
.
save
()
def
create_action_comment
(
self
,
action
,
user
):
def
create_action_comment
(
self
,
action
,
user
,
extra_comment
=
None
):
action_display
=
dict
(
self
.
ACTION_CHOICES
).
get
(
action
)
body
=
'{} {} {}'
.
format
(
user
,
action_display
,
_
(
"this ticket"
))
if
extra_comment
is
not
None
:
body
+=
extra_comment
self
.
comments
.
create
(
body
=
body
,
user
=
user
,
user_display
=
str
(
user
))
def
perform_action
(
self
,
action
,
user
):
self
.
create_action_comment
(
action
,
user
)
def
perform_action
(
self
,
action
,
user
,
extra_comment
=
None
):
self
.
create_action_comment
(
action
,
user
,
extra_comment
)
self
.
action
=
action
self
.
status
=
self
.
STATUS_CLOSED
self
.
assignee
=
user
...
...
apps/tickets/serializers/request_asset_perm.py
浏览文件 @
f8e248f0
from
itertools
import
chain
from
rest_framework
import
serializers
from
django.conf
import
settings
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.urls
import
reverse
from
django.db.models
import
Q
from
common.utils.timezone
import
dt_parser
,
dt_formater
from
orgs.utils
import
tmp_to_root_org
from
orgs.models
import
Organization
,
ROLE
as
ORG_ROLE
from
assets.models.asset
import
Asset
from
users.models.user
import
User
from
..models
import
Ticket
...
...
@@ -22,9 +29,8 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
source
=
'meta.confirmed_assets'
,
default
=
list
,
required
=
False
,
label
=
_
(
'Confirmed assets'
))
confirmed_system_user
=
serializers
.
ListField
(
child
=
serializers
.
UUIDField
(),
source
=
'meta.confirmed_system_user'
,
default
=
list
,
required
=
False
,
confirmed_system_user
=
serializers
.
UUIDField
(
source
=
'meta.confirmed_system_user'
,
default
=
''
,
required
=
False
,
label
=
_
(
'Confirmed system user'
))
assets_waitlist_url
=
serializers
.
SerializerMethodField
()
system_user_waitlist_url
=
serializers
.
SerializerMethodField
()
...
...
@@ -36,7 +42,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
'status'
,
'action'
,
'date_created'
,
'date_updated'
,
'system_user_waitlist_url'
,
'type'
,
'type_display'
,
'action_display'
,
'ips'
,
'confirmed_assets'
,
'date_start'
,
'date_expired'
,
'confirmed_system_user'
,
'hostname'
,
'assets_waitlist_url'
,
'system_user'
'assets_waitlist_url'
,
'system_user'
,
'org_id'
]
m2m_fields
=
[
'user'
,
'user_display'
,
'assignees'
,
'assignees_display'
,
...
...
@@ -52,26 +58,44 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
extra_kwargs
=
{
'status'
:
{
'label'
:
_
(
'Status'
)},
'action'
:
{
'label'
:
_
(
'Action'
)},
'user_display'
:
{
'label'
:
_
(
'User'
)}
'user_display'
:
{
'label'
:
_
(
'User'
)},
'org_id'
:
{
'required'
:
True
}
}
def
validate_assignees
(
self
,
assignees
):
def
validate
(
self
,
attrs
):
org_id
=
attrs
.
get
(
'org_id'
)
assignees
=
attrs
.
get
(
'assignees'
)
instance
=
self
.
instance
if
instance
is
not
None
:
if
org_id
and
not
assignees
:
assignees
=
list
(
instance
.
assignees
.
all
())
elif
assignees
and
not
org_id
:
org_id
=
instance
.
org_id
elif
assignees
and
org_id
:
pass
else
:
return
attrs
user
=
self
.
context
[
'request'
].
user
org
=
Organization
.
get_instance
(
org_id
)
if
org
is
None
:
raise
serializers
.
ValidationError
(
_
(
'Invalid `org_id`'
))
count
=
User
.
objects
.
filter
(
Q
(
related_admin_orgs__users
=
user
)
|
Q
(
role
=
User
.
ROLE
.
ADMIN
)).
filter
(
id__in
=
[
assignee
.
id
for
assignee
in
assignees
]).
distinct
().
count
()
q
=
Q
(
role
=
User
.
ROLE
.
ADMIN
)
if
not
org
.
is_default
():
q
|=
Q
(
m2m_org_members__role
=
ORG_ROLE
.
ADMIN
,
orgs__id
=
org_id
,
orgs__members
=
user
)
q
&=
Q
(
id__in
=
[
assignee
.
id
for
assignee
in
assignees
])
count
=
User
.
objects
.
filter
(
q
).
distinct
().
count
()
if
count
!=
len
(
assignees
):
raise
serializers
.
ValidationError
(
_
(
'
M
ust be organization admin or superuser'
))
return
a
ssignee
s
raise
serializers
.
ValidationError
(
_
(
'
Field `assignees` m
ust be organization admin or superuser'
))
return
a
ttr
s
def
get_system_user_waitlist_url
(
self
,
instance
:
Ticket
):
if
not
self
.
_is_assignee
(
instance
):
return
None
meta
=
instance
.
meta
url
=
reverse
(
'api-assets:system-user-list'
)
query
=
meta
.
get
(
'system_user'
,
''
)
return
'{}?search={}'
.
format
(
url
,
query
)
return
reverse
(
'api-assets:system-user-list'
)
def
get_assets_waitlist_url
(
self
,
instance
:
Ticket
):
if
not
self
.
_is_assignee
(
instance
):
...
...
@@ -81,37 +105,106 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
query
=
''
meta
=
instance
.
meta
ips
=
meta
.
get
(
'ips'
,
[])
hostname
=
meta
.
get
(
'hostname'
)
if
ips
:
query
=
'?ips=%s'
%
','
.
join
(
ips
)
elif
hostname
:
if
hostname
:
query
=
'?search=%s'
%
hostname
return
asset_api
+
query
def
_recommend_assets
(
self
,
data
,
instance
):
confirmed_assets
=
data
.
get
(
'confirmed_assets'
)
if
not
confirmed_assets
and
self
.
_is_assignee
(
instance
):
ips
=
data
.
get
(
'ips'
)
hostname
=
data
.
get
(
'hostname'
)
limit
=
5
q
=
Q
(
id
=
None
)
if
ips
:
limit
=
len
(
ips
)
+
2
q
|=
Q
(
ip__in
=
ips
)
if
hostname
:
q
|=
Q
(
hostname__icontains
=
hostname
)
data
[
'confirmed_assets'
]
=
list
(
map
(
lambda
x
:
str
(
x
),
chain
(
*
Asset
.
objects
.
filter
(
q
)[
0
:
limit
].
values_list
(
'id'
))))
def
to_representation
(
self
,
instance
):
data
=
super
().
to_representation
(
instance
)
self
.
_recommend_assets
(
data
,
instance
)
return
data
def
_create_body
(
self
,
validated_data
):
meta
=
validated_data
[
'meta'
]
type
=
dict
(
Ticket
.
TYPE_CHOICES
).
get
(
validated_data
.
get
(
'type'
,
''
))
date_start
=
dt_parser
(
meta
.
get
(
'date_start'
)).
strftime
(
settings
.
DATETIME_DISPLAY_FORMAT
)
date_expired
=
dt_parser
(
meta
.
get
(
'date_expired'
)).
strftime
(
settings
.
DATETIME_DISPLAY_FORMAT
)
validated_data
[
'body'
]
=
_
(
'''
Type: {type}<br>
User: {username}<br>
Ip group: {ips}<br>
Hostname: {hostname}<br>
System user: {system_user}<br>
Date start: {date_start}<br>
Date expired: {date_expired}<br>
'''
).
format
(
type
=
type
,
username
=
validated_data
.
get
(
'user'
,
''
),
ips
=
', '
.
join
(
meta
.
get
(
'ips'
,
[])),
hostname
=
meta
.
get
(
'hostname'
,
''
),
system_user
=
meta
.
get
(
'system_user'
,
''
),
date_start
=
date_start
,
date_expired
=
date_expired
)
def
create
(
self
,
validated_data
):
# `type` 与 `user` 用户不可提交,
validated_data
[
'type'
]
=
self
.
Meta
.
model
.
TYPE_REQUEST_ASSET_PERM
validated_data
[
'user'
]
=
self
.
context
[
'request'
].
user
# `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉
self
.
_pop_confirmed_fields
()
self
.
_create_body
(
validated_data
)
return
super
().
create
(
validated_data
)
def
save
(
self
,
**
kwargs
):
"""
做了一些数据转换
"""
meta
=
self
.
validated_data
.
get
(
'meta'
,
{})
org_id
=
self
.
validated_data
.
get
(
'org_id'
)
if
org_id
is
not
None
and
org_id
==
Organization
.
DEFAULT_ID
:
self
.
validated_data
[
'org_id'
]
=
''
# 时间的转换,好烦😭,可能有更好的办法吧
date_start
=
meta
.
get
(
'date_start'
)
if
date_start
:
meta
[
'date_start'
]
=
d
ate_start
.
strftime
(
'%Y-%m-%d %H:%M:%S%z'
)
meta
[
'date_start'
]
=
d
t_formater
(
date_start
)
date_expired
=
meta
.
get
(
'date_expired'
)
if
date_expired
:
meta
[
'date_expired'
]
=
date_expired
.
strftime
(
'%Y-%m-%d %H:%M:%S%z'
)
meta
[
'date_expired'
]
=
dt_formater
(
date_expired
)
# UUID 的转换
confirmed_system_user
=
meta
.
get
(
'confirmed_system_user'
)
if
confirmed_system_user
:
meta
[
'confirmed_system_user'
]
=
str
(
confirmed_system_user
)
confirmed_assets
=
meta
.
get
(
'confirmed_assets'
)
if
confirmed_assets
:
new_confirmed_assets
=
[]
for
asset
in
confirmed_assets
:
new_confirmed_assets
.
append
(
str
(
asset
))
meta
[
'confirmed_assets'
]
=
new_confirmed_assets
with
tmp_to_root_org
():
return
super
().
save
(
**
kwargs
)
def
update
(
self
,
instance
,
validated_data
):
new_meta
=
validated_data
[
'meta'
]
if
not
self
.
_is_assignee
(
instance
):
self
.
_pop_confirmed_fields
()
# Json 字段保存的坑😭
old_meta
=
instance
.
meta
meta
=
{}
meta
.
update
(
old_meta
)
...
...
@@ -134,8 +227,3 @@ class AssigneeSerializer(serializers.Serializer):
id
=
serializers
.
UUIDField
()
name
=
serializers
.
CharField
()
username
=
serializers
.
CharField
()
class
OrgAssigneeSerializer
(
serializers
.
Serializer
):
org_name
=
serializers
.
CharField
()
org_admins
=
AssigneeSerializer
(
many
=
True
)
apps/tickets/tests.py
浏览文件 @
f8e248f0
from
django.test
import
TestCas
e
import
datetim
e
# Create your tests here.
from
common.utils.timezone
import
now
from
django.urls
import
reverse
from
rest_framework.test
import
APITestCase
from
rest_framework
import
status
from
orgs.models
import
Organization
,
OrganizationMember
,
ROLE
as
ORG_ROLE
from
orgs.utils
import
set_current_org
from
users.models.user
import
User
from
assets.models
import
Asset
,
AdminUser
,
SystemUser
class
TicketTest
(
APITestCase
):
def
setUp
(
self
):
Organization
.
objects
.
bulk_create
([
Organization
(
name
=
'org-01'
),
Organization
(
name
=
'org-02'
),
Organization
(
name
=
'org-03'
),
])
org_01
,
org_02
,
org_03
=
Organization
.
objects
.
all
()
self
.
org_01
,
self
.
org_02
,
self
.
org_03
=
org_01
,
org_02
,
org_03
set_current_org
(
org_01
)
AdminUser
.
objects
.
bulk_create
([
AdminUser
(
name
=
'au-01'
,
username
=
'au-01'
),
AdminUser
(
name
=
'au-02'
,
username
=
'au-02'
),
AdminUser
(
name
=
'au-03'
,
username
=
'au-03'
),
])
SystemUser
.
objects
.
bulk_create
([
SystemUser
(
name
=
'su-01'
,
username
=
'su-01'
),
SystemUser
(
name
=
'su-02'
,
username
=
'su-02'
),
SystemUser
(
name
=
'su-03'
,
username
=
'su-03'
),
])
admin_users
=
AdminUser
.
objects
.
all
()
Asset
.
objects
.
bulk_create
([
Asset
(
hostname
=
'asset-01'
,
ip
=
'192.168.1.1'
,
public_ip
=
'192.168.1.1'
,
admin_user
=
admin_users
[
0
]),
Asset
(
hostname
=
'asset-02'
,
ip
=
'192.168.1.2'
,
public_ip
=
'192.168.1.2'
,
admin_user
=
admin_users
[
0
]),
Asset
(
hostname
=
'asset-03'
,
ip
=
'192.168.1.3'
,
public_ip
=
'192.168.1.3'
,
admin_user
=
admin_users
[
0
]),
])
new_user
=
User
.
objects
.
create
new_org_memeber
=
OrganizationMember
.
objects
.
create
u
=
new_user
(
name
=
'user-01'
,
username
=
'user-01'
,
email
=
'user-01@jms.com'
)
new_org_memeber
(
org
=
org_01
,
user
=
u
,
role
=
ORG_ROLE
.
USER
)
new_org_memeber
(
org
=
org_02
,
user
=
u
,
role
=
ORG_ROLE
.
USER
)
self
.
user_01
=
u
u
=
new_user
(
name
=
'org-admin-01'
,
username
=
'org-admin-01'
,
email
=
'org-admin-01@jms.com'
)
new_org_memeber
(
org
=
org_01
,
user
=
u
,
role
=
ORG_ROLE
.
ADMIN
)
self
.
org_admin_01
=
u
u
=
new_user
(
name
=
'org-admin-02'
,
username
=
'org-admin-02'
,
email
=
'org-admin-02@jms.com'
)
new_org_memeber
(
org
=
org_02
,
user
=
u
,
role
=
ORG_ROLE
.
ADMIN
)
self
.
org_admin_02
=
u
def
test_create_request_asset_perm
(
self
):
url
=
reverse
(
'api-tickets:ticket-request-asset-perm'
)
ticket_url
=
reverse
(
'api-tickets:ticket'
)
self
.
client
.
force_login
(
self
.
user_01
)
date_start
=
now
()
date_expired
=
date_start
+
datetime
.
timedelta
(
days
=
7
)
data
=
{
"title"
:
"request-01"
,
"ips"
:
[
"192.168.1.1"
],
"date_start"
:
date_start
,
"date_expired"
:
date_expired
,
"hostname"
:
""
,
"system_user"
:
""
,
"org_id"
:
self
.
org_01
.
id
,
"assignees"
:
[
str
(
self
.
org_admin_01
.
id
),
str
(
self
.
org_admin_02
.
id
),
]
}
self
.
client
.
post
(
data
)
self
.
client
.
force_login
(
self
.
org_admin_01
)
res
=
self
.
client
.
get
(
ticket_url
,
params
=
{
'assgin'
:
1
})
apps/tickets/urls/api_urls.py
浏览文件 @
f8e248f0
...
...
@@ -7,7 +7,7 @@ from .. import api
app_name
=
'tickets'
router
=
BulkRouter
()
#
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
router
.
register
(
'tickets/request-asset-perm'
,
api
.
RequestAssetPermTicketViewSet
,
'ticket-request-asset-perm'
)
router
.
register
(
'tickets'
,
api
.
TicketViewSet
,
'ticket'
)
router
.
register
(
'tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments'
,
api
.
TicketCommentViewSet
,
'ticket-comment'
)
...
...
apps/tickets/utils.py
浏览文件 @
f8e248f0
# -*- coding: utf-8 -*-
#
from
urllib.parse
import
urljoin
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
common.utils
import
get_logger
,
reverse
from
common.utils
import
get_logger
from
common.tasks
import
send_mail_async
logger
=
get_logger
(
__name__
)
from
tickets.models
import
Ticket
def
send_new_ticket_mail_to_assignees
(
ticket
,
assignees
):
def
send_new_ticket_mail_to_assignees
(
ticket
:
Ticket
,
assignees
):
recipient_list
=
[
user
.
email
for
user
in
assignees
]
user
=
ticket
.
user
if
not
recipient_list
:
logger
.
error
(
"Ticket not has assignees: {}"
.
format
(
ticket
.
id
))
return
subject
=
'{}: {}'
.
format
(
_
(
"New ticket"
),
ticket
.
title
)
detail_url
=
reverse
(
'tickets:ticket-detail'
,
kwargs
=
{
'pk'
:
ticket
.
id
},
external
=
True
)
# 这里要设置前端地址,因为要直接跳转到页面
if
ticket
.
type
==
ticket
.
TYPE_REQUEST_ASSET_PERM
:
detail_url
=
urljoin
(
settings
.
SITE_URL
,
f
'/tickets/tickets/request-asset-perm/
{
ticket
.
id
}
'
)
else
:
detail_url
=
urljoin
(
settings
.
SITE_URL
,
f
'/tickets/tickets/
{
ticket
.
id
}
'
)
message
=
_
(
"""
<div>
<p>Your has a new ticket</p>
...
...
apps/users/models/user.py
浏览文件 @
f8e248f0
...
...
@@ -233,6 +233,11 @@ class RoleMixin:
def
is_app
(
self
):
return
self
.
role
==
self
.
ROLE
.
APP
@
lazyproperty
def
user_all_orgs
(
self
):
from
orgs.models
import
Organization
return
Organization
.
get_user_all_orgs
(
self
)
@
lazyproperty
def
user_orgs
(
self
):
from
orgs.models
import
Organization
...
...
apps/users/serializers/user.py
浏览文件 @
f8e248f0
...
...
@@ -27,6 +27,11 @@ class UserOrgSerializer(serializers.Serializer):
name
=
serializers
.
CharField
()
class
UserOrgLabelSerializer
(
serializers
.
Serializer
):
value
=
serializers
.
CharField
(
source
=
'id'
)
label
=
serializers
.
CharField
(
source
=
'name'
)
class
UserSerializer
(
CommonBulkSerializerMixin
,
serializers
.
ModelSerializer
):
EMAIL_SET_PASSWORD
=
_
(
'Reset link will be generated and sent to the user'
)
CUSTOM_PASSWORD
=
_
(
'Set password'
)
...
...
@@ -214,6 +219,7 @@ class UserRoleSerializer(serializers.Serializer):
class
UserProfileSerializer
(
UserSerializer
):
admin_or_audit_orgs
=
UserOrgSerializer
(
many
=
True
,
read_only
=
True
)
user_all_orgs
=
UserOrgLabelSerializer
(
many
=
True
,
read_only
=
True
)
current_org_roles
=
serializers
.
ListField
(
read_only
=
True
)
public_key_comment
=
serializers
.
CharField
(
source
=
'get_public_key_comment'
,
required
=
False
,
read_only
=
True
,
max_length
=
128
...
...
@@ -231,7 +237,7 @@ class UserProfileSerializer(UserSerializer):
class
Meta
(
UserSerializer
.
Meta
):
fields
=
UserSerializer
.
Meta
.
fields
+
[
'public_key_comment'
,
'public_key_hash_md5'
,
'admin_or_audit_orgs'
,
'current_org_roles'
,
'guide_url'
'guide_url'
,
'user_all_orgs'
]
extra_kwargs
=
dict
(
UserSerializer
.
Meta
.
extra_kwargs
)
extra_kwargs
.
update
({
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录