i;i++)r.push({name:a,value:n[i]});else null!==n&&"undefined"!=typeof n&&r.push({name:this.name,value:n})}}),e.param(r)},e.fn.fieldValue=function(t){for(var r=[],a=0,n=this.length;n>a;a++){var i=this[a],o=e.fieldValue(i,t);null===o||"undefined"==typeof o||o.constructor==Array&&!o.length||(o.constructor==Array?e.merge(r,o):r.push(o))}return r},e.fieldValue=function(t,r){var a=t.name,n=t.type,i=t.tagName.toLowerCase();if(void 0===r&&(r=!0),r&&(!a||t.disabled||"reset"==n||"button"==n||("checkbox"==n||"radio"==n)&&!t.checked||("submit"==n||"image"==n)&&t.form&&t.form.clk!=t||"select"==i&&-1==t.selectedIndex))return null;if("select"==i){var o=t.selectedIndex;if(0>o)return null;for(var s=[],u=t.options,c="select-one"==n,l=c?o+1:u.length,f=c?o:0;l>f;f++){var m=u[f];if(m.selected){var d=m.value;if(d||(d=m.attributes&&m.attributes.value&&!m.attributes.value.specified?m.text:m.value),c)return d;s.push(d)}}return s}return e(t).val()},e.fn.clearForm=function(t){return this.each(function(){e("input,select,textarea",this).clearFields(t)})},e.fn.clearFields=e.fn.clearInputs=function(t){var r=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var a=this.type,n=this.tagName.toLowerCase();r.test(a)||"textarea"==n?this.value="":"checkbox"==a||"radio"==a?this.checked=!1:"select"==n?this.selectedIndex=-1:"file"==a?/MSIE/.test(navigator.userAgent)?e(this).replaceWith(e(this).clone(!0)):e(this).val(""):t&&(t===!0&&/hidden/.test(a)||"string"==typeof t&&e(this).is(t))&&(this.value="")})},e.fn.resetForm=function(){return this.each(function(){("function"==typeof this.reset||"object"==typeof this.reset&&!this.reset.nodeType)&&this.reset()})},e.fn.enable=function(e){return void 0===e&&(e=!0),this.each(function(){this.disabled=!e})},e.fn.selected=function(t){return void 0===t&&(t=!0),this.each(function(){var r=this.type;if("checkbox"==r||"radio"==r)this.checked=t;else if("option"==this.tagName.toLowerCase()){var a=e(this).parent("select");t&&a[0]&&"select-one"==a[0].type&&a.find("option").selected(!1),this.selected=t}})},e.fn.ajaxSubmit.debug=!1});
\ No newline at end of file
diff --git a/apps/users/api.py b/apps/users/api.py
index 361f532a4..9de263963 100644
--- a/apps/users/api.py
+++ b/apps/users/api.py
@@ -9,51 +9,28 @@ from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
-from .serializers import UserSerializer, UserGroupSerializer, UserAttributeSerializer, GroupUserEditSerializer, \
- GroupEditSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer
from .models import User, UserGroup
+from .serializers import UserDetailSerializer, UserAndGroupSerializer, \
+ GroupDetailSerializer, UserPKUpdateSerializer, UserBulkUpdateSerializer, GroupBulkUpdateSerializer
+from common.mixins import BulkDeleteApiMixin
logger = logging.getLogger('jumpserver.users.api')
-class UserListAddApi(generics.ListCreateAPIView):
+class UserDetailApi(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
- serializer_class = UserSerializer
+ serializer_class = UserDetailSerializer
-class UserDetailDeleteUpdateApi(generics.RetrieveUpdateDestroyAPIView):
+class UserAndGroupEditApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
- serializer_class = UserSerializer
-
- def delete(self, request, *args, **kwargs):
- print(self.request.data)
- return super(UserDetailDeleteUpdateApi, self).delete(request, *args, **kwargs)
-
-
-class UserGroupListAddApi(generics.ListCreateAPIView):
- queryset = UserGroup.objects.all()
- serializer_class = UserGroupSerializer
-
-
-class UserGroupDetailDeleteUpdateApi(generics.RetrieveUpdateDestroyAPIView):
- queryset = UserGroup.objects.all()
- serializer_class = UserGroupSerializer
-
-
-class UserAttributeApi(generics.RetrieveUpdateDestroyAPIView):
- queryset = User.objects.all()
- serializer_class = UserAttributeSerializer
-
-
-class GroupUserEditApi(generics.RetrieveUpdateAPIView):
- queryset = User.objects.all()
- serializer_class = GroupUserEditSerializer
+ serializer_class = UserAndGroupSerializer
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
- serializer_class = GroupUserEditSerializer
+ serializer_class = UserDetailSerializer
def perform_update(self, serializer):
# Note: we are not updating the user object here.
@@ -68,7 +45,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
class UserResetPKApi(generics.UpdateAPIView):
queryset = User.objects.all()
- serializer_class = GroupUserEditSerializer
+ serializer_class = UserDetailSerializer
def perform_update(self, serializer):
user = self.get_object()
@@ -88,9 +65,9 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save()
-class GroupEditApi(generics.RetrieveUpdateDestroyAPIView):
+class GroupDetailApi(generics.RetrieveUpdateDestroyAPIView):
queryset = UserGroup.objects.all()
- serializer_class = GroupEditSerializer
+ serializer_class = GroupDetailSerializer
def perform_update(self, serializer):
users = serializer.validated_data.get('users')
@@ -105,27 +82,19 @@ class GroupEditApi(generics.RetrieveUpdateDestroyAPIView):
serializer.save()
-class UserBulkUpdateApi(ListBulkCreateUpdateDestroyAPIView):
+class UserListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserBulkUpdateSerializer
- def filter_queryset(self, queryset):
- id_list = self.request.query_params.get('id__in')
- if id_list:
- import json
- try:
- ids = json.loads(id_list)
- except Exception as e:
- logger.error(str(e))
- return queryset
- if isinstance(ids, list):
- queryset = queryset.filter(id__in=ids)
- return queryset
+
+class GroupListUpdateApi(BulkDeleteApiMixin, ListBulkCreateUpdateDestroyAPIView):
+ queryset = UserGroup.objects.all()
+ serializer_class = GroupBulkUpdateSerializer
class DeleteUserFromGroupApi(generics.DestroyAPIView):
queryset = UserGroup.objects.all()
- serializer_class = GroupEditSerializer
+ serializer_class = GroupDetailSerializer
def destroy(self, request, *args, **kwargs):
group = self.get_object()
diff --git a/apps/users/forms.py b/apps/users/forms.py
index 8c21011ce..efe98e67b 100644
--- a/apps/users/forms.py
+++ b/apps/users/forms.py
@@ -34,6 +34,13 @@ class UserCreateForm(forms.ModelForm):
}
+class UserBulkImportForm(forms.ModelForm):
+
+ class Meta:
+ model = User
+ fields = ['username', 'email', 'enable_otp', 'role']
+
+
class UserUpdateForm(forms.ModelForm):
class Meta:
diff --git a/apps/users/models.py b/apps/users/models.py
index 229b70ed8..ebe45afaa 100644
--- a/apps/users/models.py
+++ b/apps/users/models.py
@@ -112,6 +112,12 @@ class User(AbstractUser):
else:
return True
+ @property
+ def is_valid(self):
+ if self.is_active and not self.is_expired:
+ return True
+ return False
+
@property
def private_key(self):
return decrypt(self._private_key)
diff --git a/apps/users/serializers.py b/apps/users/serializers.py
index 366e62fec..cfce66ab7 100644
--- a/apps/users/serializers.py
+++ b/apps/users/serializers.py
@@ -8,47 +8,13 @@ from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
from .models import User, UserGroup
-class UserSerializer(serializers.ModelSerializer):
- groups = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='users:user-group-detail-api')
-
- class Meta:
- model = User
- exclude = [
- 'password', 'first_name', 'last_name', 'secret_key_otp',
- 'private_key', 'public_key', 'avatar',
- ]
-
-
-class UserGroupSerializer(serializers.ModelSerializer):
- users = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='users:user-detail-api')
-
- class Meta:
- model = UserGroup
- fields = '__all__'
-
-
-class GroupEditSerializer(serializers.ModelSerializer):
-
- class Meta:
- model = UserGroup
- fields = ['id', 'name', 'comment', 'date_created', 'created_by', 'users']
-
-
-class UserAttributeSerializer(serializers.ModelSerializer):
+class UserDetailSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['avatar', 'wechat', 'phone', 'enable_otp', 'comment', 'is_active', 'name']
-class GroupUserEditSerializer(serializers.ModelSerializer):
- groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
-
- class Meta:
- model = User
- fields = ['id', 'groups']
-
-
class UserPKUpdateSerializer(serializers.ModelSerializer):
class Meta:
@@ -70,6 +36,21 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
return value
+class UserAndGroupSerializer(serializers.ModelSerializer):
+ groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all())
+
+ class Meta:
+ model = User
+ fields = ['id', 'groups']
+
+
+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()
@@ -88,3 +69,16 @@ class UserBulkUpdateSerializer(BulkSerializerMixin, serializers.ModelSerializer)
def get_active_display(self, obj):
# TODO: user ative 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']
+
+ def get_user_amount(self, obj):
+ return obj.users.count()
diff --git a/apps/users/templates/users/_user_import_modal.html b/apps/users/templates/users/_user_import_modal.html
new file mode 100644
index 000000000..99a61d126
--- /dev/null
+++ b/apps/users/templates/users/_user_import_modal.html
@@ -0,0 +1,19 @@
+{% extends '_modal.html' %}
+{% load i18n %}
+{% block modal_id %}user_import_modal{% endblock %}
+{% block modal_title%}{% trans "Import User" %}{% endblock %}
+{% block modal_body %}
+{% trans "Hint: your excel should organized in the following format." %}
+{% trans "* You should have a very worksheet named `users`." %}
+{% trans "* Rows in this worksheet: username, email, enable_opt(0, 1), role(one of ['Admin', 'User'])" %}
+
+{% endblock %}
+{% block modal_confirm_id %}btn_user_import{% endblock %}
diff --git a/apps/users/templates/users/user_group_detail.html b/apps/users/templates/users/user_group_detail.html
index 4f380542f..3e00d0055 100644
--- a/apps/users/templates/users/user_group_detail.html
+++ b/apps/users/templates/users/user_group_detail.html
@@ -218,7 +218,7 @@ $(document).on('click', '.btn_remove', function(){
users: plain_id_list.map(Number)
};
$('#select_user_modal').modal('hide');
- var the_url = "{% url 'users:user-group-edit-api' pk=object.id %}";
+ var the_url = "{% url 'users:user-group-detail-api' pk=object.id %}";
var success = function() {
toastr.success('{% trans "The selected users has been added to current group." %}');
var html = "";
diff --git a/apps/users/templates/users/user_group_list.html b/apps/users/templates/users/user_group_list.html
index 6eee65f21..0463c3305 100644
--- a/apps/users/templates/users/user_group_list.html
+++ b/apps/users/templates/users/user_group_list.html
@@ -1,71 +1,85 @@
{% extends '_base_list.html' %}
{% load i18n static %}
-{% load common_tags %}
{% block custom_head_css_js %}
-
-
-{% endblock %}
+{{ block.super }}
+
{% endblock %}
-
-{% block table_head %}
-
-
- |
- {% trans "Name" %} |
- {% trans "User Amount" %} |
- {% trans "Asset Amount" %} |
- {% trans "Comment" %} |
- |
-{% endblock %}
-
-{% block table_body %}
- {% for user_group in user_group_list %}
-
-
-
- |
-
-
- {{ user_group.name }}
-
- |
- {{ user_group.users.count }} |
- 999 |
- {{ user_group.comment|truncatewords:8 }} |
-
- {% trans "Edit" %}
- {% trans "Delete" %}
- |
+{% block table_search %}{% endblock %}
+{% block table_container %}
+
+
+
{% endblock %}
+{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
{% endblock %}
diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html
index 36029c59b..b08a44b53 100644
--- a/apps/users/templates/users/user_list.html
+++ b/apps/users/templates/users/user_list.html
@@ -1,7 +1,5 @@
{% extends '_base_list.html' %}
{% load i18n static %}
-{% get_current_language as LANGUAGE_CODE %}
-{% load common_tags %}
{% block custom_head_css_js %}
{{ block.super }}