diff --git a/apps/assets/models.py b/apps/assets/models.py index 2ac4aabe8..1b13b8621 100644 --- a/apps/assets/models.py +++ b/apps/assets/models.py @@ -315,9 +315,8 @@ class Asset(models.Model): admin_user = models.ForeignKey(AdminUser, null=True, blank=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_("Admin user")) system_users = models.ManyToManyField(SystemUser, blank=True, related_name='assets', verbose_name=_("System User")) - idc = models.ForeignKey(IDC, null=True, related_name='assets', + idc = models.ForeignKey(IDC, blank=True, null=True, related_name='assets', on_delete=models.SET_NULL, verbose_name=_('IDC'),) - # default=get_default_idc) mac_address = models.CharField(max_length=20, null=True, blank=True, verbose_name=_("Mac address")) brand = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Brand')) cpu = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU')) diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index e49540738..1a4aa38d5 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -30,12 +30,11 @@
- +
- - + {% endblock %} {% block custom_foot_js %} @@ -44,10 +43,9 @@ $('.select2').select2(); $("#id_tags").select2({ tags: true, - maximumSelectionLength: 8, //最多能够选择的个数 + maximumSelectionLength: 8 //最多能够选择的个数 //closeOnSelect: false }); }) - {% endblock %} \ No newline at end of file diff --git a/apps/assets/views.py b/apps/assets/views.py index 612379602..f1c1d85bb 100644 --- a/apps/assets/views.py +++ b/apps/assets/views.py @@ -44,7 +44,7 @@ class AssetListView(AdminUserRequiredMixin, ListView): return super(AssetListView, self).get_context_data(**kwargs) -class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView): +class AssetCreateView(AdminUserRequiredMixin, CreateAssetTagsMiXin, CreateView): model = Asset tag_type = 'asset' form_class = AssetCreateForm @@ -58,7 +58,8 @@ class AssetCreateView(AdminUserRequiredMixin,CreateAssetTagsMiXin,CreateView): return super(AssetCreateView, self).form_valid(form) def form_invalid(self, form): - print(form.errors) + if form.errors.get('__all__'): + form.errors['all'] = form.errors.get('__all__') return super(AssetCreateView, self).form_invalid(form) def get_context_data(self, **kwargs): diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index b68a30613..0191d8a9c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -1610,7 +1610,7 @@ msgid "" "here reset password\n" "
\n" " This link is valid for 1 hour. After it expires, request new one<\n" +"(forget_password_url)s?email=%(email)s\">request new one\n" "\n" "
\n" " ---\n" diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index c49dc96d3..6f1fff39c 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -189,9 +189,9 @@ function activeNav() { function APIUpdateAttr(props) { // props = {url: .., body: , success: , error: , method: ,} props = props || {}; - success_message = props.success_message || 'Update Successfully!'; - fail_message = props.fail_message || 'Error occurred while updating.'; - + var success_message = props.success_message || 'Update Successfully!'; + var fail_message = props.fail_message || 'Error occurred while updating.'; + console.log(props.body); $.ajax({ url: props.url, type: props.method || "PATCH", diff --git a/apps/templates/_base_create_update.html b/apps/templates/_base_create_update.html index 2e1a981f8..c011feb71 100644 --- a/apps/templates/_base_create_update.html +++ b/apps/templates/_base_create_update.html @@ -28,10 +28,17 @@
- {% block form %} {% endblock %} + {% if form.errors.all %} +
+ {{ form.errors.all }} +
+ {% endif %} + {% block form %} + {% endblock %}
{% endblock %} + diff --git a/apps/users/api.py b/apps/users/api.py index 611bcbc0a..8c945ca53 100644 --- a/apps/users/api.py +++ b/apps/users/api.py @@ -6,120 +6,119 @@ import base64 from django.shortcuts import get_object_or_404 from django.core.cache import cache from django.conf import settings -from rest_framework import generics, status +from rest_framework import generics, status, viewsets from rest_framework.response import Response from rest_framework.views import APIView -from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView +from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView, BulkModelViewSet from rest_framework import authentication from common.mixins import BulkDeleteApiMixin from common.utils import get_logger from .utils import check_user_valid, token_gen from .models import User, UserGroup -from .serializers import UserDetailSerializer, UserAndGroupSerializer, \ - GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer +from . import serializers from .backends import IsSuperUser, IsTerminalUser, IsValidUser, IsSuperUserOrTerminalUser logger = get_logger(__name__) -class UserDetailApi(generics.RetrieveUpdateDestroyAPIView): +class UserViewSet(BulkModelViewSet): queryset = User.objects.all() - serializer_class = UserDetailSerializer + serializer_class = serializers.UserSerializer permission_classes = (IsSuperUser,) -class UserAndGroupEditApi(generics.RetrieveUpdateAPIView): - queryset = User.objects.all() - serializer_class = UserAndGroupSerializer - permission_classes = (IsSuperUser,) +# class UserAndGroupEditApi(generics.RetrieveUpdateAPIView): +# queryset = User.objects.all() +# serializer_class = serializers.UserAndGroupSerializer +# permission_classes = (IsSuperUser,) class UserResetPasswordApi(generics.UpdateAPIView): queryset = User.objects.all() - serializer_class = UserDetailSerializer + serializer_class = serializers.UserSerializer def perform_update(self, serializer): # Note: we are not updating the user object here. # We just do the reset-password staff. - user = self.get_object() import uuid + from .utils import send_reset_password_mail + user = self.get_object() user.password_raw = str(uuid.uuid4()) user.save() - from .utils import send_reset_password_mail send_reset_password_mail(user) -class UserResetPKApi(generics.UpdateAPIView): +class UserResetPubKeyApi(generics.UpdateAPIView): queryset = User.objects.all() - serializer_class = UserDetailSerializer + serializer_class = serializers.UserSerializer def perform_update(self, serializer): + from .utils import send_reset_ssh_key_mail user = self.get_object() user.is_public_key_valid = False user.save() - from .utils import send_reset_ssh_key_mail send_reset_ssh_key_mail(user) - -class UserUpdatePKApi(generics.UpdateAPIView): - queryset = User.objects.all() - serializer_class = UserPKUpdateSerializer - - def perform_update(self, serializer): - user = self.get_object() - user.private_key = serializer.validated_data['_public_key'] - user.save() +# +# class UserUpdatePKApi(generics.UpdateAPIView): +# queryset = User.objects.all() +# serializer_class = serializers.UserPKUpdateSerializer +# +# def perform_update(self, serializer): +# user = self.get_object() +# user.private_key = serializer.validated_data['_public_key'] +# user.save() +# +# +# class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView): +# queryset = UserGroup.objects.all() +# serializer_class = serializers.GroupDetailSerializer +# +# def perform_update(self, serializer): +# users = serializer.validated_data.get('users') +# if users: +# group = self.get_object() +# Note: use `list` method to force hitting the db. + # group_users = list(group.users.all()) + # serializer.save() + # group.users.set(users + group_users) + # group.save() + # return + # serializer.save() +# +# +# class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): +# queryset = User.objects.all() +# serializer_class = serializers.UserBulkUpdateSerializer +# permission_classes = (IsSuperUserOrTerminalUser,) +# +# def get(self, request, *args, **kwargs): +# return super(UserListUpdateApi, self).get(request, *args, **kwargs) +# +# +# class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): +# queryset = UserGroup.objects.all() +# serializer_class = serializers.GroupBulkUpdateSerializer -class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView): - queryset = UserGroup.objects.all() - serializer_class = GroupDetailSerializer - - def perform_update(self, serializer): - users = serializer.validated_data.get('users') - if users: - group = self.get_object() - # Note: use `list` method to force hitting the db. - group_users = list(group.users.all()) - serializer.save() - group.users.set(users + group_users) - group.save() - return - serializer.save() - - -class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): - queryset = User.objects.all() - serializer_class = UserBulkUpdateSerializer - permission_classes = (IsSuperUserOrTerminalUser,) - - # def get(self, request, *args, **kwargs): - # return super(UserListUpdateApi, self).get(request, *args, **kwargs) - - -class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView): - queryset = UserGroup.objects.all() - serializer_class = GroupBulkUpdateSerializer - - -class DeleteUserFromGroupApi(generics.DestroyAPIView): - queryset = UserGroup.objects.all() - serializer_class = GroupDetailSerializer - - def destroy(self, request, *args, **kwargs): - group = self.get_object() - self.perform_destroy(group, **kwargs) - return Response(status=status.HTTP_204_NO_CONTENT) - - def perform_destroy(self, instance, **kwargs): - user_id = kwargs.get('uid') - user = get_object_or_404(User, id=user_id) - instance.users.remove(user) - - -class UserTokenApi(APIView): +# class DeleteUserFromGroupApi(generics.DestroyAPIView): +# queryset = UserGroup.objects.all() +# serializer_class = serializers.GroupDetailSerializer +# +# def destroy(self, request, *args, **kwargs): +# group = self.get_object() +# self.perform_destroy(group, **kwargs) +# return Response(status=status.HTTP_204_NO_CONTENT) +# +# def perform_destroy(self, instance, **kwargs): +# user_id = kwargs.get('uid') +# user = get_object_or_404(User, id=user_id) +# instance.users.remove(user) +# +# +class UserAuthApi(APIView): permission_classes = () expiration = settings.CONFIG.TOKEN_EXPIRATION or 3600 @@ -128,9 +127,9 @@ class UserTokenApi(APIView): password = request.data.get('password', '') public_key = request.data.get('public_key', '') remote_addr = request.META.get('REMOTE_ADDR', '') - remote_addr = base64.b64encode(remote_addr).replace('=', '') user = check_user_valid(username=username, password=password, public_key=public_key) + if user: token = cache.get('%s_%s' % (user.id, remote_addr)) if not token: diff --git a/apps/users/models.py b/apps/users/models.py index 531c8f402..934aaea61 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.conf import settings +from django.contrib.auth import logout from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser from django.core import signing diff --git a/apps/users/serializers.py b/apps/users/serializers.py index 64aab820a..0d02f85d5 100644 --- a/apps/users/serializers.py +++ b/apps/users/serializers.py @@ -9,10 +9,35 @@ from common.utils import signer from .models import User, UserGroup -class UserDetailSerializer(serializers.ModelSerializer): +# class UserDetailSerializer(BulkSerializerMixin, serializers.ModelSerializer): +# class Meta: +# model = User +# fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name'] + + +class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): + group_display = serializers.SerializerMethodField() + active_display = serializers.SerializerMethodField() + groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) + class Meta: model = User - fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name'] + list_serializer_class = BulkListSerializer + exclude = ['first_name', 'last_name', 'password', '_private_key', '_public_key'] + + def get_field_names(self, declared_fields, info): + fields = super(UserSerializer, self).get_field_names(declared_fields, info) + fields.extend(['group_display', 'get_role_display']) + return fields + + @staticmethod + def get_group_display(obj): + return " ".join([group.name for group in obj.groups.all()]) + + @staticmethod + def get_active_display(obj): + # TODO: user active state + return not (obj.is_expired and obj.is_active) class UserPKUpdateSerializer(serializers.ModelSerializer): @@ -44,43 +69,43 @@ class UserAndGroupSerializer(serializers.ModelSerializer): fields = ['id', 'groups'] -class GroupDetailSerializer(serializers.ModelSerializer): - class Meta: - model = UserGroup - fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users'] +# class GroupDetailSerializer(serializers.ModelSerializer): +# class Meta: +# model = UserGroup +# fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users'] -class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): - group_display = serializers.SerializerMethodField() - active_display = serializers.SerializerMethodField() - groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) - - class Meta(object): - model = User - list_serializer_class = BulkListSerializer - fields = ['id', 'is_active', 'username', 'name', 'email', 'role', 'avatar', - 'enable_otp', 'comment', 'groups', 'get_role_display', - 'group_display', 'active_display'] - - @staticmethod - def get_group_display(obj): - return " ".join([group.name for group in obj.groups.all()]) - - @staticmethod - def get_active_display(obj): - # TODO: user active state - return not (obj.is_expired and obj.is_active) - - -class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): - user_amount = serializers.SerializerMethodField() - - class Meta: - model = UserGroup - list_serializer_class = BulkListSerializer - fields = ['id', 'name', 'comment', 'user_amount'] - - @staticmethod - def get_user_amount(obj): - return obj.users.count() - +# class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): +# group_display = serializers.SerializerMethodField() +# active_display = serializers.SerializerMethodField() +# groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) +# +# class Meta(object): +# model = User +# list_serializer_class = BulkListSerializer +# fields = ['id', 'is_active', 'username', 'name', 'email', 'role', 'avatar', +# 'enable_otp', 'comment', 'groups', 'get_role_display', +# 'group_display', 'active_display'] +# +# @staticmethod +# def get_group_display(obj): +# return " ".join([group.name for group in obj.groups.all()]) +# +# @staticmethod +# def get_active_display(obj): +# TODO: user active state + # return not (obj.is_expired and obj.is_active) +# +# +# class GroupBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer): +# user_amount = serializers.SerializerMethodField() +# +# class Meta: +# model = UserGroup +# list_serializer_class = BulkListSerializer +# fields = ['id', 'name', 'comment', 'user_amount'] +# +# @staticmethod +# def get_user_amount(obj): +# return obj.users.count() +# diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index 54c69a9d3..dd9f8232b 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -1,82 +1,52 @@ -{% extends 'base.html' %} +{% extends '_base_create_update.html' %} {% load i18n %} {% load static %} {% load bootstrap %} -{% block custom_head_css_js %} - - - -{% endblock %} +{% block form %} +
+ {% csrf_token %} +

{% trans 'Account' %}

+ {% block username %} {% endblock %} + {{ form.name|bootstrap_horizontal }} + {{ form.email|bootstrap_horizontal }} + {{ form.groups|bootstrap_horizontal }} -{% block content %} -
-
-
-
-
-
{% block user_template_title %}{% trans 'Create user' %}{% endblock %}
- -
-
- - {% csrf_token %} -

{% trans 'Account' %}

- {% block username %} {% endblock %} - {{ form.name|bootstrap_horizontal }} - {{ form.email|bootstrap_horizontal }} - {{ form.groups|bootstrap_horizontal }} +
+ {% block password %} {% endblock %} -
- {% block password %} {% endblock %} - -
-

{% trans 'Security and Role' %}

- {{ form.role|bootstrap_horizontal }} -
- -
-
- - -
- {{ form.date_expired.errors }} -
-
-{# {{ form.date_expired|bootstrap_horizontal }}#} -
- -
- {{ form.enable_otp }} -
-
-
-

{% trans 'Profile' %}

- {{ form.phone|bootstrap_horizontal }} - {{ form.wechat|bootstrap_horizontal }} - {{ form.comment|bootstrap_horizontal }} -
-
-
- - -
-
- -
-
-
-
-
+
+

{% trans 'Security and Role' %}

+ {{ form.role|bootstrap_horizontal }} +
+ +
+
+ + +
+ {{ form.date_expired.errors }} +
+
+ {{ form.date_expired|bootstrap_horizontal }}#} +
+ +
+ {{ form.enable_otp }} +
+
+
+

{% trans 'Profile' %}

+ {{ form.phone|bootstrap_horizontal }} + {{ form.wechat|bootstrap_horizontal }} + {{ form.comment|bootstrap_horizontal }} +
+
+
+ + +
+
+ {% endblock %} {% block custom_foot_js %} diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index b87e17a39..768746b08 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -18,20 +18,23 @@
- {{ user_object.name }} + {{ user.name }}
@@ -51,56 +54,56 @@ - + {% trans 'Name' %}: - {{ user_object.name }} + {{ user.name }} {% trans 'Username' %}: - {{ user_object.username }} + {{ user.username }} {% trans 'Email' %}: - {{ user_object.email }} + {{ user.email }} - {% if user_object.phone %} + {% if user.phone %} {% trans 'Phone' %}: - {{ user_object.phone }} + {{ user.phone }} {% endif %} - {% if user_object.wechat %} + {% if user.wechat %} {% trans 'Wechat' %}: - {{ user_object.wechat }} + {{ user.wechat }} {% endif %} {% trans 'Role' %}: - {{ user_object.get_role_display }} + {{ user.get_role_display }} {% trans 'Date expired' %}: - {{ user_object.date_expired|date:"Y-m-j H:i:s" }} + {{ user.date_expired|date:"Y-m-j H:i:s" }} {% trans 'Created by' %}: - {{ user_object.created_by }} + {{ user.created_by }} {% trans 'Date joined' %}: - {{ user_object.date_joined|date:"Y-m-j H:i:s" }} + {{ user.date_joined|date:"Y-m-j H:i:s" }} {% trans 'Last login' %}: - {{ user_object.last_login|date:"Y-m-j H:i:s" }} + {{ user.last_login|date:"Y-m-j H:i:s" }} {% trans 'Comment' %}: - {{ user_object.comment }} + {{ user.comment }} @@ -120,7 +123,7 @@
- +
- Click here reset password
- This link is valid for 1 hour. After it expires, request new one< + This link is valid for 1 hour. After it expires, request new one
--- diff --git a/apps/users/views.py b/apps/users/views.py index 6144ceace..7b472584e 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -64,7 +64,7 @@ class UserLoginView(FormView): @method_decorator(never_cache, name='dispatch') class UserLogoutView(TemplateView): - template_name = 'common/flash_message_standalone.html' + template_name = 'flash_message_standalone.html' def get(self, request, *args, **kwargs): auth_logout(request) @@ -142,7 +142,7 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView): class UserDetailView(AdminUserRequiredMixin, DetailView): model = User template_name = 'users/user_detail.html' - context_object_name = "user_object" + context_object_name = "user" def get_context_data(self, **kwargs): groups = UserGroup.objects.exclude(id__in=self.object.groups.all()) @@ -239,7 +239,7 @@ class UserForgotPasswordView(TemplateView): class UserForgotPasswordSendmailSuccessView(TemplateView): - template_name = 'common/flash_message_standalone.html' + template_name = 'flash_message_standalone.html' def get_context_data(self, **kwargs): context = { @@ -252,7 +252,7 @@ class UserForgotPasswordSendmailSuccessView(TemplateView): class UserResetPasswordSuccessView(TemplateView): - template_name = 'common/flash_message_standalone.html' + template_name = 'flash_message_standalone.html' def get_context_data(self, **kwargs): context = {