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
f9e41d71
Commit
f9e41d71
authored
Nov 08, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 修改登录工单
parent
08775551
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
415 additions
and
253 deletions
+415
-253
login_confirm.py
apps/authentication/api/login_confirm.py
+26
-24
errors.py
apps/authentication/errors.py
+6
-9
mixins.py
apps/authentication/mixins.py
+1
-1
models.py
apps/authentication/models.py
+1
-4
serializers.py
apps/authentication/serializers.py
+6
-5
login_wait_confirm.html
...tication/templates/authentication/login_wait_confirm.html
+1
-1
api_urls.py
apps/authentication/urls/api_urls.py
+1
-1
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
jumpserver.js
apps/static/js/jumpserver.js
+4
-0
_nav.html
apps/templates/_nav.html
+1
-1
base.py
apps/tickets/api/base.py
+26
-9
login_confirm.py
apps/tickets/api/login_confirm.py
+16
-25
apps.py
apps/tickets/apps.py
+4
-0
mixins.py
apps/tickets/mixins.py
+18
-0
base.py
apps/tickets/models/base.py
+16
-8
login_confirm.py
apps/tickets/models/login_confirm.py
+13
-0
permissions.py
apps/tickets/permissions.py
+6
-0
base.py
apps/tickets/serializers/base.py
+17
-0
login_confirm.py
apps/tickets/serializers/login_confirm.py
+16
-1
signals_handler.py
apps/tickets/signals_handler.py
+22
-4
login_confirm_ticket_detail.html
...ickets/templates/tickets/login_confirm_ticket_detail.html
+25
-128
login_confirm_ticket_list.html
.../tickets/templates/tickets/login_confirm_ticket_list.html
+11
-19
ticket_detail.html
apps/tickets/templates/tickets/ticket_detail.html
+162
-0
api_urls.py
apps/tickets/urls/api_urls.py
+1
-5
views.py
apps/tickets/views.py
+6
-7
user.py
apps/users/serializers/user.py
+9
-1
No files found.
apps/authentication/api/login_confirm.py
View file @
f9e41d71
...
@@ -4,6 +4,7 @@ from rest_framework.generics import UpdateAPIView
...
@@ -4,6 +4,7 @@ from rest_framework.generics import UpdateAPIView
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.views
import
APIView
from
rest_framework.views
import
APIView
from
django.shortcuts
import
get_object_or_404
from
django.shortcuts
import
get_object_or_404
from
django.utils.translation
import
ugettext
as
_
from
common.utils
import
get_logger
,
get_object_or_none
from
common.utils
import
get_logger
,
get_object_or_none
from
common.permissions
import
IsOrgAdmin
from
common.permissions
import
IsOrgAdmin
...
@@ -11,7 +12,7 @@ from ..models import LoginConfirmSetting
...
@@ -11,7 +12,7 @@ from ..models import LoginConfirmSetting
from
..serializers
import
LoginConfirmSettingSerializer
from
..serializers
import
LoginConfirmSettingSerializer
from
..
import
errors
from
..
import
errors
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'
UserTicketAcceptAuth
Api'
]
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'
LoginConfirmTicketStatus
Api'
]
logger
=
get_logger
(
__name__
)
logger
=
get_logger
(
__name__
)
...
@@ -30,10 +31,10 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
...
@@ -30,10 +31,10 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
return
s
return
s
class
UserTicketAcceptAuth
Api
(
APIView
):
class
LoginConfirmTicketStatus
Api
(
APIView
):
permission_classes
=
()
permission_classes
=
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
_ticket
(
self
):
from
tickets.models
import
LoginConfirmTicket
from
tickets.models
import
LoginConfirmTicket
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
logger
.
debug
(
'Login confirm ticket id: {}'
.
format
(
ticket_id
))
logger
.
debug
(
'Login confirm ticket id: {}'
.
format
(
ticket_id
))
...
@@ -41,31 +42,32 @@ class UserTicketAcceptAuthApi(APIView):
...
@@ -41,31 +42,32 @@ class UserTicketAcceptAuthApi(APIView):
ticket
=
None
ticket
=
None
else
:
else
:
ticket
=
get_object_or_none
(
LoginConfirmTicket
,
pk
=
ticket_id
)
ticket
=
get_object_or_none
(
LoginConfirmTicket
,
pk
=
ticket_id
)
return
ticket
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
ticket
=
self
.
get_ticket
()
try
:
try
:
if
not
ticket
:
if
not
ticket
:
raise
errors
.
LoginConfirmTicketNotFound
(
ticket_id
)
raise
errors
.
LoginConfirmOtherError
(
ticket_id
,
_
(
"not found"
))
if
ticket
.
action
==
LoginConfirmTicket
.
ACTION_APPROVE
:
if
ticket
.
status
==
'open'
:
raise
errors
.
LoginConfirmWaitError
(
ticket_id
)
elif
ticket
.
action
==
ticket
.
ACTION_APPROVE
:
self
.
request
.
session
[
"auth_confirm"
]
=
"1"
self
.
request
.
session
[
"auth_confirm"
]
=
"1"
return
Response
({
"msg"
:
"ok"
})
return
Response
({
"msg"
:
"ok"
})
elif
ticket
.
action
==
LoginConfirmTicket
.
ACTION_REJECT
:
elif
ticket
.
action
==
ticket
.
ACTION_REJECT
:
raise
errors
.
LoginConfirmRejectedError
(
ticket_id
)
raise
errors
.
LoginConfirmOtherError
(
ticket_id
,
ticket
.
get_action_display
()
)
else
:
else
:
raise
errors
.
LoginConfirmWaitError
(
ticket_id
)
raise
errors
.
LoginConfirmOtherError
(
ticket_id
,
ticket
.
get_status_display
()
)
except
errors
.
AuthFailedError
as
e
:
except
errors
.
AuthFailedError
as
e
:
data
=
e
.
as_data
()
return
Response
(
e
.
as_data
(),
status
=
400
)
return
Response
(
data
,
status
=
400
)
class
UserTicketCancelAuthApi
(
APIView
):
permission_classes
=
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
from
tickets.models
import
LoginConfirmTicket
ticket
=
self
.
get_ticket
()
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
if
ticket
:
logger
.
debug
(
'Login confirm ticket id: {}'
.
format
(
ticket_id
))
ticket
.
perform_status
(
'closed'
,
request
.
user
)
if
not
ticket_id
:
return
Response
(
''
,
status
=
200
)
ticket
=
None
else
:
ticket
=
get_object_or_none
(
LoginConfirmTicket
,
pk
=
ticket_id
)
if
not
ticket
:
ticket
.
status
=
"close"
apps/authentication/errors.py
View file @
f9e41d71
...
@@ -48,8 +48,7 @@ mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
...
@@ -48,8 +48,7 @@ mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
mfa_required_msg
=
_
(
"MFA required"
)
mfa_required_msg
=
_
(
"MFA required"
)
login_confirm_required_msg
=
_
(
"Login confirm required"
)
login_confirm_required_msg
=
_
(
"Login confirm required"
)
login_confirm_wait_msg
=
_
(
"Wait login confirm ticket for accept"
)
login_confirm_wait_msg
=
_
(
"Wait login confirm ticket for accept"
)
login_confirm_rejected_msg
=
_
(
"Login confirm ticket was rejected"
)
login_confirm_error_msg
=
_
(
"Login confirm ticket was {}"
)
login_confirm_ticket_not_found_msg
=
_
(
"Ticket not found"
)
class
AuthFailedNeedLogMixin
:
class
AuthFailedNeedLogMixin
:
...
@@ -174,11 +173,9 @@ class LoginConfirmWaitError(LoginConfirmError):
...
@@ -174,11 +173,9 @@ class LoginConfirmWaitError(LoginConfirmError):
error
=
'login_confirm_wait'
error
=
'login_confirm_wait'
class
LoginConfirmRejectedError
(
LoginConfirmError
):
class
LoginConfirmOtherError
(
LoginConfirmError
):
msg
=
login_confirm_rejected_msg
error
=
'login_confirm_error'
error
=
'login_confirm_rejected'
def
__init__
(
self
,
ticket_id
,
status
):
class
LoginConfirmTicketNotFound
(
LoginConfirmError
):
msg
=
login_confirm_error_msg
.
format
(
status
)
msg
=
login_confirm_ticket_not_found_msg
super
()
.
__init__
(
ticket_id
=
ticket_id
,
msg
=
msg
)
error
=
'login_confirm_ticket_not_found'
apps/authentication/mixins.py
View file @
f9e41d71
...
@@ -106,7 +106,7 @@ class AuthMixin:
...
@@ -106,7 +106,7 @@ class AuthMixin:
if
ticket
.
status
==
"accepted"
:
if
ticket
.
status
==
"accepted"
:
return
return
elif
ticket
.
status
==
"rejected"
:
elif
ticket
.
status
==
"rejected"
:
raise
errors
.
LoginConfirm
Rejected
Error
(
ticket
.
id
)
raise
errors
.
LoginConfirm
Other
Error
(
ticket
.
id
)
else
:
else
:
raise
errors
.
LoginConfirmWaitError
(
ticket
.
id
)
raise
errors
.
LoginConfirmWaitError
(
ticket
.
id
)
...
...
apps/authentication/models.py
View file @
f9e41d71
...
@@ -62,12 +62,9 @@ class LoginConfirmSetting(CommonModelMixin):
...
@@ -62,12 +62,9 @@ class LoginConfirmSetting(CommonModelMixin):
remote_addr
=
'127.0.0.1'
remote_addr
=
'127.0.0.1'
body
=
''
body
=
''
reviewer
=
self
.
reviewers
.
all
()
reviewer
=
self
.
reviewers
.
all
()
reviewer_names
=
','
.
join
([
u
.
name
for
u
in
reviewer
])
ticket
=
LoginConfirmTicket
.
objects
.
create
(
ticket
=
LoginConfirmTicket
.
objects
.
create
(
user
=
self
.
user
,
user_display
=
str
(
self
.
user
),
user
=
self
.
user
,
title
=
title
,
body
=
body
,
title
=
title
,
body
=
body
,
city
=
city
,
ip
=
remote_addr
,
city
=
city
,
ip
=
remote_addr
,
assignees_display
=
reviewer_names
,
type
=
LoginConfirmTicket
.
TYPE_LOGIN_CONFIRM
,
type
=
LoginConfirmTicket
.
TYPE_LOGIN_CONFIRM
,
)
)
ticket
.
assignees
.
set
(
reviewer
)
ticket
.
assignees
.
set
(
reviewer
)
...
...
apps/authentication/serializers.py
View file @
f9e41d71
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
django.core.cache
import
cache
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
common.utils
import
get_object_or_none
from
common.utils
import
get_object_or_none
from
users.models
import
User
from
users.models
import
User
from
users.serializers
import
UserProfileSerializer
from
.models
import
AccessKey
,
LoginConfirmSetting
from
.models
import
AccessKey
,
LoginConfirmSetting
...
@@ -26,14 +26,15 @@ class OtpVerifySerializer(serializers.Serializer):
...
@@ -26,14 +26,15 @@ class OtpVerifySerializer(serializers.Serializer):
class
BearerTokenSerializer
(
serializers
.
Serializer
):
class
BearerTokenSerializer
(
serializers
.
Serializer
):
username
=
serializers
.
CharField
(
allow_null
=
True
,
required
=
False
)
username
=
serializers
.
CharField
(
allow_null
=
True
,
required
=
False
,
write_only
=
True
)
password
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
password
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
required
=
False
,
allow_blank
=
True
)
public_key
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
public_key
=
serializers
.
CharField
(
write_only
=
True
,
allow_null
=
True
,
required
=
False
)
allow_blank
=
True
,
required
=
False
)
token
=
serializers
.
CharField
(
read_only
=
True
)
token
=
serializers
.
CharField
(
read_only
=
True
)
keyword
=
serializers
.
SerializerMethodField
()
keyword
=
serializers
.
SerializerMethodField
()
date_expired
=
serializers
.
DateTimeField
(
read_only
=
True
)
date_expired
=
serializers
.
DateTimeField
(
read_only
=
True
)
user
=
UserProfileSerializer
(
read_only
=
True
)
@staticmethod
@staticmethod
def
get_keyword
(
obj
):
def
get_keyword
(
obj
):
...
@@ -52,9 +53,9 @@ class BearerTokenSerializer(serializers.Serializer):
...
@@ -52,9 +53,9 @@ class BearerTokenSerializer(serializers.Serializer):
)
)
token
,
date_expired
=
user
.
create_bearer_token
(
request
)
token
,
date_expired
=
user
.
create_bearer_token
(
request
)
instance
=
{
instance
=
{
"username"
:
user
.
username
,
"token"
:
token
,
"token"
:
token
,
"date_expired"
:
date_expired
,
"date_expired"
:
date_expired
,
"user"
:
user
}
}
return
instance
return
instance
...
...
apps/authentication/templates/authentication/login_wait_confirm.html
View file @
f9e41d71
...
@@ -73,7 +73,7 @@ var infoMsgRef = $(".info-messages");
...
@@ -73,7 +73,7 @@ var infoMsgRef = $(".info-messages");
var
timestamp
=
'{{ timestamp }}'
;
var
timestamp
=
'{{ timestamp }}'
;
var
progressBarRef
=
$
(
".progress-bar"
);
var
progressBarRef
=
$
(
".progress-bar"
);
var
interval
,
checkInterval
;
var
interval
,
checkInterval
;
var
url
=
"{% url 'api-auth:
user-order-auth
' %}"
;
var
url
=
"{% url 'api-auth:
login-confirm-ticket-status
' %}"
;
var
successUrl
=
"{% url 'authentication:login-guard' %}"
;
var
successUrl
=
"{% url 'authentication:login-guard' %}"
;
function
doRequestAuth
()
{
function
doRequestAuth
()
{
...
...
apps/authentication/urls/api_urls.py
View file @
f9e41d71
...
@@ -18,7 +18,7 @@ urlpatterns = [
...
@@ -18,7 +18,7 @@ urlpatterns = [
path
(
'connection-token/'
,
path
(
'connection-token/'
,
api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
path
(
'otp/verify/'
,
api
.
UserOtpVerifyApi
.
as_view
(),
name
=
'user-otp-verify'
),
path
(
'otp/verify/'
,
api
.
UserOtpVerifyApi
.
as_view
(),
name
=
'user-otp-verify'
),
path
(
'
order/auth/'
,
api
.
UserTicketAcceptAuthApi
.
as_view
(),
name
=
'user-order-auth
'
),
path
(
'
login-confirm-ticket/status/'
,
api
.
LoginConfirmTicketStatusApi
.
as_view
(),
name
=
'login-confirm-ticket-status
'
),
path
(
'login-confirm-settings/<uuid:user_id>/'
,
api
.
LoginConfirmSettingUpdateApi
.
as_view
(),
name
=
'login-confirm-setting-update'
)
path
(
'login-confirm-settings/<uuid:user_id>/'
,
api
.
LoginConfirmSettingUpdateApi
.
as_view
(),
name
=
'login-confirm-setting-update'
)
]
]
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
f9e41d71
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
f9e41d71
This diff is collapsed.
Click to expand it.
apps/static/js/jumpserver.js
View file @
f9e41d71
...
@@ -1317,3 +1317,7 @@ function initDateRangePicker(selector, options) {
...
@@ -1317,3 +1317,7 @@ function initDateRangePicker(selector, options) {
options
=
Object
.
assign
(
defaultOption
,
options
);
options
=
Object
.
assign
(
defaultOption
,
options
);
return
$
(
selector
).
daterangepicker
(
options
);
return
$
(
selector
).
daterangepicker
(
options
);
}
}
function
reloadPage
()
{
window
.
location
.
reload
();
}
apps/templates/_nav.html
View file @
f9e41d71
...
@@ -127,7 +127,7 @@
...
@@ -127,7 +127,7 @@
<i
class=
"fa fa-check-square-o"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Tickets' %}
</span><span
class=
"fa arrow"
></span>
<i
class=
"fa fa-check-square-o"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Tickets' %}
</span><span
class=
"fa arrow"
></span>
</a>
</a>
<ul
class=
"nav nav-second-level"
>
<ul
class=
"nav nav-second-level"
>
<li
id=
"login-confirm-
order
s"
><a
href=
"{% url 'tickets:login-confirm-ticket-list' %}"
>
{% trans 'Login confirm' %}
</a></li>
<li
id=
"login-confirm-
ticket
s"
><a
href=
"{% url 'tickets:login-confirm-ticket-list' %}"
>
{% trans 'Login confirm' %}
</a></li>
</ul>
</ul>
</li>
</li>
{% endif %}
{% endif %}
...
...
apps/tickets/api/base.py
View file @
f9e41d71
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
rest_framework
import
viewsets
,
generics
from
..
import
serializers
,
models
from
rest_framework
import
viewsets
from
django.shortcuts
import
get_object_or_404
from
common.utils
import
lazyproperty
from
..
import
serializers
,
models
,
mixins
class
TicketViewSet
(
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
TicketSerializer
def
get_queryset
(
self
):
class
TicketViewSet
(
mixins
.
TicketMixin
,
viewsets
.
ModelViewSet
):
queryset
=
models
.
Ticket
.
objects
.
all
()
.
none
()
serializer_class
=
serializers
.
TicketSerializer
return
queryset
queryset
=
models
.
Ticket
.
objects
.
all
()
class
CommentViewSet
(
viewsets
.
ModelViewSet
):
class
Ticket
CommentViewSet
(
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
CommentSerializer
serializer_class
=
serializers
.
CommentSerializer
def
check_permissions
(
self
,
request
):
ticket
=
self
.
ticket
if
request
.
user
==
ticket
.
user
or
request
.
user
in
ticket
.
assignees
.
all
():
return
True
return
False
def
get_serializer_context
(
self
):
context
=
super
()
.
get_serializer_context
()
context
[
'ticket'
]
=
self
.
ticket
return
context
@lazyproperty
def
ticket
(
self
):
ticket_id
=
self
.
kwargs
.
get
(
'ticket_id'
)
ticket
=
get_object_or_404
(
models
.
Ticket
,
pk
=
ticket_id
)
return
ticket
def
get_queryset
(
self
):
def
get_queryset
(
self
):
queryset
=
models
.
Comment
.
objects
.
none
()
queryset
=
self
.
ticket
.
comments
.
all
()
return
queryset
return
queryset
apps/tickets/api/login_confirm.py
View file @
f9e41d71
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
rest_framework
import
viewsets
,
generics
from
rest_framework
import
viewsets
,
generics
from
rest_framework.serializers
import
ValidationError
from
django.shortcuts
import
get_object_or_404
from
django.shortcuts
import
get_object_or_404
from
common.permissions
import
IsValidUser
from
common.permissions
import
IsValidUser
from
common.mixins
import
CommonApiMixin
from
common.mixins
import
CommonApiMixin
from
..
import
serializers
from
..
import
serializers
,
mixins
from
..models
import
LoginConfirmTicket
from
..models
import
LoginConfirmTicket
class
LoginConfirmTicketViewSet
(
CommonApiMixin
,
viewsets
.
ModelViewSet
):
class
LoginConfirmTicketViewSet
(
CommonApiMixin
,
mixins
.
TicketMixin
,
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
LoginConfirmTicketSerializer
serializer_class
=
serializers
.
LoginConfirmTicketSerializer
permission_classes
=
(
IsValidUser
,)
permission_classes
=
(
IsValidUser
,)
filter_fields
=
[
'status'
,
'title'
]
queryset
=
LoginConfirmTicket
.
objects
.
all
()
filter_fields
=
[
'status'
,
'title'
,
'action'
,
'ip'
]
search_fields
=
[
'user_display'
,
'title'
,
'ip'
,
'city'
]
search_fields
=
[
'user_display'
,
'title'
,
'ip'
,
'city'
]
def
get_queryset
(
self
):
# def check_update_permission(self, serializer):
queryset
=
LoginConfirmTicket
.
objects
.
all
()
\
# data = serializer.validated_data
.
filter
(
assignees
=
self
.
request
.
user
)
# action = data.get("action")
return
queryset
# user = self.request.user
# instance = serializer.instance
# if action and user not in instance.assignees.all():
class
LoginConfirmTicketsCreateActionApi
(
generics
.
CreateAPIView
):
# error = {"action": "Only assignees can update"}
permission_classes
=
(
IsValidUser
,)
# raise ValidationError(error)
serializer_class
=
serializers
.
LoginConfirmTicketActionSerializer
#
# def perform_update(self, serializer):
def
get_ticket
(
self
):
# self.check_update_permission(serializer)
ticket_id
=
self
.
kwargs
.
get
(
'pk'
)
queryset
=
LoginConfirmTicket
.
objects
.
all
()
\
.
filter
(
assignees
=
self
.
request
.
user
)
ticket
=
get_object_or_404
(
queryset
,
id
=
ticket_id
)
return
ticket
def
get_serializer_context
(
self
):
context
=
super
()
.
get_serializer_context
()
ticket
=
self
.
get_ticket
()
context
[
'ticket'
]
=
ticket
return
context
apps/tickets/apps.py
View file @
f9e41d71
...
@@ -3,3 +3,7 @@ from django.apps import AppConfig
...
@@ -3,3 +3,7 @@ from django.apps import AppConfig
class
TicketsConfig
(
AppConfig
):
class
TicketsConfig
(
AppConfig
):
name
=
'tickets'
name
=
'tickets'
def
ready
(
self
):
from
.
import
signals_handler
return
super
()
.
ready
()
apps/tickets/mixins.py
0 → 100644
View file @
f9e41d71
# -*- coding: utf-8 -*-
#
from
django.db.models
import
Q
class
TicketMixin
:
def
get_queryset
(
self
):
queryset
=
super
()
.
get_queryset
()
assign
=
self
.
request
.
GET
.
get
(
'assign'
,
None
)
if
assign
is
None
:
queryset
=
queryset
.
filter
(
Q
(
assignees
=
self
.
request
.
user
)
|
Q
(
user
=
self
.
request
.
user
)
)
.
distinct
()
elif
assign
in
[
'1'
]:
queryset
=
queryset
.
filter
(
assignees
=
self
.
request
.
user
)
else
:
queryset
=
queryset
.
filter
(
user
=
self
.
request
.
user
)
return
queryset
apps/tickets/models/base.py
View file @
f9e41d71
...
@@ -31,16 +31,12 @@ class Ticket(CommonModelMixin):
...
@@ -31,16 +31,12 @@ class Ticket(CommonModelMixin):
assignee_display
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
"Assignee display name"
))
assignee_display
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
"Assignee display name"
))
assignees
=
models
.
ManyToManyField
(
'users.User'
,
related_name
=
'
%(class)
s_assigned'
,
verbose_name
=
_
(
"Assignees"
))
assignees
=
models
.
ManyToManyField
(
'users.User'
,
related_name
=
'
%(class)
s_assigned'
,
verbose_name
=
_
(
"Assignees"
))
assignees_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"Assignees display name"
),
blank
=
True
)
assignees_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"Assignees display name"
),
blank
=
True
)
type
=
models
.
CharField
(
max_length
=
16
,
default
=
'general'
,
verbose_name
=
_
(
"Type"
))
type
=
models
.
CharField
(
max_length
=
16
,
choices
=
TYPE_CHOICES
,
default
=
TYPE_GENERAL
,
verbose_name
=
_
(
"Type"
))
status
=
models
.
CharField
(
choices
=
STATUS_CHOICES
,
max_length
=
16
,
default
=
'open'
)
status
=
models
.
CharField
(
choices
=
STATUS_CHOICES
,
max_length
=
16
,
default
=
'open'
)
def
__str__
(
self
):
def
__str__
(
self
):
return
'{}: {}'
.
format
(
self
.
user_display
,
self
.
title
)
return
'{}: {}'
.
format
(
self
.
user_display
,
self
.
title
)
@property
def
comments
(
self
):
return
Comment
.
objects
.
filter
(
order_id
=
self
.
id
)
@property
@property
def
body_as_html
(
self
):
def
body_as_html
(
self
):
return
self
.
body
.
replace
(
'
\n
'
,
'<br/>'
)
return
self
.
body
.
replace
(
'
\n
'
,
'<br/>'
)
...
@@ -49,17 +45,29 @@ class Ticket(CommonModelMixin):
...
@@ -49,17 +45,29 @@ class Ticket(CommonModelMixin):
def
status_display
(
self
):
def
status_display
(
self
):
return
self
.
get_status_display
()
return
self
.
get_status_display
()
def
create_status_comment
(
self
,
status
,
user
):
if
status
==
self
.
STATUS_CLOSED
:
action
=
_
(
"Close"
)
else
:
action
=
_
(
"Open"
)
body
=
_
(
'{} {} this ticket'
)
.
format
(
self
.
user
,
action
)
self
.
comments
.
create
(
user
=
user
,
body
=
body
)
def
perform_status
(
self
,
status
,
user
):
if
self
.
status
==
status
:
return
self
.
status
=
status
self
.
save
()
class
Meta
:
class
Meta
:
ordering
=
(
'-date_created'
,)
ordering
=
(
'-date_created'
,)
class
Comment
(
CommonModelMixin
):
class
Comment
(
CommonModelMixin
):
ticket
=
models
.
ForeignKey
(
Ticket
,
on_delete
=
models
.
CASCADE
)
ticket
=
models
.
ForeignKey
(
Ticket
,
on_delete
=
models
.
CASCADE
,
related_name
=
'comments'
)
user
=
models
.
ForeignKey
(
'users.User'
,
on_delete
=
models
.
SET_NULL
,
null
=
True
,
verbose_name
=
_
(
"User"
),
related_name
=
'comments'
)
user
=
models
.
ForeignKey
(
'users.User'
,
on_delete
=
models
.
SET_NULL
,
null
=
True
,
verbose_name
=
_
(
"User"
),
related_name
=
'comments'
)
user_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"User display name"
))
user_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"User display name"
))
body
=
models
.
TextField
(
verbose_name
=
_
(
"Body"
))
body
=
models
.
TextField
(
verbose_name
=
_
(
"Body"
))
class
Meta
:
class
Meta
:
ordering
=
(
'date_created'
,
)
ordering
=
(
'date_created'
,
)
apps/tickets/models/login_confirm.py
View file @
f9e41d71
...
@@ -18,3 +18,16 @@ class LoginConfirmTicket(Ticket):
...
@@ -18,3 +18,16 @@ class LoginConfirmTicket(Ticket):
ip
=
models
.
GenericIPAddressField
(
blank
=
True
,
null
=
True
)
ip
=
models
.
GenericIPAddressField
(
blank
=
True
,
null
=
True
)
city
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
default
=
''
)
city
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
default
=
''
)
action
=
models
.
CharField
(
choices
=
ACTION_CHOICES
,
max_length
=
16
,
default
=
''
,
blank
=
True
)
action
=
models
.
CharField
(
choices
=
ACTION_CHOICES
,
max_length
=
16
,
default
=
''
,
blank
=
True
)
def
create_action_comment
(
self
,
action
,
user
):
action_display
=
dict
(
self
.
ACTION_CHOICES
)
.
get
(
action
)
body
=
'{} {} {}'
.
format
(
user
,
action_display
,
_
(
"this order"
))
self
.
comments
.
create
(
body
=
body
,
user
=
user
,
user_display
=
str
(
user
))
def
perform_action
(
self
,
action
,
user
):
self
.
create_action_comment
(
action
,
user
)
self
.
action
=
action
self
.
status
=
self
.
STATUS_CLOSED
self
.
assignee
=
user
self
.
assignees_display
=
str
(
user
)
self
.
save
()
apps/tickets/permissions.py
0 → 100644
View file @
f9e41d71
# -*- coding: utf-8 -*-
#
from
rest_framework.permissions
import
BasePermission
apps/tickets/serializers/base.py
View file @
f9e41d71
...
@@ -21,7 +21,24 @@ class TicketSerializer(serializers.ModelSerializer):
...
@@ -21,7 +21,24 @@ class TicketSerializer(serializers.ModelSerializer):
]
]
class
CurrentTicket
(
object
):
ticket
=
None
def
set_context
(
self
,
serializer_field
):
self
.
ticket
=
serializer_field
.
context
[
'ticket'
]
def
__call__
(
self
):
return
self
.
ticket
class
CommentSerializer
(
serializers
.
ModelSerializer
):
class
CommentSerializer
(
serializers
.
ModelSerializer
):
user
=
serializers
.
HiddenField
(
default
=
serializers
.
CurrentUserDefault
(),
)
ticket
=
serializers
.
HiddenField
(
default
=
CurrentTicket
()
)
class
Meta
:
class
Meta
:
model
=
models
.
Comment
model
=
models
.
Comment
fields
=
[
fields
=
[
...
...
apps/tickets/serializers/login_confirm.py
View file @
f9e41d71
...
@@ -17,13 +17,28 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
...
@@ -17,13 +17,28 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
]
]
read_only_fields
=
TicketSerializer
.
Meta
.
read_only_fields
read_only_fields
=
TicketSerializer
.
Meta
.
read_only_fields
def
create
(
self
,
validated_data
):
validated_data
.
pop
(
'action'
)
return
super
()
.
create
(
validated_data
)
def
update
(
self
,
instance
,
validated_data
):
action
=
validated_data
.
get
(
"action"
)
user
=
self
.
context
[
"request"
]
.
user
if
action
and
user
not
in
instance
.
assignees
.
all
():
error
=
{
"action"
:
"Only assignees can update"
}
raise
serializers
.
ValidationError
(
error
)
instance
=
super
()
.
update
(
instance
,
validated_data
)
if
action
:
instance
.
perform_action
(
action
,
user
)
return
instance
class
LoginConfirmTicketActionSerializer
(
serializers
.
ModelSerializer
):
class
LoginConfirmTicketActionSerializer
(
serializers
.
ModelSerializer
):
comment
=
serializers
.
CharField
(
allow_blank
=
True
)
comment
=
serializers
.
CharField
(
allow_blank
=
True
)
class
Meta
:
class
Meta
:
model
=
LoginConfirmTicket
model
=
LoginConfirmTicket
fields
=
[
'action'
,
'comment'
]
fields
=
[
'action'
]
def
update
(
self
,
instance
,
validated_data
):
def
update
(
self
,
instance
,
validated_data
):
pass
pass
...
...
apps/tickets/signals_handler.py
View file @
f9e41d71
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.db.models.signals
import
m2m_changed
,
post_save
from
django.db.models.signals
import
m2m_changed
,
post_save
,
pre_save
from
common.utils
import
get_logger
from
common.utils
import
get_logger
from
.models
import
LoginConfirmTicket
from
.models
import
LoginConfirmTicket
,
Ticket
,
Comment
from
.utils
import
(
from
.utils
import
(
send_login_confirm_ticket_mail_to_assignees
,
send_login_confirm_ticket_mail_to_assignees
,
send_login_confirm_action_mail_to_user
send_login_confirm_action_mail_to_user
...
@@ -16,16 +16,34 @@ logger = get_logger(__name__)
...
@@ -16,16 +16,34 @@ logger = get_logger(__name__)
@receiver
(
m2m_changed
,
sender
=
LoginConfirmTicket
.
assignees
.
through
)
@receiver
(
m2m_changed
,
sender
=
LoginConfirmTicket
.
assignees
.
through
)
def
on_login_confirm_ticket_assignees_set
(
sender
,
instance
=
None
,
action
=
None
,
def
on_login_confirm_ticket_assignees_set
(
sender
,
instance
=
None
,
action
=
None
,
model
=
None
,
pk_set
=
None
,
**
kwargs
):
reverse
=
False
,
model
=
None
,
pk_set
=
None
,
**
kwargs
):
if
action
==
'post_add'
:
if
action
==
'post_add'
:
logger
.
debug
(
'New ticket create, send mail: {}'
.
format
(
instance
.
id
))
logger
.
debug
(
'New ticket create, send mail: {}'
.
format
(
instance
.
id
))
assignees
=
model
.
objects
.
filter
(
pk__in
=
pk_set
)
assignees
=
model
.
objects
.
filter
(
pk__in
=
pk_set
)
send_login_confirm_ticket_mail_to_assignees
(
instance
,
assignees
)
send_login_confirm_ticket_mail_to_assignees
(
instance
,
assignees
)
if
action
.
startswith
(
'post'
)
and
not
reverse
:
instance
.
assignees_display
=
', '
.
join
([
str
(
u
)
for
u
in
instance
.
assignees
.
all
()
])
instance
.
save
()
@receiver
(
post_save
,
sender
=
LoginConfirmTicket
)
@receiver
(
post_save
,
sender
=
LoginConfirmTicket
)
def
on_login_confirm_ticket_status_change
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
def
on_login_confirm_ticket_status_change
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
if
created
or
instance
.
status
==
"
pending
"
:
if
created
or
instance
.
status
==
"
open
"
:
return
return
logger
.
debug
(
'Ticket changed, send mail: {}'
.
format
(
instance
.
id
))
logger
.
debug
(
'Ticket changed, send mail: {}'
.
format
(
instance
.
id
))
send_login_confirm_action_mail_to_user
(
instance
)
send_login_confirm_action_mail_to_user
(
instance
)
@receiver
(
pre_save
,
sender
=
LoginConfirmTicket
)
def
on_ticket_create
(
sender
,
instance
=
None
,
**
kwargs
):
instance
.
user_display
=
str
(
instance
.
user
)
if
instance
.
assignee
:
instance
.
assignee_display
=
str
(
instance
.
assignee
)
@receiver
(
pre_save
,
sender
=
Comment
)
def
on_comment_create
(
sender
,
instance
=
None
,
**
kwargs
):
instance
.
user_display
=
str
(
instance
.
user
)
apps/tickets/templates/tickets/login_confirm_ticket_detail.html
View file @
f9e41d71
{% extends '
base
.html' %}
{% extends '
tickets/ticket_detail
.html' %}
{% load static %}
{% load static %}
{% load i18n %}
{% load i18n %}
{% block content %}
{% block status %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
{% endblock %}
<div
class=
"row"
>
<div
class=
"col-sm-12"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<h5>
{{ object.title }}
</h5>
<div
class=
"ibox-tools"
>
<a
class=
"collapse-link"
>
<i
class=
"fa fa-chevron-up"
></i>
</a>
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"#"
>
<i
class=
"fa fa-wrench"
></i>
</a>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<div
class=
"row"
>
<div
class=
"col-lg-11"
>
<div
class=
"row"
>
<div
class=
"col-lg-6"
>
<dl
class=
"dl-horizontal"
>
<dt>
{% trans 'User' %}:
</dt>
<dd>
{{ object.user_display }}
</dd>
<dt>
{% trans 'IP' %}:
</dt>
<dd>
{{ object.ip }}
</dd>
<dt>
{% trans 'Assignees' %}:
</dt>
<dd>
{{ object.assignees_display }}
</dd>
<dt>
{% trans 'Status' %}:
</dt>
<dd>
{% if object.status == "accpeted" %}
<span
class=
"label label-primary"
>
{{ object.get_status_display }}
</span>
{% endif %}
{% if object.status == "rejected" %}
<span
class=
"label label-danger"
>
{{ object.get_status_display }}
</span>
{% endif %}
{% if object.status == "pending" %}
<span
class=
"label label-info"
>
{{ object.get_status_display }}
</span>
{% endif %}
</dd>
</dl>
</div>
<div
class=
"col-lg-6"
>
<dl
class=
"dl-horizontal"
>
<dt><br></dt><dd></dd>
<dt>
{% trans 'City' %}:
</dt>
<dd>
{{ object.city }}
</dd>
<dt>
{% trans 'Assignee' %}:
</dt>
<dd>
{{ object.assignee_display | default_if_none:"" }}
</dd>
<dt>
{% trans 'Date created' %}:
</dt>
<dd>
{{ object.date_created }}
</dd>
</dl>
</div>
</div>
<div
class=
"row m-t-sm"
>
<div
class=
"col-lg-12"
>
<div
class=
"panel blank-panel"
>
<div
class=
"panel-body"
>
<div
class=
"feed-activity-list"
>
{% for comment in object.comments %}
<div
class=
"feed-element"
>
<a
href=
"#"
class=
"pull-left"
>
<img
alt=
"image"
class=
"img-circle"
src=
"{% static 'img/avatar/user.png'%}"
>
</a>
<div
class=
"media-body "
>
<strong>
{{ comment.user_display }}
</strong>
<small
class=
"text-muted"
>
{{ comment.date_created|timesince}} {% trans 'ago' %}
</small>
<br/>
<small
class=
"text-muted"
>
{{ comment.date_created }}
</small>
<div
style=
"padding-top: 10px"
>
{{ comment.body }}
</div>
</div>
</div>
{% endfor %}
</div>
<div
class=
"feed-element"
>
<a
href=
""
class=
"pull-left"
>
<img
alt=
"image"
class=
"img-circle"
src=
"{% static 'img/avatar/user.png'%}"
>
</a>
<div
class=
"media-body"
>
<textarea
class=
"form-control"
placeholder=
""
id=
"comment"
></textarea>
</div>
</div>
<div
class=
"text-right"
>
<a
class=
"btn btn-sm btn-primary btn-action btn-update"
data-action=
"accept"
><i
class=
"fa fa-check"
></i>
{% trans 'Accept' %}
</a>
<a
class=
"btn btn-sm btn-danger btn-action btn-update"
data-action=
"reject"
><i
class=
"fa fa-times"
></i>
{% trans 'Reject' %}
</a>
<a
class=
"btn btn-sm btn-info btn-action"
data-action=
"comment"
><i
class=
"fa fa-pencil"
></i>
{% trans 'Comment' %}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"col-lg-1"
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% block action %}
<a
class=
"btn btn-sm btn-primary btn-update btn-action"
data-action=
"approve"
><i
class=
"fa fa-check"
></i>
{% trans 'Approve' %}
</a>
<a
class=
"btn btn-sm btn-danger btn-update btn-action"
data-action=
"reject"
><i
class=
"fa fa-times"
></i>
{% trans 'Reject' %}
</a>
{% endblock %}
{% endblock %}
{% block custom_foot_js %}
{% block custom_foot_js %}
{{ block.super }}
<script>
<script>
var
ticketId
=
"{{ object.id }}"
;
var
ticketDetailUrl
=
"{% url 'api-tickets:login-confirm-ticket-detail' pk=object.id %}"
;
var
status
=
"{{ object.status }}"
;
$
(
document
).
ready
(
function
()
{
var
actionCreateUrl
=
"{% url 'api-tickets:login-confirm-ticket-create-action' pk=object.id %}"
;
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
$
(
document
).
ready
(
function
()
{
createComment
(
function
()
{
if
(
status
!==
"pending"
)
{
});
$
(
'.btn-update'
).
attr
(
'disabled'
,
'1'
)
var
action
=
$
(
this
).
data
(
'action'
);
}
var
data
=
{
})
url
:
ticketDetailUrl
,
.
on
(
'click'
,
'.btn-action'
,
function
()
{
body
:
JSON
.
stringify
({
action
:
action
}),
var
action
=
$
(
this
).
data
(
'action'
);
method
:
"PATCH"
,
var
comment
=
$
(
"#comment"
).
val
();
success
:
reloadPage
var
data
=
{
};
url
:
actionCreateUrl
,
requestApi
(
data
);
method
:
'POST'
,
})
body
:
JSON
.
stringify
({
action
:
action
,
comment
:
comment
}),
success
:
function
()
{
window
.
location
.
reload
();
}
};
requestApi
(
data
);
})
</script>
</script>
{% endblock %}
{% endblock %}
apps/tickets/templates/tickets/login_confirm_ticket_list.html
View file @
f9e41d71
...
@@ -13,7 +13,6 @@
...
@@ -13,7 +13,6 @@
</th>
</th>
<th
class=
"text-center"
>
{% trans 'Title' %}
</th>
<th
class=
"text-center"
>
{% trans 'Title' %}
</th>
<th
class=
"text-center"
>
{% trans 'User' %}
</th>
<th
class=
"text-center"
>
{% trans 'User' %}
</th>
<th
class=
"text-center"
>
{% trans 'IP' %}
</th>
<th
class=
"text-center"
>
{% trans 'Status' %}
</th>
<th
class=
"text-center"
>
{% trans 'Status' %}
</th>
<th
class=
"text-center"
>
{% trans 'Datetime' %}
</th>
<th
class=
"text-center"
>
{% trans 'Datetime' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
...
@@ -39,10 +38,6 @@ function initTable() {
...
@@ -39,10 +38,6 @@ function initTable() {
$
(
td
).
html
(
detailBtn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
$
(
td
).
html
(
detailBtn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
}},
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
d
=
cellData
+
"("
+
rowData
.
city
+
")"
;
$
(
td
).
html
(
d
)
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
if
(
cellData
===
"approval"
)
{
if
(
cellData
===
"approval"
)
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
else
if
(
cellData
===
"rejected"
)
{
}
else
if
(
cellData
===
"rejected"
)
{
...
@@ -53,12 +48,12 @@ function initTable() {
...
@@ -53,12 +48,12 @@ function initTable() {
$
(
td
).
html
(
'<i class="fa fa-circle text-info"></i>'
)
$
(
td
).
html
(
'<i class="fa fa-circle text-info"></i>'
)
}
}
}},
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
d
=
toSafeLocalDateStr
(
cellData
);
var
d
=
toSafeLocalDateStr
(
cellData
);
$
(
td
).
html
(
d
)
$
(
td
).
html
(
d
)
}},
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
acceptBtn
=
'<a class="btn btn-xs btn-info btn-action" data-action="a
ccept" data-uid="{{ DEFAULT_PK }}" >{% trans "Accept
" %}</a> '
;
var
acceptBtn
=
'<a class="btn btn-xs btn-info btn-action" data-action="a
pprove" data-uid="{{ DEFAULT_PK }}" >{% trans "Approve
" %}</a> '
;
var
rejectBtn
=
'<a class="btn btn-xs btn-danger btn-action" data-action="reject" data-uid="{{ DEFAULT_PK }}" >{% trans "Reject" %}</a>'
;
var
rejectBtn
=
'<a class="btn btn-xs btn-danger btn-action" data-action="reject" data-uid="{{ DEFAULT_PK }}" >{% trans "Reject" %}</a>'
;
acceptBtn
=
acceptBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
acceptBtn
=
acceptBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
rejectBtn
=
rejectBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
rejectBtn
=
rejectBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
...
@@ -74,7 +69,7 @@ function initTable() {
...
@@ -74,7 +69,7 @@ function initTable() {
ajax_url
:
'{% url "api-tickets:login-confirm-ticket-list" %}'
,
ajax_url
:
'{% url "api-tickets:login-confirm-ticket-list" %}'
,
columns
:
[
columns
:
[
{
data
:
"id"
},
{
data
:
"title"
},
{
data
:
"id"
},
{
data
:
"title"
},
{
data
:
"user_display"
},
{
data
:
"ip"
},
{
data
:
"user_display"
},
{
data
:
"status"
,
ticketable
:
false
},
{
data
:
"status"
,
ticketable
:
false
},
{
data
:
"date_created"
,
width
:
"120px"
},
{
data
:
"date_created"
,
width
:
"120px"
},
{
data
:
"id"
,
ticketable
:
false
}
{
data
:
"id"
,
ticketable
:
false
}
...
@@ -101,18 +96,15 @@ $(document).ready(function(){
...
@@ -101,18 +96,15 @@ $(document).ready(function(){
];
];
initTableFilterDropdown
(
'#login_confirm_ticket_list_table_filter input'
,
menu
)
initTableFilterDropdown
(
'#login_confirm_ticket_list_table_filter input'
,
menu
)
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
var
actionCreateUrl
=
"{% url 'api-tickets:login-confirm-ticket-create-action' pk=DEFAULT_PK %}"
;
var
ticketId
=
$
(
this
).
data
(
"uid"
);
var
ticketId
=
$
(
this
).
data
(
'uid'
);
actionCreateUrl
=
actionCreateUrl
.
replace
(
"{{ DEFAULT_PK }}"
,
ticketId
);
var
action
=
$
(
this
).
data
(
'action'
);
var
action
=
$
(
this
).
data
(
'action'
);
var
comment
=
''
;
var
ticketDetailUrl
=
"{% url 'api-tickets:login-confirm-ticket-detail' pk=DEFAULT_PK %}"
;
ticketDetailUrl
=
ticketDetailUrl
.
replace
(
"{{ DEFAULT_PK }}"
,
ticketId
);
var
data
=
{
var
data
=
{
url
:
actionCreateUrl
,
url
:
ticketDetailUrl
,
method
:
'POST'
,
body
:
JSON
.
stringify
({
action
:
action
}),
body
:
JSON
.
stringify
({
action
:
action
,
comment
:
comment
}),
method
:
"PATCH"
,
success
:
function
()
{
success
:
reloadPage
window
.
location
.
reload
();
}
};
};
requestApi
(
data
);
requestApi
(
data
);
})
})
...
...
apps/tickets/templates/tickets/ticket_detail.html
0 → 100644
View file @
f9e41d71
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
<div
class=
"col-sm-12"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<h5>
{{ object.title }}
</h5>
<div
class=
"ibox-tools"
>
<a
class=
"collapse-link"
>
<i
class=
"fa fa-chevron-up"
></i>
</a>
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"#"
>
<i
class=
"fa fa-wrench"
></i>
</a>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<div
class=
"row"
>
<div
class=
"col-lg-11"
>
<div
class=
"row"
>
<div
class=
"col-lg-6"
>
<dl
class=
"dl-horizontal"
>
<dt>
{% trans 'User' %}:
</dt>
<dd>
{{ object.user_display }}
</dd>
<dt>
{% trans 'Type' %}:
</dt>
<dd>
{{ object.get_type_display | default_if_none:"" }}
</dd>
<dt>
{% trans 'Status' %}:
</dt>
<dd>
{% if object.status == "open" %}
<span
class=
"label label-primary"
>
{{ object.get_status_display }}
</span>
{% elif object.status == "closed" %}
<span
class=
"label label-danger"
>
{{ object.get_status_display }}
</span>
{% endif %}
</dd>
</dl>
</div>
<div
class=
"col-lg-6"
>
<dl
class=
"dl-horizontal"
>
<dt>
{% trans 'Assignees' %}:
</dt>
<dd>
{{ object.assignees_display }}
</dd>
<dt>
{% trans 'Assignee' %}:
</dt>
<dd>
{{ object.assignee_display | default_if_none:"" }}
</dd>
<dt>
{% trans 'Date created' %}:
</dt>
<dd>
{{ object.date_created }}
</dd>
</dl>
</div>
</div>
<div
class=
"row m-t-sm"
>
<div
class=
"col-lg-12"
>
<div
class=
"panel blank-panel"
>
<div
class=
"panel-body"
>
<div
class=
"feed-activity-list"
>
<div
class=
"feed-element"
>
<a
href=
"#"
class=
"pull-left"
>
<img
alt=
"image"
class=
"img-circle"
src=
"{% static 'img/avatar/user.png'%}"
>
</a>
<div
class=
"media-body "
>
<strong>
{{ object.user_display }}
</strong>
<small
class=
"text-muted"
>
{{ object.date_created|timesince}} {% trans 'ago' %}
</small>
<br/>
<small
class=
"text-muted"
>
{{ object.date_created }}
</small>
<div
style=
"padding-top: 10px"
>
{{ object.body_as_html | safe }}
</div>
</div>
</div>
{% for comment in object.comments.all %}
<div
class=
"feed-element"
>
<a
href=
"#"
class=
"pull-left"
>
<img
alt=
"image"
class=
"img-circle"
src=
"{% static 'img/avatar/user.png'%}"
>
</a>
<div
class=
"media-body "
>
<strong>
{{ comment.user_display }}
</strong>
<small
class=
"text-muted"
>
{{ comment.date_created|timesince}} {% trans 'ago' %}
</small>
<br/>
<small
class=
"text-muted"
>
{{ comment.date_created }}
</small>
<div
style=
"padding-top: 10px"
>
{{ comment.body }}
</div>
</div>
</div>
{% endfor %}
</div>
<div
class=
"feed-element"
>
<a
href=
""
class=
"pull-left"
>
<img
alt=
"image"
class=
"img-circle"
src=
"{% static 'img/avatar/user.png'%}"
>
</a>
<div
class=
"media-body"
>
<textarea
class=
"form-control"
placeholder=
""
id=
"comment"
></textarea>
</div>
</div>
<div
class=
"text-right"
>
{% block action %}
{% endblock %}
{% block status %}
<a
class=
"btn btn-sm btn-danger btn-update btn-status"
data-uid=
"close"
><i
class=
"fa fa-times"
></i>
{% trans 'Close' %}
</a>
{% endblock %}
{% block comment %}
<a
class=
"btn btn-sm btn-info btn-update btn-comment"
data-uid=
"comment"
><i
class=
"fa fa-pencil"
></i>
{% trans 'Comment' %}
</a>
{% endblock %}
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"col-lg-1"
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var
ticketId
=
"{{ object.id }}"
;
var
status
=
"{{ object.status }}"
;
var
commentUrl
=
"{% url 'api-tickets:ticket-comment-list' ticket_id=object.id %}"
;
function
createComment
(
successCallback
)
{
var
commentText
=
$
(
"#comment"
).
val
();
if
(
!
commentText
)
{
return
}
var
body
=
{
body
:
commentText
,
ticket
:
ticketId
,
};
var
success
=
function
()
{
window
.
location
.
reload
();
};
if
(
successCallback
){
success
=
successCallback
;
}
requestApi
({
url
:
commentUrl
,
data
:
JSON
.
stringify
(
body
),
method
:
"POST"
,
success
:
success
})
}
$
(
document
).
ready
(
function
()
{
if
(
status
!==
"open"
)
{
$
(
'.btn-update'
).
attr
(
'disabled'
,
'1'
)
}
})
.
on
(
'click'
,
'.btn-comment'
,
function
()
{
createComment
();
})
</script>
{% endblock %}
apps/tickets/urls/api_urls.py
View file @
f9e41d71
...
@@ -9,15 +9,11 @@ app_name = 'tickets'
...
@@ -9,15 +9,11 @@ app_name = 'tickets'
router
=
DefaultRouter
()
router
=
DefaultRouter
()
router
.
register
(
'tickets'
,
api
.
TicketViewSet
,
'ticket'
)
router
.
register
(
'tickets'
,
api
.
TicketViewSet
,
'ticket'
)
router
.
register
(
'tickets/(?P<ticket_id>[0-9a-zA-Z
\
-]{36})/comments'
,
api
.
TicketCommentViewSet
,
'ticket-comment'
)
router
.
register
(
'login-confirm-tickets'
,
api
.
LoginConfirmTicketViewSet
,
'login-confirm-ticket'
)
router
.
register
(
'login-confirm-tickets'
,
api
.
LoginConfirmTicketViewSet
,
'login-confirm-ticket'
)
router
.
register
(
'tickets/<uuid:ticket_id>/comments/'
,
api
.
CommentViewSet
,
'ticket-comment'
)
urlpatterns
=
[
urlpatterns
=
[
path
(
'login-confirm-tickets/<uuid:pk>/actions/'
,
api
.
LoginConfirmTicketsCreateActionApi
.
as_view
(),
name
=
'login-confirm-ticket-create-action'
),
]
]
urlpatterns
+=
router
.
urls
urlpatterns
+=
router
.
urls
apps/tickets/views.py
View file @
f9e41d71
from
django.views.generic
import
TemplateView
,
DetailView
from
django.views.generic
import
TemplateView
,
DetailView
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
common.permissions
import
PermissionsMixin
,
Is
OrgAdmin
from
common.permissions
import
PermissionsMixin
,
Is
ValidUser
from
.models
import
LoginConfirmTicket
from
.models
import
LoginConfirmTicket
from
.
import
mixins
class
LoginConfirmTicketListView
(
PermissionsMixin
,
TemplateView
):
class
LoginConfirmTicketListView
(
PermissionsMixin
,
TemplateView
):
template_name
=
'tickets/login_confirm_ticket_list.html'
template_name
=
'tickets/login_confirm_ticket_list.html'
permission_classes
=
(
Is
OrgAdmin
,)
permission_classes
=
(
Is
ValidUser
,)
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
()
.
get_context_data
(
**
kwargs
)
context
=
super
()
.
get_context_data
(
**
kwargs
)
...
@@ -18,12 +19,10 @@ class LoginConfirmTicketListView(PermissionsMixin, TemplateView):
...
@@ -18,12 +19,10 @@ class LoginConfirmTicketListView(PermissionsMixin, TemplateView):
return
context
return
context
class
LoginConfirmTicketDetailView
(
PermissionsMixin
,
DetailView
):
class
LoginConfirmTicketDetailView
(
PermissionsMixin
,
mixins
.
TicketMixin
,
DetailView
):
template_name
=
'tickets/login_confirm_ticket_detail.html'
template_name
=
'tickets/login_confirm_ticket_detail.html'
permission_classes
=
(
IsOrgAdmin
,)
queryset
=
LoginConfirmTicket
.
objects
.
all
()
permission_classes
=
(
IsValidUser
,)
def
get_queryset
(
self
):
return
LoginConfirmTicket
.
objects
.
filter
(
assignees
=
self
.
request
.
user
)
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
()
.
get_context_data
(
**
kwargs
)
context
=
super
()
.
get_context_data
(
**
kwargs
)
...
...
apps/users/serializers/user.py
View file @
f9e41d71
...
@@ -13,11 +13,11 @@ from ..models import User, UserGroup
...
@@ -13,11 +13,11 @@ from ..models import User, UserGroup
__all__
=
[
__all__
=
[
'UserSerializer'
,
'UserPKUpdateSerializer'
,
'UserUpdateGroupSerializer'
,
'UserSerializer'
,
'UserPKUpdateSerializer'
,
'UserUpdateGroupSerializer'
,
'ChangeUserPasswordSerializer'
,
'ResetOTPSerializer'
,
'ChangeUserPasswordSerializer'
,
'ResetOTPSerializer'
,
'UserProfileSerializer'
,
]
]
class
UserSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
class
UserSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
can_update
=
serializers
.
SerializerMethodField
()
can_update
=
serializers
.
SerializerMethodField
()
can_delete
=
serializers
.
SerializerMethodField
()
can_delete
=
serializers
.
SerializerMethodField
()
...
@@ -135,3 +135,11 @@ class ResetOTPSerializer(serializers.Serializer):
...
@@ -135,3 +135,11 @@ class ResetOTPSerializer(serializers.Serializer):
def
update
(
self
,
instance
,
validated_data
):
def
update
(
self
,
instance
,
validated_data
):
pass
pass
class
UserProfileSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
User
fields
=
[
'id'
,
'username'
,
'name'
,
'role'
,
'email'
]
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