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
ebe129b3
Commit
ebe129b3
authored
Nov 15, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Update] 修改工单
parent
18f88647
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
258 additions
and
403 deletions
+258
-403
login_confirm.py
apps/authentication/api/login_confirm.py
+4
-4
mixins.py
apps/authentication/mixins.py
+2
-2
models.py
apps/authentication/models.py
+11
-9
login_wait_confirm.html
...tication/templates/authentication/login_wait_confirm.html
+2
-2
api_urls.py
apps/authentication/urls/api_urls.py
+1
-1
login.py
apps/authentication/views/login.py
+3
-3
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
_nav.html
apps/templates/_nav.html
+3
-5
__init__.py
apps/tickets/api/__init__.py
+1
-2
login_confirm.py
apps/tickets/api/login_confirm.py
+0
-16
ticket.py
apps/tickets/api/ticket.py
+7
-1
0001_initial.py
apps/tickets/migrations/0001_initial.py
+6
-16
0002_auto_20191114_1105.py
apps/tickets/migrations/0002_auto_20191114_1105.py
+0
-24
__init__.py
apps/tickets/models/__init__.py
+1
-2
login_confirm.py
apps/tickets/models/login_confirm.py
+0
-33
ticket.py
apps/tickets/models/ticket.py
+30
-0
__init__.py
apps/tickets/serializers/__init__.py
+1
-2
login_confirm.py
apps/tickets/serializers/login_confirm.py
+0
-53
ticket.py
apps/tickets/serializers/ticket.py
+19
-0
signals_handler.py
apps/tickets/signals_handler.py
+8
-8
login_confirm_ticket_detail.html
...ickets/templates/tickets/login_confirm_ticket_detail.html
+0
-34
login_confirm_ticket_list.html
.../tickets/templates/tickets/login_confirm_ticket_list.html
+0
-154
ticket_detail.html
apps/tickets/templates/tickets/ticket_detail.html
+27
-8
ticket_list.html
apps/tickets/templates/tickets/ticket_list.html
+115
-0
api_urls.py
apps/tickets/urls/api_urls.py
+0
-1
views_urls.py
apps/tickets/urls/views_urls.py
+2
-2
utils.py
apps/tickets/utils.py
+5
-13
views.py
apps/tickets/views.py
+10
-8
No files found.
apps/authentication/api/login_confirm.py
View file @
ebe129b3
...
...
@@ -12,7 +12,7 @@ from ..models import LoginConfirmSetting
from
..serializers
import
LoginConfirmSettingSerializer
from
..
import
errors
,
mixins
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'
LoginConfirm
TicketStatusApi'
]
__all__
=
[
'LoginConfirmSettingUpdateApi'
,
'TicketStatusApi'
]
logger
=
get_logger
(
__name__
)
...
...
@@ -31,17 +31,17 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
return
s
class
LoginConfirm
TicketStatusApi
(
mixins
.
AuthMixin
,
APIView
):
class
TicketStatusApi
(
mixins
.
AuthMixin
,
APIView
):
permission_classes
=
()
def
get_ticket
(
self
):
from
tickets.models
import
LoginConfirm
Ticket
from
tickets.models
import
Ticket
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
logger
.
debug
(
'Login confirm ticket id: {}'
.
format
(
ticket_id
))
if
not
ticket_id
:
ticket
=
None
else
:
ticket
=
get_object_or_none
(
LoginConfirm
Ticket
,
pk
=
ticket_id
)
ticket
=
get_object_or_none
(
Ticket
,
pk
=
ticket_id
)
return
ticket
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
apps/authentication/mixins.py
View file @
ebe129b3
...
...
@@ -104,13 +104,13 @@ class AuthMixin:
raise
errors
.
MFAFailedError
(
username
=
user
.
username
,
request
=
self
.
request
)
def
get_ticket
(
self
):
from
tickets.models
import
LoginConfirm
Ticket
from
tickets.models
import
Ticket
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
logger
.
debug
(
'Login confirm ticket id: {}'
.
format
(
ticket_id
))
if
not
ticket_id
:
ticket
=
None
else
:
ticket
=
get_object_or_none
(
LoginConfirm
Ticket
,
pk
=
ticket_id
)
ticket
=
get_object_or_none
(
Ticket
,
pk
=
ticket_id
)
return
ticket
def
get_ticket_or_create
(
self
,
confirm_setting
):
...
...
apps/authentication/models.py
View file @
ebe129b3
...
...
@@ -49,23 +49,25 @@ class LoginConfirmSetting(CommonModelMixin):
return
get_object_or_none
(
cls
,
user
=
user
)
def
create_confirm_ticket
(
self
,
request
=
None
):
from
tickets.models
import
LoginConfirm
Ticket
title
=
_
(
'User login confirm: {}'
)
.
format
(
self
.
user
)
from
tickets.models
import
Ticket
title
=
'['
+
__
(
'Login confirm'
)
+
']: {}'
.
format
(
self
.
user
)
if
request
:
remote_addr
=
get_request_ip
(
request
)
city
=
get_ip_city
(
remote_addr
)
body
=
_
(
"User: {}
\n
IP: {}
\n
City: {}
\n
Date: {}
\n
"
)
.
format
(
self
.
user
,
remote_addr
,
city
,
timezone
.
now
()
body
=
__
(
"{user_key}: {username}<br>"
"IP: {ip}<br>"
"{city_key}: {city}<br>"
"{date_key}: {date}<br>"
)
.
format
(
user_key
=
__
(
"User"
),
username
=
self
.
user
,
ip
=
remote_addr
,
city_key
=
_
(
"City"
),
city
=
city
,
date_key
=
__
(
"Datetime"
),
date
=
timezone
.
now
()
)
else
:
city
=
'Localhost'
remote_addr
=
'127.0.0.1'
body
=
''
reviewer
=
self
.
reviewers
.
all
()
ticket
=
LoginConfirm
Ticket
.
objects
.
create
(
ticket
=
Ticket
.
objects
.
create
(
user
=
self
.
user
,
title
=
title
,
body
=
body
,
city
=
city
,
ip
=
remote_addr
,
type
=
LoginConfirmTicket
.
TYPE_LOGIN_CONFIRM
,
type
=
Ticket
.
TYPE_LOGIN_CONFIRM
,
)
ticket
.
assignees
.
set
(
reviewer
)
return
ticket
...
...
apps/authentication/templates/authentication/login_wait_confirm.html
View file @
ebe129b3
...
...
@@ -126,7 +126,7 @@ function handleProgressBar() {
progressBarRef
.
attr
(
'aria-valuenow'
,
offset
);
}
function
cancel
LoginConfirm
Ticket
()
{
function
cancelTicket
()
{
requestApi
({
url
:
url
,
method
:
"DELETE"
,
...
...
@@ -144,7 +144,7 @@ function setCloseConfirm() {
return
'Confirm'
;
};
window
.
onunload
=
function
(
e
)
{
cancel
LoginConfirm
Ticket
();
cancelTicket
();
}
}
...
...
apps/authentication/urls/api_urls.py
View file @
ebe129b3
...
...
@@ -18,7 +18,7 @@ urlpatterns = [
path
(
'connection-token/'
,
api
.
UserConnectionTokenApi
.
as_view
(),
name
=
'connection-token'
),
path
(
'otp/verify/'
,
api
.
UserOtpVerifyApi
.
as_view
(),
name
=
'user-otp-verify'
),
path
(
'login-confirm-ticket/status/'
,
api
.
LoginConfirm
TicketStatusApi
.
as_view
(),
name
=
'login-confirm-ticket-status'
),
path
(
'login-confirm-ticket/status/'
,
api
.
TicketStatusApi
.
as_view
(),
name
=
'login-confirm-ticket-status'
),
path
(
'login-confirm-settings/<uuid:user_id>/'
,
api
.
LoginConfirmSettingUpdateApi
.
as_view
(),
name
=
'login-confirm-setting-update'
)
]
...
...
apps/authentication/views/login.py
View file @
ebe129b3
...
...
@@ -144,16 +144,16 @@ class UserLoginWaitConfirmView(TemplateView):
template_name
=
'authentication/login_wait_confirm.html'
def
get_context_data
(
self
,
**
kwargs
):
from
tickets.models
import
LoginConfirm
Ticket
from
tickets.models
import
Ticket
ticket_id
=
self
.
request
.
session
.
get
(
"auth_ticket_id"
)
if
not
ticket_id
:
ticket
=
None
else
:
ticket
=
get_object_or_none
(
LoginConfirm
Ticket
,
pk
=
ticket_id
)
ticket
=
get_object_or_none
(
Ticket
,
pk
=
ticket_id
)
context
=
super
()
.
get_context_data
(
**
kwargs
)
if
ticket
:
timestamp_created
=
datetime
.
datetime
.
timestamp
(
ticket
.
date_created
)
ticket_detail_url
=
reverse
(
'tickets:
login-confirm-
ticket-detail'
,
kwargs
=
{
'pk'
:
ticket_id
})
ticket_detail_url
=
reverse
(
'tickets:ticket-detail'
,
kwargs
=
{
'pk'
:
ticket_id
})
msg
=
_
(
"""Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>
Don't close this page"""
)
.
format
(
ticket
.
assignees_display
)
else
:
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
ebe129b3
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
ebe129b3
This diff is collapsed.
Click to expand it.
apps/templates/_nav.html
View file @
ebe129b3
...
...
@@ -123,12 +123,10 @@
{% if request.user.can_admin_current_org and LOGIN_CONFIRM_ENABLE %}
<li
id=
"tickets"
>
<a>
<i
class=
"fa fa-check-square-o"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Tickets' %}
</span><span
class=
"fa arrow"
></span>
<a
href=
"{% url 'tickets:ticket-list' %}"
>
<i
class=
"fa fa-check-square-o"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Tickets' %}
</span>
</a>
<ul
class=
"nav nav-second-level"
>
<li
id=
"login-confirm-tickets"
><a
href=
"{% url 'tickets:login-confirm-ticket-list' %}"
>
{% trans 'Login confirm' %}
</a></li>
</ul>
</li>
{% endif %}
...
...
apps/tickets/api/__init__.py
View file @
ebe129b3
# -*- coding: utf-8 -*-
#
from
.base
import
*
from
.login_confirm
import
*
from
.ticket
import
*
apps/tickets/api/login_confirm.py
deleted
100644 → 0
View file @
18f88647
# -*- coding: utf-8 -*-
#
from
rest_framework_bulk
import
BulkModelViewSet
from
common.permissions
import
IsValidUser
from
common.mixins
import
CommonApiMixin
from
..
import
serializers
,
mixins
from
..models
import
LoginConfirmTicket
class
LoginConfirmTicketViewSet
(
CommonApiMixin
,
mixins
.
TicketMixin
,
BulkModelViewSet
):
serializer_class
=
serializers
.
LoginConfirmTicketSerializer
permission_classes
=
(
IsValidUser
,)
queryset
=
LoginConfirmTicket
.
objects
.
all
()
filter_fields
=
[
'status'
,
'title'
,
'action'
,
'ip'
]
search_fields
=
[
'user_display'
,
'title'
,
'ip'
,
'city'
]
apps/tickets/api/
base
.py
→
apps/tickets/api/
ticket
.py
View file @
ebe129b3
...
...
@@ -4,6 +4,7 @@
from
rest_framework
import
viewsets
from
django.shortcuts
import
get_object_or_404
from
common.permissions
import
IsValidUser
from
common.utils
import
lazyproperty
from
..
import
serializers
,
models
,
mixins
...
...
@@ -11,14 +12,19 @@ from .. import serializers, models, mixins
class
TicketViewSet
(
mixins
.
TicketMixin
,
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
TicketSerializer
queryset
=
models
.
Ticket
.
objects
.
all
()
permission_classes
=
(
IsValidUser
,)
filter_fields
=
[
'status'
,
'title'
,
'action'
]
search_fields
=
[
'user_display'
,
'title'
]
class
TicketCommentViewSet
(
viewsets
.
ModelViewSet
):
serializer_class
=
serializers
.
CommentSerializer
http_method_names
=
[
'get'
,
'post'
]
def
check_permissions
(
self
,
request
):
ticket
=
self
.
ticket
if
request
.
user
==
ticket
.
user
or
request
.
user
in
ticket
.
assignees
.
all
():
if
request
.
user
==
ticket
.
user
or
\
request
.
user
in
ticket
.
assignees
.
all
():
return
True
return
False
...
...
apps/tickets/migrations/0001_initial.py
View file @
ebe129b3
# Generated by Django 2.2.5 on 2019-11-
07 08:02
# Generated by Django 2.2.5 on 2019-11-
15 06:57
import
common.fields.model
from
django.conf
import
settings
from
django.db
import
migrations
,
models
import
django.db.models.deletion
...
...
@@ -25,10 +26,12 @@ class Migration(migrations.Migration):
(
'user_display'
,
models
.
CharField
(
max_length
=
128
,
verbose_name
=
'User display name'
)),
(
'title'
,
models
.
CharField
(
max_length
=
256
,
verbose_name
=
'Title'
)),
(
'body'
,
models
.
TextField
(
verbose_name
=
'Body'
)),
(
'meta'
,
common
.
fields
.
model
.
JsonDictTextField
(
default
=
'{}'
,
verbose_name
=
'Meta'
)),
(
'assignee_display'
,
models
.
CharField
(
blank
=
True
,
max_length
=
128
,
null
=
True
,
verbose_name
=
'Assignee display name'
)),
(
'assignees_display'
,
models
.
CharField
(
blank
=
True
,
max_length
=
128
,
verbose_name
=
'Assignees display name'
)),
(
'type'
,
models
.
CharField
(
default
=
'general'
,
max_length
=
16
,
verbose_name
=
'Type'
)),
(
'type'
,
models
.
CharField
(
choices
=
[(
'general'
,
'General'
),
(
'login_confirm'
,
'Login confirm'
)],
default
=
'general'
,
max_length
=
16
,
verbose_name
=
'Type'
)),
(
'status'
,
models
.
CharField
(
choices
=
[(
'open'
,
'Open'
),
(
'closed'
,
'Closed'
)],
default
=
'open'
,
max_length
=
16
)),
(
'action'
,
models
.
CharField
(
blank
=
True
,
choices
=
[(
'approve'
,
'Approve'
),
(
'reject'
,
'Reject'
)],
default
=
''
,
max_length
=
16
)),
(
'assignee'
,
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
SET_NULL
,
related_name
=
'ticket_handled'
,
to
=
settings
.
AUTH_USER_MODEL
,
verbose_name
=
'Assignee'
)),
(
'assignees'
,
models
.
ManyToManyField
(
related_name
=
'ticket_assigned'
,
to
=
settings
.
AUTH_USER_MODEL
,
verbose_name
=
'Assignees'
)),
(
'user'
,
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
SET_NULL
,
related_name
=
'ticket_requested'
,
to
=
settings
.
AUTH_USER_MODEL
,
verbose_name
=
'User'
)),
...
...
@@ -37,19 +40,6 @@ class Migration(migrations.Migration):
'ordering'
:
(
'-date_created'
,),
},
),
migrations
.
CreateModel
(
name
=
'LoginConfirmTicket'
,
fields
=
[
(
'ticket_ptr'
,
models
.
OneToOneField
(
auto_created
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
parent_link
=
True
,
primary_key
=
True
,
serialize
=
False
,
to
=
'tickets.Ticket'
)),
(
'ip'
,
models
.
GenericIPAddressField
(
blank
=
True
,
null
=
True
)),
(
'city'
,
models
.
CharField
(
blank
=
True
,
default
=
''
,
max_length
=
16
)),
(
'action'
,
models
.
CharField
(
blank
=
True
,
choices
=
[(
'approve'
,
'Approve'
),
(
'reject'
,
'Reject'
)],
default
=
''
,
max_length
=
16
)),
],
options
=
{
'abstract'
:
False
,
},
bases
=
(
'tickets.ticket'
,),
),
migrations
.
CreateModel
(
name
=
'Comment'
,
fields
=
[
...
...
@@ -59,7 +49,7 @@ class Migration(migrations.Migration):
(
'date_updated'
,
models
.
DateTimeField
(
auto_now
=
True
,
verbose_name
=
'Date updated'
)),
(
'user_display'
,
models
.
CharField
(
max_length
=
128
,
verbose_name
=
'User display name'
)),
(
'body'
,
models
.
TextField
(
verbose_name
=
'Body'
)),
(
'ticket'
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
to
=
'tickets.Ticket'
)),
(
'ticket'
,
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
'comments'
,
to
=
'tickets.Ticket'
)),
(
'user'
,
models
.
ForeignKey
(
null
=
True
,
on_delete
=
django
.
db
.
models
.
deletion
.
SET_NULL
,
related_name
=
'comments'
,
to
=
settings
.
AUTH_USER_MODEL
,
verbose_name
=
'User'
)),
],
options
=
{
...
...
apps/tickets/migrations/0002_auto_20191114_1105.py
deleted
100644 → 0
View file @
18f88647
# Generated by Django 2.2.5 on 2019-11-14 03:05
from
django.db
import
migrations
,
models
import
django.db.models.deletion
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'tickets'
,
'0001_initial'
),
]
operations
=
[
migrations
.
AlterField
(
model_name
=
'comment'
,
name
=
'ticket'
,
field
=
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
CASCADE
,
related_name
=
'comments'
,
to
=
'tickets.Ticket'
),
),
migrations
.
AlterField
(
model_name
=
'ticket'
,
name
=
'type'
,
field
=
models
.
CharField
(
choices
=
[(
'general'
,
'General'
),
(
'login_confirm'
,
'Login confirm'
)],
default
=
'general'
,
max_length
=
16
,
verbose_name
=
'Type'
),
),
]
apps/tickets/models/__init__.py
View file @
ebe129b3
# -*- coding: utf-8 -*-
#
from
.base
import
*
from
.login_confirm
import
*
from
.ticket
import
*
apps/tickets/models/login_confirm.py
deleted
100644 → 0
View file @
18f88647
# -*- coding: utf-8 -*-
#
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
.base
import
Ticket
__all__
=
[
'LoginConfirmTicket'
]
class
LoginConfirmTicket
(
Ticket
):
ACTION_APPROVE
=
'approve'
ACTION_REJECT
=
'reject'
ACTION_CHOICES
=
(
(
ACTION_APPROVE
,
_
(
'Approve'
)),
(
ACTION_REJECT
,
_
(
'Reject'
)),
)
ip
=
models
.
GenericIPAddressField
(
blank
=
True
,
null
=
True
)
city
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
,
default
=
''
)
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/models/
base
.py
→
apps/tickets/models/
ticket
.py
View file @
ebe129b3
...
...
@@ -5,6 +5,7 @@ from django.db import models
from
django.utils.translation
import
ugettext_lazy
as
_
from
common.mixins.models
import
CommonModelMixin
from
common.fields.model
import
JsonDictTextField
__all__
=
[
'Ticket'
,
'Comment'
]
...
...
@@ -22,17 +23,25 @@ class Ticket(CommonModelMixin):
(
TYPE_GENERAL
,
_
(
"General"
)),
(
TYPE_LOGIN_CONFIRM
,
_
(
"Login confirm"
))
)
ACTION_APPROVE
=
'approve'
ACTION_REJECT
=
'reject'
ACTION_CHOICES
=
(
(
ACTION_APPROVE
,
_
(
'Approve'
)),
(
ACTION_REJECT
,
_
(
'Reject'
)),
)
user
=
models
.
ForeignKey
(
'users.User'
,
on_delete
=
models
.
SET_NULL
,
null
=
True
,
related_name
=
'
%(class)
s_requested'
,
verbose_name
=
_
(
"User"
))
user_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"User display name"
))
title
=
models
.
CharField
(
max_length
=
256
,
verbose_name
=
_
(
"Title"
))
body
=
models
.
TextField
(
verbose_name
=
_
(
"Body"
))
meta
=
JsonDictTextField
(
verbose_name
=
_
(
"Meta"
),
default
=
'{}'
)
assignee
=
models
.
ForeignKey
(
'users.User'
,
on_delete
=
models
.
SET_NULL
,
null
=
True
,
related_name
=
'
%(class)
s_handled'
,
verbose_name
=
_
(
"Assignee"
))
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_display
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"Assignees display name"
),
blank
=
True
)
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'
)
action
=
models
.
CharField
(
choices
=
ACTION_CHOICES
,
max_length
=
16
,
default
=
''
,
blank
=
True
)
def
__str__
(
self
):
return
'{}: {}'
.
format
(
self
.
user_display
,
self
.
title
)
...
...
@@ -45,6 +54,14 @@ class Ticket(CommonModelMixin):
def
status_display
(
self
):
return
self
.
get_status_display
()
@property
def
type_display
(
self
):
return
self
.
get_type_display
()
@property
def
action_display
(
self
):
return
self
.
get_action_display
()
def
create_status_comment
(
self
,
status
,
user
):
if
status
==
self
.
STATUS_CLOSED
:
action
=
_
(
"Close"
)
...
...
@@ -59,6 +76,19 @@ class Ticket(CommonModelMixin):
self
.
status
=
status
self
.
save
()
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
()
class
Meta
:
ordering
=
(
'-date_created'
,)
...
...
apps/tickets/serializers/__init__.py
View file @
ebe129b3
# -*- coding: utf-8 -*-
#
from
.base
import
*
from
.login_confirm
import
*
from
.ticket
import
*
apps/tickets/serializers/login_confirm.py
deleted
100644 → 0
View file @
18f88647
# -*- coding: utf-8 -*-
#
from
rest_framework
import
serializers
from
common.serializers
import
AdaptedBulkListSerializer
from
common.mixins.serializers
import
BulkSerializerMixin
from
.base
import
TicketSerializer
from
..models
import
LoginConfirmTicket
__all__
=
[
'LoginConfirmTicketSerializer'
,
'LoginConfirmTicketActionSerializer'
]
class
LoginConfirmTicketSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
class
Meta
:
list_serializer_class
=
AdaptedBulkListSerializer
model
=
LoginConfirmTicket
fields
=
TicketSerializer
.
Meta
.
fields
+
[
'ip'
,
'city'
,
'action'
]
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
)
if
instance
.
status
==
instance
.
STATUS_CLOSED
:
validated_data
.
pop
(
'action'
)
instance
=
super
()
.
update
(
instance
,
validated_data
)
if
not
instance
.
status
==
instance
.
STATUS_CLOSED
:
instance
.
perform_action
(
action
,
user
)
return
instance
class
LoginConfirmTicketActionSerializer
(
serializers
.
ModelSerializer
):
comment
=
serializers
.
CharField
(
allow_blank
=
True
)
class
Meta
:
model
=
LoginConfirmTicket
fields
=
[
'action'
]
def
update
(
self
,
instance
,
validated_data
):
pass
def
create
(
self
,
validated_data
):
pass
apps/tickets/serializers/
base
.py
→
apps/tickets/serializers/
ticket
.py
View file @
ebe129b3
...
...
@@ -14,12 +14,31 @@ class TicketSerializer(serializers.ModelSerializer):
'id'
,
'user'
,
'user_display'
,
'title'
,
'body'
,
'assignees'
,
'assignees_display'
,
'status'
,
'date_created'
,
'date_updated'
,
'type_display'
,
'action_display'
,
]
read_only_fields
=
[
'user_display'
,
'assignees_display'
,
'date_created'
,
'date_updated'
,
]
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
)
if
instance
.
status
==
instance
.
STATUS_CLOSED
:
validated_data
.
pop
(
'action'
)
instance
=
super
()
.
update
(
instance
,
validated_data
)
if
not
instance
.
status
==
instance
.
STATUS_CLOSED
and
action
:
instance
.
perform_action
(
action
,
user
)
return
instance
class
CurrentTicket
(
object
):
ticket
=
None
...
...
apps/tickets/signals_handler.py
View file @
ebe129b3
...
...
@@ -4,24 +4,24 @@ from django.dispatch import receiver
from
django.db.models.signals
import
m2m_changed
,
post_save
,
pre_save
from
common.utils
import
get_logger
from
.models
import
LoginConfirmTicket
,
Ticket
,
Comment
from
.models
import
Ticket
,
Comment
from
.utils
import
(
send_
login_confirm
_ticket_mail_to_assignees
,
send_
login_confirm
_action_mail_to_user
send_
new
_ticket_mail_to_assignees
,
send_
ticket
_action_mail_to_user
)
logger
=
get_logger
(
__name__
)
@receiver
(
m2m_changed
,
sender
=
LoginConfirm
Ticket
.
assignees
.
through
)
@receiver
(
m2m_changed
,
sender
=
Ticket
.
assignees
.
through
)
def
on_login_confirm_ticket_assignees_set
(
sender
,
instance
=
None
,
action
=
None
,
reverse
=
False
,
model
=
None
,
pk_set
=
None
,
**
kwargs
):
if
action
==
'post_add'
:
logger
.
debug
(
'New ticket create, send mail: {}'
.
format
(
instance
.
id
))
assignees
=
model
.
objects
.
filter
(
pk__in
=
pk_set
)
send_
login_confirm
_ticket_mail_to_assignees
(
instance
,
assignees
)
send_
new
_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
()
...
...
@@ -29,15 +29,15 @@ def on_login_confirm_ticket_assignees_set(sender, instance=None, action=None,
instance
.
save
()
@receiver
(
post_save
,
sender
=
LoginConfirm
Ticket
)
@receiver
(
post_save
,
sender
=
Ticket
)
def
on_login_confirm_ticket_status_change
(
sender
,
instance
=
None
,
created
=
False
,
**
kwargs
):
if
created
or
instance
.
status
==
"open"
:
return
logger
.
debug
(
'Ticket changed, send mail: {}'
.
format
(
instance
.
id
))
send_
login_confirm
_action_mail_to_user
(
instance
)
send_
ticket
_action_mail_to_user
(
instance
)
@receiver
(
pre_save
,
sender
=
LoginConfirm
Ticket
)
@receiver
(
pre_save
,
sender
=
Ticket
)
def
on_ticket_create
(
sender
,
instance
=
None
,
**
kwargs
):
instance
.
user_display
=
str
(
instance
.
user
)
if
instance
.
assignee
:
...
...
apps/tickets/templates/tickets/login_confirm_ticket_detail.html
deleted
100644 → 0
View file @
18f88647
{% extends 'tickets/ticket_detail.html' %}
{% load static %}
{% load i18n %}
{% block status %}
{% endblock %}
{% 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 %}
{% block custom_foot_js %}
{{ block.super }}
<script>
var
ticketDetailUrl
=
"{% url 'api-tickets:login-confirm-ticket-detail' pk=object.id %}"
;
$
(
document
).
ready
(
function
()
{
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
createComment
(
function
()
{
});
var
action
=
$
(
this
).
data
(
'action'
);
var
data
=
{
url
:
ticketDetailUrl
,
body
:
JSON
.
stringify
({
action
:
action
}),
method
:
"PATCH"
,
success
:
reloadPage
};
requestApi
(
data
);
})
</script>
{% endblock %}
apps/tickets/templates/tickets/login_confirm_ticket_list.html
deleted
100644 → 0
View file @
18f88647
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}
{% endblock %}
{% block custom_head_css_js %}
{% endblock %}
{% block table_container %}
<table
class=
"table table-striped table-bordered table-hover "
id=
"login_confirm_ticket_list_table"
>
<thead>
<tr>
<th
class=
"text-center"
>
<input
id=
""
type=
"checkbox"
class=
"ipt_check_all"
>
</th>
<th
class=
"text-center"
>
{% trans 'Title' %}
</th>
<th
class=
"text-center"
>
{% trans 'User' %}
</th>
<th
class=
"text-center"
>
{% trans 'Status' %}
</th>
<th
class=
"text-center"
>
{% trans 'Datetime' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div
id=
"actions"
class=
"hide"
>
<div
class=
"input-group"
>
<select
class=
"form-control m-b"
style=
"width: auto"
id=
"slct_bulk_update"
>
<option
value=
"approve"
>
{% trans 'Approve selected' %}
</option>
<option
value=
"reject"
>
{% trans 'Reject selected' %}
</option>
</select>
<div
class=
"input-group-btn pull-left"
style=
"padding-left: 5px;"
>
<button
id=
'btn_bulk_update'
style=
"height: 32px;"
class=
"btn btn-sm btn-primary"
>
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var
ticketTable
=
0
;
function
initTable
()
{
var
options
=
{
ele
:
$
(
'#login_confirm_ticket_list_table'
),
oSearch
:
{
sSearch
:
"status:open"
},
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
cellData
=
htmlEscape
(
cellData
);
var
detailBtn
=
'<a href="{% url "tickets:login-confirm-ticket-detail" pk=DEFAULT_PK %}">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detailBtn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
if
(
cellData
===
"approve"
)
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
else
if
(
cellData
===
"reject"
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
if
(
cellData
===
"open"
)
{
$
(
td
).
html
(
'<i class="fa fa-spinner text-info"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-circle text-info"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
d
=
toSafeLocalDateStr
(
cellData
);
$
(
td
).
html
(
d
)
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
acceptBtn
=
'<a class="btn btn-xs btn-info btn-action" data-action="approve" 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>'
;
acceptBtn
=
acceptBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
rejectBtn
=
rejectBtn
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
var
acceptBtnRef
=
$
(
acceptBtn
);
var
rejectBtnRef
=
$
(
rejectBtn
);
if
(
rowData
.
action
!==
""
)
{
acceptBtnRef
.
attr
(
'disabled'
,
'disabled'
);
rejectBtnRef
.
attr
(
'disabled'
,
'disabled'
);
}
var
btn
=
acceptBtnRef
.
prop
(
'outerHTML'
)
+
' '
+
rejectBtnRef
.
prop
(
'outerHTML'
);
$
(
td
).
html
(
btn
)
}}],
ajax_url
:
'{% url "api-tickets:login-confirm-ticket-list" %}'
,
columns
:
[
{
data
:
"id"
},
{
data
:
"title"
},
{
data
:
"user_display"
},
{
data
:
"action"
,
width
:
"40px"
},
{
data
:
"date_created"
,
width
:
"120px"
},
{
data
:
"id"
,
orderable
:
false
}
],
op_html
:
$
(
'#actions'
).
html
()
};
ticketTable
=
jumpserver
.
initServerSideDataTable
(
options
);
return
ticketTable
}
$
(
document
).
ready
(
function
(){
initTable
();
var
menu
=
[
{
title
:
"IP"
,
value
:
"ip"
},
{
title
:
"{% trans 'Title' %}"
,
value
:
"title"
},
{
title
:
"{% trans 'User' %}"
,
value
:
"user_display"
},
{
title
:
"{% trans 'Status' %}"
,
value
:
"status"
,
submenu
:
[
{
title
:
"{% trans 'Open' %}"
,
value
:
"open"
},
{
title
:
"{% trans 'Closed' %}"
,
value
:
"closed"
},
]},
{
title
:
"{% trans 'Action' %}"
,
value
:
"action"
,
submenu
:
[
{
title
:
"{% trans 'Approve' %}"
,
value
:
"approve"
},
{
title
:
"{% trans 'Reject' %}"
,
value
:
"reject"
},
]},
];
initTableFilterDropdown
(
'#login_confirm_ticket_list_table_filter input'
,
menu
)
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
var
ticketId
=
$
(
this
).
data
(
"uid"
);
var
action
=
$
(
this
).
data
(
'action'
);
var
ticketDetailUrl
=
"{% url 'api-tickets:login-confirm-ticket-detail' pk=DEFAULT_PK %}"
;
ticketDetailUrl
=
ticketDetailUrl
.
replace
(
"{{ DEFAULT_PK }}"
,
ticketId
);
var
data
=
{
url
:
ticketDetailUrl
,
body
:
JSON
.
stringify
({
action
:
action
}),
method
:
"PATCH"
,
success
:
reloadPage
};
requestApi
(
data
);
}).
on
(
'click'
,
'#btn_bulk_update'
,
function
()
{
var
action
=
$
(
'#slct_bulk_update'
).
val
();
var
idList
=
ticketTable
.
selected
;
if
(
idList
.
length
===
0
)
{
return
false
;
}
var
theUrl
=
"{% url 'api-tickets:login-confirm-ticket-list' %}"
;
function
doAction
(
action
)
{
var
data
=
[];
$
.
each
(
idList
,
function
(
index
,
object_id
)
{
var
obj
=
{
"pk"
:
object_id
,
"action"
:
action
};
data
.
push
(
obj
);
});
requestApi
({
url
:
theUrl
,
method
:
'PATCH'
,
body
:
JSON
.
stringify
(
data
),
success
:
function
(){
$
(
".ipt_check_all"
).
prop
(
"checked"
,
false
)
ticketTable
.
ajax
.
reload
();
}
});
}
doAction
(
action
)
})
</script>
{% endblock %}
apps/tickets/templates/tickets/ticket_detail.html
View file @
ebe129b3
...
...
@@ -72,7 +72,6 @@
</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'%}"
>
...
...
@@ -97,14 +96,12 @@
</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 %}
{% if object.type == object.TYPE_LOGIN_CONFIRM %}
<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-warning btn-update btn-action"
data-action=
"reject"
><i
class=
"fa fa-ban"
></i>
{% trans 'Reject' %}
</a>
{% endif %}
<a
class=
"btn btn-sm btn-danger btn-update btn-status"
data-uid=
"closed"
><i
class=
"fa fa-times"
></i>
{% trans 'Close' %}
</a>
<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>
...
...
@@ -127,6 +124,7 @@ var ticketId = "{{ object.id }}";
var
status
=
"{{ object.status }}"
;
var
commentUrl
=
"{% url 'api-tickets:ticket-comment-list' ticket_id=object.id %}"
;
var
ticketDetailUrl
=
"{% url 'api-tickets:ticket-detail' pk=object.id %}"
;
function
createComment
(
successCallback
)
{
var
commentText
=
$
(
"#comment"
).
val
();
...
...
@@ -158,5 +156,26 @@ $(document).ready(function () {
.
on
(
'click'
,
'.btn-comment'
,
function
()
{
createComment
();
})
.
on
(
'click'
,
'.btn-action'
,
function
()
{
createComment
(
function
()
{});
var
action
=
$
(
this
).
data
(
'action'
);
var
data
=
{
url
:
ticketDetailUrl
,
body
:
JSON
.
stringify
({
action
:
action
}),
method
:
"PATCH"
,
success
:
reloadPage
};
requestApi
(
data
);
})
.
on
(
'click'
,
'.btn-status'
,
function
()
{
var
status
=
$
(
this
).
data
(
'uid'
);
var
data
=
{
url
:
ticketDetailUrl
,
body
:
JSON
.
stringify
({
status
:
status
}),
method
:
"PATCH"
,
success
:
reloadPage
};
requestApi
(
data
);
})
</script>
{% endblock %}
apps/tickets/templates/tickets/ticket_list.html
0 → 100644
View file @
ebe129b3
{% extends 'base.html' %}
{% load i18n static %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeIn"
>
<div
class=
"col-lg-12"
>
<div
class=
"tabs-container"
>
<ul
class=
"nav nav-tabs"
>
<li
{%
if
not
assign
%}
class=
"active"
{%
endif
%}
><a
href=
"{% url 'tickets:ticket-list' %}"
>
{% trans 'My tickets' %}
</a></li>
<li
{%
if
assign
%}
class=
"active"
{%
endif
%}
><a
href=
"{% url 'tickets:ticket-list' %}?assign=1"
>
{% trans 'Assigned me' %}
</a></li>
</ul>
<div
class=
"tab-content"
>
<div
id=
"my-tickets"
class=
"tab-pane active"
>
<div
class=
"panel-body"
>
<table
class=
"table table-striped table-bordered table-hover"
id=
"ticket-list-table"
>
<thead>
<tr>
<th
class=
"text-center"
>
<input
id=
""
type=
"checkbox"
class=
"ipt_check_all"
>
</th>
<th
class=
"text-center"
>
{% trans 'Title' %}
</th>
<th
class=
"text-center"
>
{% trans 'User' %}
</th>
<th
class=
"text-center"
>
{% trans 'Type' %}
</th>
<th
class=
"text-center"
>
{% trans 'Status' %}
</th>
<th
class=
"text-center"
>
{% trans 'Datetime' %}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
var
assignedTable
,
myTable
,
listUrl
;
{
%
if
assign
%
}
listUrl
=
'{% url "api-tickets:ticket-list" %}?assign=1'
;
{
%
else
%
}
listUrl
=
'{% url "api-tickets:ticket-list" %}?assign=0'
;
{
%
endif
%
}
function
initTable
()
{
var
options
=
{
ele
:
$
(
'#ticket-list-table'
),
oSearch
:
{
sSearch
:
"status:open"
},
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
cellData
=
htmlEscape
(
cellData
);
var
detailBtn
=
'<a href="{% url "tickets:ticket-detail" pk=DEFAULT_PK %}">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detailBtn
.
replace
(
"{{ DEFAULT_PK }}"
,
rowData
.
id
));
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
cellData
===
"open"
)
{
$
(
td
).
html
(
'<i class="fa fa-check-circle-o text-navy"></i>'
);
}
else
{
$
(
td
).
html
(
'<i class="fa fa-times-circle-o text-danger"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
d
=
toSafeLocalDateStr
(
cellData
);
$
(
td
).
html
(
d
)
}},
],
ajax_url
:
listUrl
,
columns
:
[
{
data
:
"id"
},
{
data
:
"title"
},
{
data
:
"user_display"
},
{
data
:
"type_display"
},
{
data
:
"status"
,
width
:
"40px"
},
{
data
:
"date_created"
},
],
op_html
:
$
(
'#actions'
).
html
()
};
myTable
=
jumpserver
.
initServerSideDataTable
(
options
);
return
myTable
}
$
(
document
).
ready
(
function
(){
initTable
();
var
menu
=
[
{
title
:
"IP"
,
value
:
"ip"
},
{
title
:
"{% trans 'Title' %}"
,
value
:
"title"
},
{
title
:
"{% trans 'User' %}"
,
value
:
"user_display"
},
{
title
:
"{% trans 'Status' %}"
,
value
:
"status"
,
submenu
:
[
{
title
:
"{% trans 'Open' %}"
,
value
:
"open"
},
{
title
:
"{% trans 'Closed' %}"
,
value
:
"closed"
},
]},
{
title
:
"{% trans 'Action' %}"
,
value
:
"action"
,
submenu
:
[
{
title
:
"{% trans 'Approve' %}"
,
value
:
"approve"
},
{
title
:
"{% trans 'Reject' %}"
,
value
:
"reject"
},
]},
];
initTableFilterDropdown
(
'#assigned-ticket-list-table input'
,
menu
)
}).
on
(
'click'
,
'.btn-action'
,
function
()
{
var
ticketId
=
$
(
this
).
data
(
"uid"
);
var
action
=
$
(
this
).
data
(
'action'
);
var
ticketDetailUrl
=
"{% url 'api-tickets:ticket-detail' pk=DEFAULT_PK %}"
;
ticketDetailUrl
=
ticketDetailUrl
.
replace
(
"{{ DEFAULT_PK }}"
,
ticketId
);
var
data
=
{
url
:
ticketDetailUrl
,
body
:
JSON
.
stringify
({
action
:
action
}),
method
:
"PATCH"
,
success
:
reloadPage
};
requestApi
(
data
);
})
</script>
{% endblock %}
apps/tickets/urls/api_urls.py
View file @
ebe129b3
...
...
@@ -9,7 +9,6 @@ router = BulkRouter()
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'
)
urlpatterns
=
[
...
...
apps/tickets/urls/views_urls.py
View file @
ebe129b3
...
...
@@ -6,6 +6,6 @@ from .. import views
app_name
=
'tickets'
urlpatterns
=
[
path
(
'
login-confirm-tickets/'
,
views
.
LoginConfirmTicketListView
.
as_view
(),
name
=
'login-confirm-
ticket-list'
),
path
(
'
login-confirm-tickets/<uuid:pk>/'
,
views
.
LoginConfirmTicketDetailView
.
as_view
(),
name
=
'login-confirm-ticket-detail'
)
path
(
'
tickets/'
,
views
.
TicketListView
.
as_view
(),
name
=
'
ticket-list'
),
path
(
'
tickets/<uuid:pk>/'
,
views
.
TicketDetailView
.
as_view
(),
name
=
'ticket-detail'
),
]
apps/tickets/utils.py
View file @
ebe129b3
...
...
@@ -9,37 +9,29 @@ from common.tasks import send_mail_async
logger
=
get_logger
(
__name__
)
def
send_
login_confirm
_ticket_mail_to_assignees
(
ticket
,
assignees
):
def
send_
new
_ticket_mail_to_assignees
(
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:
login-confirm-
ticket-detail'
,
detail_url
=
reverse
(
'tickets:ticket-detail'
,
kwargs
=
{
'pk'
:
ticket
.
id
},
external
=
True
)
message
=
_
(
"""
<div>
<p>Your has a new ticket</p>
<div>
<b>Title:</b> {ticket.title}
<br/>
<b>User:</b> {user}
<br/>
<b>Assignees:</b> {ticket.assignees_display}
<br/>
<b>City:</b> {ticket.city}
<br/>
<b>IP:</b> {ticket.ip}
{body}
<br/>
<a href={url}>click here to review</a>
</div>
</div>
"""
)
.
format
(
ticket
=
ticket
,
user
=
user
,
url
=
detail_url
)
"""
)
.
format
(
body
=
ticket
.
body
,
user
=
user
,
url
=
detail_url
)
send_mail_async
.
delay
(
subject
,
message
,
recipient_list
,
html_message
=
message
)
def
send_
login_confirm
_action_mail_to_user
(
ticket
):
def
send_
ticket
_action_mail_to_user
(
ticket
):
if
not
ticket
.
user
:
logger
.
error
(
"Ticket not has user: {}"
.
format
(
ticket
.
id
))
return
...
...
apps/tickets/views.py
View file @
ebe129b3
...
...
@@ -2,32 +2,34 @@ from django.views.generic import TemplateView, DetailView
from
django.utils.translation
import
ugettext
as
_
from
common.permissions
import
PermissionsMixin
,
IsValidUser
from
.models
import
LoginConfirm
Ticket
from
.models
import
Ticket
from
.
import
mixins
class
LoginConfirm
TicketListView
(
PermissionsMixin
,
TemplateView
):
template_name
=
'tickets/
login_confirm_
ticket_list.html'
class
TicketListView
(
PermissionsMixin
,
TemplateView
):
template_name
=
'tickets/ticket_list.html'
permission_classes
=
(
IsValidUser
,)
def
get_context_data
(
self
,
**
kwargs
):
assign
=
self
.
request
.
GET
.
get
(
'assign'
,
'0'
)
==
'1'
context
=
super
()
.
get_context_data
(
**
kwargs
)
context
.
update
({
'app'
:
_
(
"Tickets"
),
'action'
:
_
(
"Login confirm ticket list"
)
'action'
:
_
(
"Ticket list"
),
'assign'
:
assign
,
})
return
context
class
LoginConfirmTicketDetailView
(
PermissionsMixin
,
mixins
.
TicketMixin
,
DetailView
):
template_name
=
'tickets/login_confirm_ticket_detail.html'
queryset
=
LoginConfirmTicket
.
objects
.
all
()
class
TicketDetailView
(
PermissionsMixin
,
mixins
.
TicketMixin
,
DetailView
):
template_name
=
'tickets/ticket_detail.html'
permission_classes
=
(
IsValidUser
,)
queryset
=
Ticket
.
objects
.
all
()
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
()
.
get_context_data
(
**
kwargs
)
context
.
update
({
'app'
:
_
(
"Tickets"
),
'action'
:
_
(
"
Login confirm t
icket detail"
)
'action'
:
_
(
"
T
icket detail"
)
})
return
context
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