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
08e17884
Commit
08e17884
authored
Dec 14, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 打算拆分下载和上传为独立模块,时间有限暂时放弃
parent
160b01ec
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
121 additions
and
105 deletions
+121
-105
forms.py
apps/assets/forms.py
+5
-2
user.py
apps/assets/models/user.py
+23
-5
serializers.py
apps/assets/serializers.py
+1
-1
_system_user.html
apps/assets/templates/assets/_system_user.html
+1
-1
asset_update.html
apps/assets/templates/assets/asset_update.html
+3
-1
admin_user.py
apps/assets/views/admin_user.py
+1
-1
asset.py
apps/assets/views/asset.py
+10
-42
cluster.py
apps/assets/views/cluster.py
+1
-1
group.py
apps/assets/views/group.py
+1
-1
system_user.py
apps/assets/views/system_user.py
+1
-1
imexp.py
apps/common/imexp.py
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+3
-3
views.py
apps/perms/views.py
+11
-11
delete_confirm.html
apps/templates/delete_confirm.html
+16
-0
command_list.html
apps/terminal/templates/terminal/command_list.html
+6
-2
session_list.html
apps/terminal/templates/terminal/session_list.html
+17
-11
terminal_list.html
apps/terminal/templates/terminal/terminal_list.html
+8
-9
terminal_modal_accept.html
apps/terminal/templates/terminal/terminal_modal_accept.html
+3
-3
api_urls.py
apps/terminal/urls/api_urls.py
+2
-2
command.py
apps/terminal/views/command.py
+1
-1
session.py
apps/terminal/views/session.py
+2
-1
terminal.py
apps/terminal/views/terminal.py
+2
-2
authentication.py
apps/users/models/authentication.py
+0
-1
user.py
apps/users/models/user.py
+3
-3
No files found.
apps/assets/forms.py
View file @
08e17884
...
@@ -9,6 +9,7 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen,
...
@@ -9,6 +9,7 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen,
logger
=
get_logger
(
__file__
)
logger
=
get_logger
(
__file__
)
from
rest_framework
import
serializers
class
AssetCreateForm
(
forms
.
ModelForm
):
class
AssetCreateForm
(
forms
.
ModelForm
):
...
@@ -240,7 +241,7 @@ class SystemUserForm(forms.ModelForm):
...
@@ -240,7 +241,7 @@ class SystemUserForm(forms.ModelForm):
fields
=
[
fields
=
[
'name'
,
'username'
,
'protocol'
,
'auto_generate_key'
,
'name'
,
'username'
,
'protocol'
,
'auto_generate_key'
,
'password'
,
'private_key_file'
,
'auto_push'
,
'sudo'
,
'password'
,
'private_key_file'
,
'auto_push'
,
'sudo'
,
'comment'
,
'shell'
,
'cluster'
'comment'
,
'shell'
,
'cluster'
,
'priority'
,
]
]
widgets
=
{
widgets
=
{
'name'
:
forms
.
TextInput
(
attrs
=
{
'placeholder'
:
_
(
'Name'
)}),
'name'
:
forms
.
TextInput
(
attrs
=
{
'placeholder'
:
_
(
'Name'
)}),
...
@@ -254,6 +255,7 @@ class SystemUserForm(forms.ModelForm):
...
@@ -254,6 +255,7 @@ class SystemUserForm(forms.ModelForm):
'username'
:
'* required'
,
'username'
:
'* required'
,
'cluster'
:
'If auto push checked, system user will be create at cluster assets'
,
'cluster'
:
'If auto push checked, system user will be create at cluster assets'
,
'auto_push'
:
'Auto push system user to asset'
,
'auto_push'
:
'Auto push system user to asset'
,
'priority'
:
'High level will be using login asset as default, if user was granted more than 2 system user'
,
}
}
...
@@ -261,7 +263,7 @@ class SystemUserUpdateForm(forms.ModelForm):
...
@@ -261,7 +263,7 @@ class SystemUserUpdateForm(forms.ModelForm):
class
Meta
:
class
Meta
:
model
=
SystemUser
model
=
SystemUser
fields
=
[
fields
=
[
'name'
,
'username'
,
'protocol'
,
'name'
,
'username'
,
'protocol'
,
'priority'
,
'sudo'
,
'comment'
,
'shell'
,
'cluster'
'sudo'
,
'comment'
,
'shell'
,
'cluster'
]
]
widgets
=
{
widgets
=
{
...
@@ -275,6 +277,7 @@ class SystemUserUpdateForm(forms.ModelForm):
...
@@ -275,6 +277,7 @@ class SystemUserUpdateForm(forms.ModelForm):
'name'
:
'* required'
,
'name'
:
'* required'
,
'username'
:
'* required'
,
'username'
:
'* required'
,
'cluster'
:
'If auto push checked, then push system user to that cluster assets'
,
'cluster'
:
'If auto push checked, then push system user to that cluster assets'
,
'priority'
:
'High level will be using login asset as default, if user was granted more than 2 system user'
,
}
}
...
...
apps/assets/models/user.py
View file @
08e17884
...
@@ -7,6 +7,7 @@ import logging
...
@@ -7,6 +7,7 @@ import logging
import
uuid
import
uuid
from
hashlib
import
md5
from
hashlib
import
md5
import
sshpubkeys
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -27,7 +28,8 @@ class AssetUser(models.Model):
...
@@ -27,7 +28,8 @@ class AssetUser(models.Model):
_private_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'SSH private key'
),
validators
=
[
private_key_validator
,
])
_private_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'SSH private key'
),
validators
=
[
private_key_validator
,
])
_public_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
verbose_name
=
_
(
'SSH public key'
))
_public_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
verbose_name
=
_
(
'SSH public key'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date_updated
=
models
.
DateTimeField
(
auto_now
=
True
)
created_by
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
verbose_name
=
_
(
'Created by'
))
created_by
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
verbose_name
=
_
(
'Created by'
))
@property
@property
...
@@ -45,16 +47,21 @@ class AssetUser(models.Model):
...
@@ -45,16 +47,21 @@ class AssetUser(models.Model):
@property
@property
def
private_key
(
self
):
def
private_key
(
self
):
if
self
.
_private_key
:
if
self
.
_private_key
:
key_str
=
signer
.
unsign
(
self
.
_private_key
)
return
signer
.
unsign
(
self
.
_private_key
)
return
ssh_key_string_to_obj
(
key_str
,
password
=
self
.
password
)
else
:
return
None
@private_key.setter
@private_key.setter
def
private_key
(
self
,
private_key_raw
):
def
private_key
(
self
,
private_key_raw
):
raise
AttributeError
(
"Using set_auth do that"
)
raise
AttributeError
(
"Using set_auth do that"
)
# self._private_key = signer.sign(private_key_raw)
# self._private_key = signer.sign(private_key_raw)
@property
def
private_key_obj
(
self
):
if
self
.
_private_key
:
key_str
=
signer
.
unsign
(
self
.
_private_key
)
return
ssh_key_string_to_obj
(
key_str
,
password
=
self
.
password
)
else
:
return
None
@property
@property
def
private_key_file
(
self
):
def
private_key_file
(
self
):
if
not
self
.
private_key
:
if
not
self
.
private_key
:
...
@@ -74,6 +81,15 @@ class AssetUser(models.Model):
...
@@ -74,6 +81,15 @@ class AssetUser(models.Model):
def
public_key
(
self
):
def
public_key
(
self
):
return
signer
.
unsign
(
self
.
_public_key
)
return
signer
.
unsign
(
self
.
_public_key
)
@property
def
public_key_obj
(
self
):
if
self
.
public_key
:
try
:
return
sshpubkeys
.
SSHKey
(
self
.
public_key
)
except
TabError
:
pass
return
None
def
set_auth
(
self
,
password
=
None
,
private_key
=
None
,
public_key
=
None
):
def
set_auth
(
self
,
password
=
None
,
private_key
=
None
,
public_key
=
None
):
update_fields
=
[]
update_fields
=
[]
if
password
:
if
password
:
...
@@ -170,6 +186,7 @@ class SystemUser(AssetUser):
...
@@ -170,6 +186,7 @@ class SystemUser(AssetUser):
(
'K'
,
'Public key'
),
(
'K'
,
'Public key'
),
)
)
cluster
=
models
.
ManyToManyField
(
'assets.Cluster'
,
verbose_name
=
_
(
"Cluster"
))
cluster
=
models
.
ManyToManyField
(
'assets.Cluster'
,
verbose_name
=
_
(
"Cluster"
))
priority
=
models
.
IntegerField
(
default
=
10
,
verbose_name
=
_
(
"Priority"
))
# Todo: If user granted more priority user, default will be login as the hign
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
sudo
=
models
.
TextField
(
default
=
'/sbin/ifconfig'
,
verbose_name
=
_
(
'Sudo'
))
sudo
=
models
.
TextField
(
default
=
'/sbin/ifconfig'
,
verbose_name
=
_
(
'Sudo'
))
...
@@ -205,6 +222,7 @@ class SystemUser(AssetUser):
...
@@ -205,6 +222,7 @@ class SystemUser(AssetUser):
'name'
:
self
.
name
,
'name'
:
self
.
name
,
'username'
:
self
.
username
,
'username'
:
self
.
username
,
'protocol'
:
self
.
protocol
,
'protocol'
:
self
.
protocol
,
'priority'
:
self
.
priority
,
'auto_push'
:
self
.
auto_push
,
'auto_push'
:
self
.
auto_push
,
}
}
...
...
apps/assets/serializers.py
View file @
08e17884
...
@@ -115,7 +115,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
...
@@ -115,7 +115,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
class
AssetSystemUserSerializer
(
serializers
.
ModelSerializer
):
class
AssetSystemUserSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
class
Meta
:
model
=
SystemUser
model
=
SystemUser
fields
=
(
'id'
,
'name'
,
'username'
,
'pr
otocol'
,
'comment'
)
fields
=
(
'id'
,
'name'
,
'username'
,
'pr
iority'
,
'protocol'
,
'comment'
,
)
class
SystemUserUpdateAssetsSerializer
(
serializers
.
ModelSerializer
):
class
SystemUserUpdateAssetsSerializer
(
serializers
.
ModelSerializer
):
...
...
apps/assets/templates/assets/_system_user.html
View file @
08e17884
...
@@ -37,6 +37,7 @@
...
@@ -37,6 +37,7 @@
<h3>
{% trans 'Basic' %}
</h3>
<h3>
{% trans 'Basic' %}
</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
...
@@ -49,7 +50,6 @@
...
@@ -49,7 +50,6 @@
{{ form.auto_generate_key}}
{{ form.auto_generate_key}}
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"auth-fields"
>
<div
class=
"auth-fields"
>
{% bootstrap_field form.private_key_file layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
...
...
apps/assets/templates/assets/asset_update.html
View file @
08e17884
...
@@ -64,7 +64,9 @@
...
@@ -64,7 +64,9 @@
{% block custom_foot_js %}
{% block custom_foot_js %}
<script>
<script>
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
({
allowClear
:
true
});
$
(
"#tags"
).
select2
({
$
(
"#tags"
).
select2
({
tags
:
true
,
tags
:
true
,
maximumSelectionLength
:
8
//最多能够选择的个数
maximumSelectionLength
:
8
//最多能够选择的个数
...
...
apps/assets/views/admin_user.py
View file @
08e17884
...
@@ -123,7 +123,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
...
@@ -123,7 +123,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
class
AdminUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
AdminUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
AdminUser
model
=
AdminUser
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:admin-user-list'
)
success_url
=
reverse_lazy
(
'assets:admin-user-list'
)
apps/assets/views/asset.py
View file @
08e17884
...
@@ -9,12 +9,13 @@ import chardet
...
@@ -9,12 +9,13 @@ import chardet
from
io
import
StringIO
from
io
import
StringIO
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.exceptions
import
ImproperlyConfigured
,
FieldDoesNotExist
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.views.generic
import
TemplateView
,
ListView
,
View
from
django.views.generic
import
TemplateView
,
ListView
,
View
from
django.views.generic.edit
import
CreateView
,
DeleteView
,
FormView
,
UpdateView
from
django.views.generic.edit
import
CreateView
,
DeleteView
,
FormView
,
UpdateView
from
django.urls
import
reverse_lazy
from
django.urls
import
reverse_lazy
from
django.views.generic.detail
import
DetailView
,
SingleObjectMixin
from
django.views.generic.detail
import
DetailView
,
SingleObjectMixin
from
django.http
import
HttpResponse
,
JsonResponse
,
HttpResponseRedirect
from
django.http
import
HttpResponse
,
JsonResponse
,
HttpResponseRedirect
,
Http404
from
django.views.decorators.csrf
import
csrf_protect
,
csrf_exempt
from
django.views.decorators.csrf
import
csrf_protect
,
csrf_exempt
from
django.utils.decorators
import
method_decorator
from
django.utils.decorators
import
method_decorator
from
django.core.cache
import
cache
from
django.core.cache
import
cache
...
@@ -22,8 +23,10 @@ from django.utils import timezone
...
@@ -22,8 +23,10 @@ from django.utils import timezone
from
django.contrib.auth.mixins
import
LoginRequiredMixin
from
django.contrib.auth.mixins
import
LoginRequiredMixin
from
django.shortcuts
import
get_object_or_404
,
redirect
,
reverse
from
django.shortcuts
import
get_object_or_404
,
redirect
,
reverse
from
common.mixins
import
JSONResponseMixin
from
common.mixins
import
JSONResponseMixin
from
common.utils
import
get_object_or_none
from
common.utils
import
get_object_or_none
from
common.imexp
import
ModelExportView
from
..
import
forms
from
..
import
forms
from
..models
import
Asset
,
AssetGroup
,
AdminUser
,
Cluster
,
SystemUser
from
..models
import
Asset
,
AssetGroup
,
AdminUser
,
Cluster
,
SystemUser
from
..hands
import
AdminUserRequiredMixin
from
..hands
import
AdminUserRequiredMixin
...
@@ -169,7 +172,7 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
...
@@ -169,7 +172,7 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
class
AssetDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
AssetDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Asset
model
=
Asset
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:asset-list'
)
success_url
=
reverse_lazy
(
'assets:asset-list'
)
...
@@ -193,46 +196,11 @@ class AssetDetailView(DetailView):
...
@@ -193,46 +196,11 @@ class AssetDetailView(DetailView):
@method_decorator
(
csrf_exempt
,
name
=
'dispatch'
)
@method_decorator
(
csrf_exempt
,
name
=
'dispatch'
)
class
AssetExportView
(
View
):
class
AssetExportView
(
ModelExportView
):
def
get
(
self
,
request
):
filename_prefix
=
'jumpserver'
spm
=
request
.
GET
.
get
(
'spm'
,
''
)
redirect_url
=
reverse_lazy
(
'assets:asset-export'
)
assets_id_default
=
[
Asset
.
objects
.
first
()
.
id
]
if
Asset
.
objects
.
first
()
else
[
1
]
model
=
Asset
assets_id
=
cache
.
get
(
spm
,
assets_id_default
)
fields
=
(
'hostname'
,
'ip'
)
fields
=
[
field
for
field
in
Asset
.
_meta
.
fields
if
field
.
name
not
in
[
'date_created'
]
]
filename
=
'assets-{}.csv'
.
format
(
timezone
.
localtime
(
timezone
.
now
())
.
strftime
(
'
%
Y-
%
m-
%
d_
%
H-
%
M-
%
S'
))
response
=
HttpResponse
(
content_type
=
'text/csv'
)
response
[
'Content-Disposition'
]
=
'attachment; filename="
%
s"'
%
filename
response
.
write
(
codecs
.
BOM_UTF8
)
assets
=
Asset
.
objects
.
filter
(
id__in
=
assets_id
)
writer
=
csv
.
writer
(
response
,
dialect
=
'excel'
,
quoting
=
csv
.
QUOTE_MINIMAL
)
header
=
[
field
.
verbose_name
for
field
in
fields
]
header
.
append
(
_
(
'Asset groups'
))
writer
.
writerow
(
header
)
for
asset
in
assets
:
groups
=
','
.
join
([
group
.
name
for
group
in
asset
.
groups
.
all
()])
data
=
[
getattr
(
asset
,
field
.
name
)
for
field
in
fields
]
data
.
append
(
groups
)
writer
.
writerow
(
data
)
return
response
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
try
:
assets_id
=
json
.
loads
(
request
.
body
)
.
get
(
'assets_id'
,
[])
except
ValueError
:
return
HttpResponse
(
'Json object not valid'
,
status
=
400
)
spm
=
uuid
.
uuid4
()
.
hex
cache
.
set
(
spm
,
assets_id
,
300
)
url
=
reverse_lazy
(
'assets:asset-export'
)
+
'?spm=
%
s'
%
spm
return
JsonResponse
({
'redirect'
:
url
})
class
BulkImportAssetView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
FormView
):
class
BulkImportAssetView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
FormView
):
...
...
apps/assets/views/cluster.py
View file @
08e17884
...
@@ -97,5 +97,5 @@ class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
...
@@ -97,5 +97,5 @@ class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
class
ClusterDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
ClusterDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Cluster
model
=
Cluster
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:cluster-list'
)
success_url
=
reverse_lazy
(
'assets:cluster-list'
)
apps/assets/views/group.py
View file @
08e17884
...
@@ -101,6 +101,6 @@ class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
...
@@ -101,6 +101,6 @@ class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
class
AssetGroupDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
AssetGroupDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
model
=
AssetGroup
model
=
AssetGroup
success_url
=
reverse_lazy
(
'assets:asset-group-list'
)
success_url
=
reverse_lazy
(
'assets:asset-group-list'
)
apps/assets/views/system_user.py
View file @
08e17884
...
@@ -99,7 +99,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
...
@@ -99,7 +99,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
class
SystemUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
SystemUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
SystemUser
model
=
SystemUser
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:system-user-list'
)
success_url
=
reverse_lazy
(
'assets:system-user-list'
)
...
...
apps/common/imexp.py
0 → 100644
View file @
08e17884
This diff is collapsed.
Click to expand it.
apps/locale/zh/LC_MESSAGES/django.po
View file @
08e17884
...
@@ -1067,13 +1067,13 @@ msgstr "配置"
...
@@ -1067,13 +1067,13 @@ msgstr "配置"
msgid "Location"
msgid "Location"
msgstr "位置"
msgstr "位置"
#: assets/templates/
assets/
delete_confirm.html:6
#: assets/templates/delete_confirm.html:6
#: perms/templates/
perms/
delete_confirm.html:6
#: perms/templates/delete_confirm.html:6
#: users/templates/users/user_delete_confirm.html:6
#: users/templates/users/user_delete_confirm.html:6
msgid "Confirm delete"
msgid "Confirm delete"
msgstr "确认删除"
msgstr "确认删除"
#: assets/templates/
assets/
delete_confirm.html:11
#: assets/templates/delete_confirm.html:11
msgid "Are you sure delete"
msgid "Are you sure delete"
msgstr "您确定删除吗?"
msgstr "您确定删除吗?"
...
...
apps/perms/views.py
View file @
08e17884
...
@@ -19,7 +19,7 @@ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
...
@@ -19,7 +19,7 @@ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
Asset
,
AssetGroup
Asset
,
AssetGroup
from
.models
import
AssetPermission
from
.models
import
AssetPermission
from
.forms
import
AssetPermissionForm
from
.forms
import
AssetPermissionForm
from
.utils
import
associate_system_users_and_assets
#
from .utils import associate_system_users_and_assets
class
AssetPermissionListView
(
AdminUserRequiredMixin
,
ListView
):
class
AssetPermissionListView
(
AdminUserRequiredMixin
,
ListView
):
...
@@ -87,15 +87,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
...
@@ -87,15 +87,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
'successfully.'
.
format
(
url
=
url
,
name
=
self
.
object
.
name
))
'successfully.'
.
format
(
url
=
url
,
name
=
self
.
object
.
name
))
return
success_message
return
success_message
def
form_valid
(
self
,
form
):
# Todo: When create push system user
assets
=
form
.
cleaned_data
[
'assets'
]
# def form_valid(self, form):
asset_groups
=
form
.
cleaned_data
[
'asset_group
s'
]
# assets = form.cleaned_data['asset
s']
system_users
=
form
.
cleaned_data
[
'system_user
s'
]
# asset_groups = form.cleaned_data['asset_group
s']
associate_system_users_and_assets
(
system_users
,
assets
,
asset_groups
)
# system_users = form.cleaned_data['system_users']
response
=
super
(
AssetPermissionCreateView
,
self
)
.
form_valid
(
form
)
#
response = super(AssetPermissionCreateView, self).form_valid(form)
self
.
object
.
created_by
=
self
.
request
.
user
.
name
#
self.object.created_by = self.request.user.name
self
.
object
.
save
()
#
self.object.save()
return
response
#
return response
class
AssetPermissionUpdateView
(
AdminUserRequiredMixin
,
UpdateView
):
class
AssetPermissionUpdateView
(
AdminUserRequiredMixin
,
UpdateView
):
...
@@ -150,7 +150,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
...
@@ -150,7 +150,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
class
AssetPermissionDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
AssetPermissionDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
AssetPermission
model
=
AssetPermission
template_name
=
'
perms/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'perms:asset-permission-list'
)
success_url
=
reverse_lazy
(
'perms:asset-permission-list'
)
...
...
apps/templates/delete_confirm.html
0 → 100644
View file @
08e17884
{% load i18n %}
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<meta
charset=
"UTF-8"
>
<title>
{% trans 'Confirm delete' %}
</title>
</head>
<body>
<form
action=
""
method=
"post"
>
{% csrf_token %}
<p>
{% trans 'Are you sure delete' %}
<b>
{{ object.name }}
</b>
?
</p>
<input
type=
"submit"
value=
"Confirm"
/>
</form>
</body>
</html>
\ No newline at end of file
apps/terminal/templates/terminal/command_list.html
View file @
08e17884
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
{% load i18n %}
{% load i18n %}
{% load static %}
{% load static %}
{% load common_tags %}
{% load common_tags %}
{% block c
ontent_left_head
%}
{% block c
ustom_head_css_js
%}
<link
href=
"{% static "
css
/
plugins
/
footable
/
footable
.
core
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static "
css
/
plugins
/
footable
/
footable
.
core
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<style>
<style>
...
@@ -12,6 +12,10 @@
...
@@ -12,6 +12,10 @@
</style>
</style>
{% endblock %}
{% endblock %}
{% block content_left_head %}
123
{% endblock %}
{% block table_search %}
{% block table_search %}
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
<div
class=
"form-group"
id=
"date"
>
<div
class=
"form-group"
id=
"date"
>
...
@@ -26,7 +30,7 @@
...
@@ -26,7 +30,7 @@
<select
class=
"select2 form-control"
name=
"username"
>
<select
class=
"select2 form-control"
name=
"username"
>
<option
value=
""
>
{% trans 'User' %}
</option>
<option
value=
""
>
{% trans 'User' %}
</option>
{% for u in user_list %}
{% for u in user_list %}
<option
value=
"{{ u }}"
{%
if
u
ser =
=
u
%}
selected
{%
endif
%}
>
{{ u }}
</option>
<option
value=
"{{ u }}"
{%
if
u
=
=
username
%}
selected
{%
endif
%}
>
{{ u }}
</option>
{% endfor %}
{% endfor %}
</select>
</select>
</div>
</div>
...
...
apps/terminal/templates/terminal/session_list.html
View file @
08e17884
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
{% load i18n %}
{% load i18n %}
{% load static %}
{% load static %}
{% load terminal_tags %}
{% load terminal_tags %}
{% block c
ontent_left_head
%}
{% block c
ustom_head_css_js
%}
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<style>
<style>
#search_btn
{
#search_btn
{
...
@@ -11,6 +11,9 @@
...
@@ -11,6 +11,9 @@
</style>
</style>
{% endblock %}
{% endblock %}
{% block content_left_head %}
{% endblock %}
{% block table_search %}
{% block table_search %}
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
...
@@ -26,7 +29,7 @@
...
@@ -26,7 +29,7 @@
<select
class=
"select2 form-control"
name=
"user"
>
<select
class=
"select2 form-control"
name=
"user"
>
<option
value=
""
>
{% trans 'User' %}
</option>
<option
value=
""
>
{% trans 'User' %}
</option>
{% for u in user_list %}
{% for u in user_list %}
<option
value=
"{{ u }}"
{%
if
u =
=
user
%}
selected
{%
endif
%}
>
{{ u }}
</option>
<option
value=
"{{ u }}"
{%
if
u =
=
user
name
%}
selected
{%
endif
%}
>
{{ u }}
</option>
{% endfor %}
{% endfor %}
</select>
</select>
</div>
</div>
...
@@ -106,10 +109,17 @@
...
@@ -106,10 +109,17 @@
function
success
()
{
function
success
()
{
window
.
setTimeout
(
function
()
{
window
.
setTimeout
(
function
()
{
window
.
location
.
reload
()
window
.
location
.
reload
()
},
3
00
)
},
10
00
)
}
}
var
success_message
=
'{% trans "Terminate task send, waiting ..." %}'
;
var
the_url
=
"{% url 'api-terminal:tasks-list' %}"
;
var
the_url
=
"{% url 'api-terminal:tasks-list' %}"
;
APIUpdateAttr
({
url
:
the_url
,
method
:
'POST'
,
body
:
JSON
.
stringify
(
data
),
success
:
success
,
success_message
:
'Terminate success'
});
APIUpdateAttr
({
url
:
the_url
,
method
:
'POST'
,
body
:
JSON
.
stringify
(
data
),
success
:
success
,
success_message
:
success_message
});
}
}
$
(
document
).
ready
(
function
()
{
$
(
document
).
ready
(
function
()
{
$
(
'table'
).
DataTable
({
$
(
'table'
).
DataTable
({
...
@@ -118,7 +128,9 @@
...
@@ -118,7 +128,9 @@
"bInfo"
:
false
,
"bInfo"
:
false
,
"order"
:
[]
"order"
:
[]
});
});
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
});
$
(
'#date .input-daterange'
).
datepicker
({
$
(
'#date .input-daterange'
).
datepicker
({
dateFormat
:
'mm/dd/yy'
,
dateFormat
:
'mm/dd/yy'
,
keyboardNavigation
:
false
,
keyboardNavigation
:
false
,
...
@@ -135,12 +147,6 @@
...
@@ -135,12 +147,6 @@
terminal
:
terminal_id
terminal
:
terminal_id
};
};
terminateSession
(
data
)
terminateSession
(
data
)
}).
on
(
'click'
,
'#btn_bulk_update'
,
function
()
{
var
data
=
[];
$
(
'.cbx-term:checked'
).
each
(
function
()
{
data
.
push
({
proxy_log_id
:
$
(
this
).
attr
(
'value'
)})
});
terminateSession
(
data
)
})
})
</script>
</script>
{% endblock %}
{% endblock %}
...
...
apps/terminal/templates/terminal/terminal_list.html
View file @
08e17884
...
@@ -2,7 +2,6 @@
...
@@ -2,7 +2,6 @@
{% load i18n static %}
{% load i18n static %}
{% block custom_head_css_js %}
{% block custom_head_css_js %}
{{ block.super }}
{{ block.super }}
<style>
<style>
div
.dataTables_wrapper
div
.dataTables_filter
,
div
.dataTables_wrapper
div
.dataTables_filter
,
.dataTables_length
{
.dataTables_length
{
...
@@ -15,9 +14,10 @@
...
@@ -15,9 +14,10 @@
#modal
.modal-body
{
max-height
:
200px
;
}
#modal
.modal-body
{
max-height
:
200px
;
}
</style>
</style>
{% endblock %}
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
{% block table_container %}
{#
<div
class=
"uc pull-left m-l-5 m-r-5"
><a
href=
"{% url "
users:user-create
"
%}"
class=
"btn btn-sm btn-primary"
>
{% trans "Create user" %}
</a></div>
#}
<table
class=
"table table-striped table-bordered table-hover "
id=
"terminal_list_table"
>
<table
class=
"table table-striped table-bordered table-hover "
id=
"terminal_list_table"
>
<thead>
<thead>
<tr>
<tr>
...
@@ -40,12 +40,11 @@
...
@@ -40,12 +40,11 @@
</tbody>
</tbody>
</table>
</table>
{% include 'terminal/terminal_modal_accept.html' %}
{% include 'terminal/terminal_modal_accept.html' %}
{% endblock %}
{% endblock %}
{% block custom_foot_js %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/jquery.form.min.js' %}"
></script>
<script
src=
"{% static 'js/jquery.form.min.js' %}"
></script>
<script>
<script>
$
(
document
).
ready
(
function
()
{
function
initTable
()
{
var
options
=
{
var
options
=
{
ele
:
$
(
'#terminal_list_table'
),
ele
:
$
(
'#terminal_list_table'
),
buttons
:
[],
buttons
:
[],
...
@@ -100,8 +99,11 @@ $(document).ready(function(){
...
@@ -100,8 +99,11 @@ $(document).ready(function(){
op_html
:
$
(
'#actions'
).
html
()
op_html
:
$
(
'#actions'
).
html
()
};
};
jumpserver
.
initDataTable
(
options
);
jumpserver
.
initDataTable
(
options
);
}
$
(
document
).
ready
(
function
(){
initTable
();
$
(
'#btn_terminal_accept'
).
click
(
function
()
{
}).
on
(
'click'
,
'#btn-confirm'
,
function
()
{
var
$form
=
$
(
'#form_terminal_accept'
);
var
$form
=
$
(
'#form_terminal_accept'
);
function
success
(
data
,
textStatus
,
jqXHR
)
{
function
success
(
data
,
textStatus
,
jqXHR
)
{
if
(
data
.
success
===
true
)
{
if
(
data
.
success
===
true
)
{
...
@@ -111,8 +113,6 @@ $(document).ready(function(){
...
@@ -111,8 +113,6 @@ $(document).ready(function(){
}
}
}
}
$form
.
ajaxSubmit
({
success
:
success
});
$form
.
ajaxSubmit
({
success
:
success
});
})
}).
on
(
'click'
,
'.btn-del'
,
function
(){
}).
on
(
'click'
,
'.btn-del'
,
function
(){
var
$this
=
$
(
this
);
var
$this
=
$
(
this
);
var
id
=
$this
.
data
(
'id'
);
var
id
=
$this
.
data
(
'id'
);
...
@@ -124,8 +124,7 @@ $(document).ready(function(){
...
@@ -124,8 +124,7 @@ $(document).ready(function(){
var
$this
=
$
(
this
);
var
$this
=
$
(
this
);
var
terminal_id
=
$this
.
data
(
'id'
);
var
terminal_id
=
$this
.
data
(
'id'
);
var
the_url
=
"{% url 'api-terminal:terminal-detail' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
var
the_url
=
"{% url 'api-terminal:terminal-detail' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
var
post_url
=
$
(
'#form_terminal_accept'
).
attr
(
'action'
).
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
var
post_url
=
"{% url 'terminal:terminal-accept' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
console
.
log
(
post_url
);
$
.
ajax
({
$
.
ajax
({
url
:
the_url
,
url
:
the_url
,
method
:
'GET'
,
method
:
'GET'
,
...
...
apps/terminal/templates/terminal/terminal_modal_accept.html
View file @
08e17884
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
{% block modal_body %}
{% block modal_body %}
{% load bootstrap3 %}
{% load bootstrap3 %}
<form
action=
"
{% url 'terminal:terminal-accept' pk="
{{
DEFAULT_PK
}}"
%}
"
method=
"post"
class=
"form-horizontal"
id=
"form_terminal_accept"
enctype=
"multipart/form-data"
>
<form
action=
""
method=
"post"
class=
"form-horizontal"
id=
"form_terminal_accept"
enctype=
"multipart/form-data"
>
{% csrf_token %}
{% csrf_token %}
<p
class=
"alert alert-danger"
id=
"modal-error"
style=
"display: none"
></p>
<p
class=
"alert alert-danger"
id=
"modal-error"
style=
"display: none"
></p>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.name layout="horizontal" %}
...
@@ -16,4 +16,4 @@
...
@@ -16,4 +16,4 @@
</form>
</form>
{% endblock %}
{% endblock %}
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}
{% block modal_confirm_id %}btn-confirm{% endblock %}
\ No newline at end of file
\ No newline at end of file
apps/terminal/urls/api_urls.py
View file @
08e17884
...
@@ -10,8 +10,8 @@ from .. import api
...
@@ -10,8 +10,8 @@ from .. import api
app_name
=
'terminal'
app_name
=
'terminal'
router
=
routers
.
DefaultRouter
()
router
=
routers
.
DefaultRouter
()
router
.
register
(
r'v1/terminal/(?P<terminal>[
0-9]+
)?/?status'
,
api
.
StatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>[
a-zA-Z0-9\-]{36}
)?/?status'
,
api
.
StatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>
[0-9]+
)?/?sessions'
,
api
.
SessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/terminal/(?P<terminal>
a-zA-Z0-9\-]{36}
)?/?sessions'
,
api
.
SessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/tasks'
,
api
.
TaskViewSet
,
'tasks'
)
router
.
register
(
r'v1/tasks'
,
api
.
TaskViewSet
,
'tasks'
)
router
.
register
(
r'v1/terminal'
,
api
.
TerminalViewSet
,
'terminal'
)
router
.
register
(
r'v1/terminal'
,
api
.
TerminalViewSet
,
'terminal'
)
router
.
register
(
r'v1/command'
,
api
.
CommandViewSet
,
'command'
)
router
.
register
(
r'v1/command'
,
api
.
CommandViewSet
,
'command'
)
...
...
apps/terminal/views/command.py
View file @
08e17884
...
@@ -70,7 +70,7 @@ class CommandListView(ListView):
...
@@ -70,7 +70,7 @@ class CommandListView(ListView):
'command'
:
self
.
command
,
'command'
:
self
.
command
,
'date_from'
:
self
.
date_from_s
,
'date_from'
:
self
.
date_from_s
,
'date_to'
:
self
.
date_to_s
,
'date_to'
:
self
.
date_to_s
,
'user'
:
self
.
user
,
'user
name
'
:
self
.
user
,
'asset'
:
self
.
asset
,
'asset'
:
self
.
asset
,
'system_user'
:
self
.
system_user
,
'system_user'
:
self
.
system_user
,
}
}
...
...
apps/terminal/views/session.py
View file @
08e17884
...
@@ -78,7 +78,7 @@ class SessionListView(AdminUserRequiredMixin, ListView):
...
@@ -78,7 +78,7 @@ class SessionListView(AdminUserRequiredMixin, ListView):
'system_user_list'
:
utils
.
get_system_user_list_from_cache
(),
'system_user_list'
:
utils
.
get_system_user_list_from_cache
(),
'date_from'
:
self
.
date_from_s
,
'date_from'
:
self
.
date_from_s
,
'date_to'
:
self
.
date_to_s
,
'date_to'
:
self
.
date_to_s
,
'user'
:
self
.
user
,
'user
name
'
:
self
.
user
,
'asset'
:
self
.
asset
,
'asset'
:
self
.
asset
,
'system_user'
:
self
.
system_user
,
'system_user'
:
self
.
system_user
,
}
}
...
@@ -122,6 +122,7 @@ class SessionOfflineListView(SessionListView):
...
@@ -122,6 +122,7 @@ class SessionOfflineListView(SessionListView):
class
SessionDetailView
(
SingleObjectMixin
,
ListView
):
class
SessionDetailView
(
SingleObjectMixin
,
ListView
):
template_name
=
'terminal/session_detail.html'
template_name
=
'terminal/session_detail.html'
model
=
Session
model
=
Session
object
=
None
def
get_queryset
(
self
):
def
get_queryset
(
self
):
self
.
object
=
self
.
get_object
()
self
.
object
=
self
.
get_object
()
...
...
apps/terminal/views/terminal.py
View file @
08e17884
...
@@ -62,14 +62,14 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
...
@@ -62,14 +62,14 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
class
TerminalDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
class
TerminalDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Terminal
model
=
Terminal
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'terminal:terminal-list'
)
success_url
=
reverse_lazy
(
'terminal:terminal-list'
)
class
TerminalAcceptView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
UpdateView
):
class
TerminalAcceptView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
UpdateView
):
model
=
Terminal
model
=
Terminal
form_class
=
TerminalForm
form_class
=
TerminalForm
template_name
=
'
Terminal/terminal_modal_tes
t.html'
template_name
=
'
terminal/terminal_modal_accep
t.html'
def
form_valid
(
self
,
form
):
def
form_valid
(
self
,
form
):
terminal
=
form
.
save
()
terminal
=
form
.
save
()
...
...
apps/users/models/authentication.py
View file @
08e17884
...
@@ -49,5 +49,4 @@ class LoginLog(models.Model):
...
@@ -49,5 +49,4 @@ class LoginLog(models.Model):
datetime
=
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
_
(
'Date login'
))
datetime
=
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
_
(
'Date login'
))
class
Meta
:
class
Meta
:
db_table
=
'login_log'
ordering
=
[
'-datetime'
,
'username'
]
ordering
=
[
'-datetime'
,
'username'
]
apps/users/models/user.py
View file @
08e17884
...
@@ -38,9 +38,9 @@ class User(AbstractUser):
...
@@ -38,9 +38,9 @@ class User(AbstractUser):
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
))
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
))
enable_otp
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Enable OTP'
))
enable_otp
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Enable OTP'
))
secret_key_otp
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
)
secret_key_otp
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
)
# Todo:
private_key may be not use
d
# Todo:
Auto generate key, let user downloa
d
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
ssh p
rivate key'
))
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
P
rivate key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
ssh p
ublic key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
P
ublic key'
))
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
is_first_login
=
models
.
BooleanField
(
default
=
False
)
is_first_login
=
models
.
BooleanField
(
default
=
False
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
))
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
))
...
...
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