mirror of https://github.com/jumpserver/jumpserver
[Feature] 添加setting页面
parent
b49e3b8f84
commit
121f56f44b
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
from django.db.models.signals import post_save, post_init, m2m_changed, pre_save
|
||||
from django.db.models.signals import post_save, post_init
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.views import Response
|
||||
from django.core.mail import get_connection, send_mail
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .permissions import IsSuperUser
|
||||
from .serializers import MailTestSerializer
|
||||
|
||||
|
||||
class MailTestingAPI(APIView):
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = MailTestSerializer
|
||||
success_message = _("Test mail sent to {}, please check")
|
||||
|
||||
def post(self, request):
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
|
||||
kwargs = {
|
||||
"host": serializer.validated_data["EMAIL_HOST"],
|
||||
"port": serializer.validated_data["EMAIL_PORT"],
|
||||
"username": serializer.validated_data["EMAIL_HOST_USER"],
|
||||
"password": serializer.validated_data["EMAIL_HOST_PASSWORD"],
|
||||
"use_ssl": serializer.validated_data["EMAIL_USE_SSL"],
|
||||
"use_tls": serializer.validated_data["EMAIL_USE_TLS"]
|
||||
}
|
||||
connection = get_connection(timeout=5, **kwargs)
|
||||
|
||||
try:
|
||||
connection.open()
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
try:
|
||||
send_mail("Test", "Test smtp setting", email_host_user,
|
||||
[email_host_user], connection=connection)
|
||||
except Exception as e:
|
||||
return Response({"error": str(e)}, status=401)
|
||||
|
||||
return Response({"msg": self.success_message.format(email_host_user)})
|
|
@ -5,3 +5,9 @@ from django.apps import AppConfig
|
|||
|
||||
class CommonConfig(AppConfig):
|
||||
name = 'common'
|
||||
|
||||
def ready(self):
|
||||
from . import signals_handler
|
||||
from .signals import django_ready
|
||||
django_ready.send(self.__class__)
|
||||
return super().ready()
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import transaction
|
||||
|
||||
from .models import Setting
|
||||
|
||||
|
||||
def to_model_value(value):
|
||||
try:
|
||||
return json.dumps(value)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
def to_form_value(value):
|
||||
try:
|
||||
return json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
return ''
|
||||
|
||||
|
||||
class BaseForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.is_bound:
|
||||
settings = Setting.objects.all()
|
||||
for name, field in self.fields.items():
|
||||
db_value = getattr(settings, name).value
|
||||
if db_value:
|
||||
field.initial = to_form_value(db_value)
|
||||
|
||||
def save(self):
|
||||
if not self.is_bound:
|
||||
raise ValueError("Form is not bound")
|
||||
|
||||
if self.is_valid():
|
||||
with transaction.atomic():
|
||||
for name, value in self.cleaned_data.items():
|
||||
field = self.fields[name]
|
||||
if isinstance(field.widget, forms.PasswordInput) and not value:
|
||||
continue
|
||||
defaults = {
|
||||
'name': name,
|
||||
'value': to_model_value(value)
|
||||
}
|
||||
Setting.objects.update_or_create(defaults=defaults, name=name)
|
||||
else:
|
||||
raise ValueError(self.errors)
|
||||
|
||||
|
||||
class EmailSettingForm(BaseForm):
|
||||
EMAIL_HOST = forms.CharField(
|
||||
max_length=1024, label=_("SMTP host"), initial='smtp.jumpserver.org'
|
||||
)
|
||||
EMAIL_PORT = forms.CharField(max_length=5, label=_("SMTP port"), initial=25)
|
||||
EMAIL_HOST_USER = forms.CharField(
|
||||
max_length=128, label=_("SMTP user"), initial='noreply@jumpserver.org'
|
||||
)
|
||||
EMAIL_HOST_PASSWORD = forms.CharField(
|
||||
max_length=1024, label=_("SMTP password"), widget=forms.PasswordInput,
|
||||
required=False, help_text=_("Some provider use token except password")
|
||||
)
|
||||
EMAIL_USE_SSL = forms.BooleanField(
|
||||
label=_("Use SSL"), initial=False, required=False,
|
||||
help_text=_("If SMTP port is 465, may be select")
|
||||
)
|
||||
EMAIL_USE_TLS = forms.BooleanField(
|
||||
label=_("Use TLS"), initial=False, required=False,
|
||||
help_text=_("If SMTP port is 587, may be select")
|
||||
)
|
|
@ -1,10 +1,10 @@
|
|||
# coding: utf-8
|
||||
|
||||
import inspect
|
||||
from django.db import models
|
||||
from django.http import JsonResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
|
||||
|
||||
class NoDeleteQuerySet(models.query.QuerySet):
|
||||
|
@ -113,4 +113,14 @@ class DatetimeSearchMixin:
|
|||
)
|
||||
else:
|
||||
self.date_to = timezone.now()
|
||||
return super().get(request, *args, **kwargs)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AdminUserRequiredMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
return False
|
||||
elif not self.request.user.is_superuser:
|
||||
self.raise_exception = True
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -1,2 +1,47 @@
|
|||
from django.db import models
|
||||
import json
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class SettingQuerySet(models.QuerySet):
|
||||
def __getattr__(self, item):
|
||||
instances = self.filter(name=item)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
else:
|
||||
return Setting()
|
||||
|
||||
|
||||
class SettingManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return SettingQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class Setting(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
|
||||
value = models.TextField(verbose_name=_("Value"))
|
||||
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
|
||||
comment = models.TextField(verbose_name=_("Comment"))
|
||||
|
||||
objects = SettingManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def refresh_all_settings(cls):
|
||||
settings_list = cls.objects.all()
|
||||
for setting in settings_list:
|
||||
setting.refresh_setting()
|
||||
|
||||
def refresh_setting(self):
|
||||
try:
|
||||
value = json.loads(self.value)
|
||||
except json.JSONDecodeError:
|
||||
return
|
||||
setattr(settings, self.name, value)
|
||||
|
||||
class Meta:
|
||||
db_table = "settings"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
|
||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
||||
"""Allows access to valid user, is active and not expired"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsValidUser, self).has_permission(request, view) \
|
||||
and request.user.is_valid
|
||||
|
||||
|
||||
class IsAppUser(IsValidUser):
|
||||
"""Allows access only to app user """
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsAppUser, self).has_permission(request, view) \
|
||||
and request.user.is_app
|
||||
|
||||
|
||||
class IsSuperUser(IsValidUser):
|
||||
"""Allows access only to superuser"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUser, self).has_permission(request, view) \
|
||||
and request.user.is_superuser
|
||||
|
||||
|
||||
class IsSuperUserOrAppUser(IsValidUser):
|
||||
"""Allows access between superuser and app user"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUserOrAppUser, self).has_permission(request, view) \
|
||||
and (request.user.is_superuser or request.user.is_app)
|
||||
|
||||
|
||||
class IsSuperUserOrAppUserOrUserReadonly(IsSuperUserOrAppUser):
|
||||
def has_permission(self, request, view):
|
||||
if IsValidUser.has_permission(self, request, view) \
|
||||
and request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
else:
|
||||
return IsSuperUserOrAppUser.has_permission(self, request, view)
|
||||
|
||||
|
||||
class IsCurrentUserOrReadOnly(permissions.BasePermission):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return obj == request.user
|
|
@ -0,0 +1,10 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class MailTestSerializer(serializers.Serializer):
|
||||
EMAIL_HOST = serializers.CharField(max_length=1024, required=True)
|
||||
EMAIL_PORT = serializers.IntegerField(default=25)
|
||||
EMAIL_HOST_USER = serializers.CharField(max_length=1024)
|
||||
EMAIL_HOST_PASSWORD = serializers.CharField()
|
||||
EMAIL_USE_SSL = serializers.BooleanField(default=False)
|
||||
EMAIL_USE_TLS = serializers.BooleanField(default=False)
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
django_ready = Signal()
|
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from .models import Setting
|
||||
from .utils import get_logger
|
||||
from .signals import django_ready
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Setting, dispatch_uid="my_unique_identifier")
|
||||
def refresh_settings_on_changed(sender, instance=None, **kwargs):
|
||||
logger.debug("Receive setting item change")
|
||||
logger.debug(" - refresh setting: {}".format(instance.name))
|
||||
if instance:
|
||||
instance.refresh_setting()
|
||||
|
||||
|
||||
@receiver(django_ready, dispatch_uid="my_unique_identifier")
|
||||
def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||
logger.debug("Receive django ready signal")
|
||||
logger.debug(" - fresh all settings")
|
||||
Setting.refresh_all_settings()
|
|
@ -0,0 +1,73 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% 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="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
||||
</li>
|
||||
<li class="active">
|
||||
<a class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-12" style="padding-left:0">
|
||||
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% if not field.field|is_bool_field %}
|
||||
{% bootstrap_field field layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-1">
|
||||
{{ field }}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<span class="help-block" >{{ field.help_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -4,6 +4,7 @@ from django import template
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.html import escape
|
||||
from django import forms
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -83,3 +84,11 @@ def time_util_with_seconds(date_from, date_to):
|
|||
return '{} h'.format(seconds//3600)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_bool_field(field):
|
||||
if isinstance(field, forms.BooleanField):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'common'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/mail/testing/$', api.MailTestingAPI.as_view(), name='mail-testing'),
|
||||
]
|
|
@ -0,0 +1,11 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .. import views
|
||||
|
||||
app_name = 'common'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||
]
|
|
@ -1,2 +1,33 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from django.views.generic import View
|
||||
from django.shortcuts import render
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .forms import EmailSettingForm
|
||||
from .mixins import AdminUserRequiredMixin
|
||||
|
||||
|
||||
class EmailSettingView(AdminUserRequiredMixin, View):
|
||||
form_class = EmailSettingForm
|
||||
template_name = "common/email_setting.html"
|
||||
|
||||
def get(self, request):
|
||||
context = {
|
||||
'app': 'settings',
|
||||
'action': 'Email setting',
|
||||
"form": EmailSettingForm(),
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
def post(self, request):
|
||||
form = self.form_class(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("Update email setting successfully"))
|
||||
|
||||
context = {
|
||||
'app': 'settings',
|
||||
'action': 'Email setting',
|
||||
"form": EmailSettingForm(),
|
||||
}
|
||||
return render(request, self.template_name, context)
|
||||
|
|
|
@ -121,15 +121,6 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
|||
# Database
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
# if CONFIG.DB_ENGINE == 'sqlite':
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||
# 'NAME': CONFIG.DB_NAME or os.path.join(BASE_DIR, 'data', 'db.sqlite3'),
|
||||
# 'ATOMIC_REQUESTS': True,
|
||||
# }
|
||||
# }
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.{}'.format(CONFIG.DB_ENGINE),
|
||||
|
|
|
@ -19,6 +19,8 @@ urlpatterns = [
|
|||
url(r'^perms/', include('perms.urls.views_urls', namespace='perms')),
|
||||
url(r'^terminal/', include('terminal.urls.views_urls', namespace='terminal')),
|
||||
url(r'^ops/', include('ops.urls.view_urls', namespace='ops')),
|
||||
url(r'^settings/', include('common.urls.view_urls', namespace='settings')),
|
||||
url(r'^common/', include('common.urls.view_urls', namespace='common')),
|
||||
|
||||
# Api url view map
|
||||
url(r'^api/users/', include('users.urls.api_urls', namespace='api-users')),
|
||||
|
@ -26,13 +28,12 @@ urlpatterns = [
|
|||
url(r'^api/perms/', include('perms.urls.api_urls', namespace='api-perms')),
|
||||
url(r'^api/terminal/', include('terminal.urls.api_urls', namespace='api-terminal')),
|
||||
url(r'^api/ops/', include('ops.urls.api_urls', namespace='api-ops')),
|
||||
url(r'^api/common/', include('common.urls.api_urls', namespace='api-common')),
|
||||
|
||||
# External apps url
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
|
||||
]
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
url(r'^docs/', schema_view, name="docs"),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .group import *
|
||||
from .user import *
|
||||
from .group import *
|
||||
from .authentication import *
|
||||
from .utils import *
|
||||
|
|
|
@ -20,12 +20,6 @@ class UserGroup(NoDeleteModelMixin):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.name != 'Default':
|
||||
self.users.clear()
|
||||
return super(UserGroup, self).delete()
|
||||
return True
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils import timezone
|
||||
from django.shortcuts import reverse
|
||||
|
||||
from .group import UserGroup
|
||||
from common.utils import get_signer, date_expired_default
|
||||
|
||||
|
||||
|
@ -35,7 +34,7 @@ class User(AbstractUser):
|
|||
username = models.CharField(max_length=128, unique=True, verbose_name=_('Username'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
email = models.EmailField(max_length=128, unique=True, verbose_name=_('Email'))
|
||||
groups = models.ManyToManyField(UserGroup, related_name='users', blank=True, verbose_name=_('User group'))
|
||||
groups = models.ManyToManyField('users.UserGroup', related_name='users', blank=True, verbose_name=_('User group'))
|
||||
role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
|
||||
avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar'))
|
||||
wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
|
||||
|
|
Loading…
Reference in New Issue