diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 221c89b..5737775 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -3,10 +3,11 @@ + - + - + @@ -20,54 +21,60 @@ - - + + - - + + - + - - + + - - - + + + + + - + - - + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - + + + + + @@ -82,6 +89,12 @@ + + + reverse + OA + + @@ -92,21 +105,26 @@ - - - + + + + + + + + @@ -116,11 +134,11 @@ true DEFINITION_ORDER - - + + - - + + @@ -167,12 +185,6 @@ - - - - - - @@ -189,7 +201,7 @@ - + @@ -201,7 +213,7 @@ - + @@ -217,11 +229,11 @@ + - @@ -287,23 +299,23 @@ - + + - - + + - @@ -322,60 +334,11 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -531,13 +494,6 @@ - - - - - - - @@ -587,13 +543,6 @@ - - - - - - - @@ -601,13 +550,6 @@ - - - - - - - @@ -623,28 +565,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -652,13 +572,17 @@ - + - - - - - + + + + + + + + + @@ -670,18 +594,117 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/system/__pycache__/forms.cpython-36.pyc b/apps/system/__pycache__/forms.cpython-36.pyc index 31e3e4d..a4f30b7 100644 Binary files a/apps/system/__pycache__/forms.cpython-36.pyc and b/apps/system/__pycache__/forms.cpython-36.pyc differ diff --git a/apps/system/__pycache__/urls.cpython-36.pyc b/apps/system/__pycache__/urls.cpython-36.pyc index 9fb0b49..c7b32f5 100644 Binary files a/apps/system/__pycache__/urls.cpython-36.pyc and b/apps/system/__pycache__/urls.cpython-36.pyc differ diff --git a/apps/system/__pycache__/views_structure.cpython-36.pyc b/apps/system/__pycache__/views_structure.cpython-36.pyc index 1c7ea39..a931d6a 100644 Binary files a/apps/system/__pycache__/views_structure.cpython-36.pyc and b/apps/system/__pycache__/views_structure.cpython-36.pyc differ diff --git a/apps/system/__pycache__/views_user.cpython-36.pyc b/apps/system/__pycache__/views_user.cpython-36.pyc index 56743c9..9009685 100644 Binary files a/apps/system/__pycache__/views_user.cpython-36.pyc and b/apps/system/__pycache__/views_user.cpython-36.pyc differ diff --git a/apps/system/forms.py b/apps/system/forms.py index 68a4737..58cb4c0 100644 --- a/apps/system/forms.py +++ b/apps/system/forms.py @@ -2,9 +2,14 @@ # @Author : RobbieHan # @File : forms.py +import re from django import forms +from django.contrib.auth import get_user_model + from .models import Structure +User = get_user_model() + class LoginForm(forms.Form): username = forms.CharField(required=True, error_messages={"requeired": "请填写用户名"}) @@ -15,3 +20,102 @@ class StructureForm(forms.ModelForm): class Meta: model = Structure fields = ['type', 'name', 'parent'] + + +class UserCreateForm(forms.ModelForm): + password = forms.CharField( + required=True, + min_length=6, + max_length=20, + error_messages={ + "required": "密码不能为空", + "min_length": "密码长度最少6位数", + } + ) + + confirm_password = forms.CharField( + required=True, + min_length=6, + max_length=20, + error_messages={ + "required": "确认密码不能为空", + "min_length": "密码长度最少6位数", + } + ) + + class Meta: + model = User + fields = [ + 'name', 'gender', 'birthday', 'username', 'mobile', 'email', + 'department', 'post', 'superior', 'is_active', 'roles', 'password' + ] + + error_messages = { + "name": {"required": "姓名不能为空"}, + "username": {"required": "用户名不能为空"}, + "email": {"required": "邮箱不能为空"}, + "mobile": { + "required": "手机号码不能为空", + "max_length": "输入有效的手机号码", + "min_length": "输入有效的手机号码" + } + } + + def clean(self): + cleaned_data = super(UserCreateForm, self).clean() + username = cleaned_data.get("username") + mobile = cleaned_data.get("mobile", "") + email = cleaned_data.get("email") + password = cleaned_data.get("password") + confirm_password = cleaned_data.get("confirm_password") + + if User.objects.filter(username=username).count(): + raise forms.ValidationError('用户名:{}已存在'.format(username)) + + if password != confirm_password: + raise forms.ValidationError("两次密码输入不一致") + + if User.objects.filter(mobile=mobile).count(): + raise forms.ValidationError('手机号码:{}已存在'.format(mobile)) + + REGEX_MOBILE = "^1[3578]\d{9}$|^147\d{8}$|^176\d{8}$" + if not re.match(REGEX_MOBILE, mobile): + raise forms.ValidationError("手机号码非法") + + if User.objects.filter(email=email).count(): + raise forms.ValidationError('邮箱:{}已存在'.format(email)) + + +class UserUpdateForm(forms.ModelForm): + class Meta: + model = User + fields = [ + 'name', 'gender', 'birthday', 'username', 'mobile', 'email', + 'department', 'post', 'superior', 'is_active', 'roles' + ] + + +class PasswordChangeForm(forms.Form): + + password = forms.CharField( + required=True, + min_length=6, + max_length=20, + error_messages={ + "required": u"密码不能为空" + }) + + confirm_password = forms.CharField( + required=True, + min_length=6, + max_length=20, + error_messages={ + "required": u"确认密码不能为空" + }) + + def clean(self): + cleaned_data = super(PasswordChangeForm, self).clean() + password = cleaned_data.get("password") + confirm_password = cleaned_data.get("confirm_password") + if password != confirm_password: + raise forms.ValidationError("两次密码输入不一致") \ No newline at end of file diff --git a/apps/system/urls.py b/apps/system/urls.py index 4d680ee..7f45c89 100644 --- a/apps/system/urls.py +++ b/apps/system/urls.py @@ -1,7 +1,7 @@ from django.urls import path, re_path from .views import SystemView -from . import views_structure +from . import views_structure, views_user app_name = 'system' @@ -12,5 +12,14 @@ urlpatterns = [ path('basic/structure/list/', views_structure.StructureListView.as_view(), name='basic-structure-list'), path('basic/structure/delete/', views_structure.StructureDeleteView.as_view(), name='basic-structure-delete'), path('basic/structure/add_user/', views_structure.Structure2UserView.as_view(), name='basic-structure-add_user'), -] + path('basic/user/', views_user.UserView.as_view(), name='basic-user'), + path('basic/user/list/', views_user.UserListView.as_view(), name='basic-user-list'), + path('basic/user/create/', views_user.UserCreateView.as_view(), name='basic-user-create'), + path('basic/user/detail/', views_user.UserDetailView.as_view(), name='basic-user-detail'), + path('basic/user/update/', views_user.UserUpdateView.as_view(), name='basic-user-update'), + path('basic/user/password_change/', views_user.PasswordChangeView.as_view(), name='basic-user-password_change'), + path('basic/user/delete/', views_user.UserDeleteView.as_view(), name='basic-user-delete'), + path('basic/user/enable/', views_user.UserEnableView.as_view(), name='basic-user-enable'), + path('basic/user/disable/', views_user.UserDisableView.as_view(), name='basic-user-disable'), +] diff --git a/apps/system/views_user.py b/apps/system/views_user.py index 3e8999b..d575a31 100644 --- a/apps/system/views_user.py +++ b/apps/system/views_user.py @@ -2,14 +2,23 @@ # @Author : RobbieHan # @File : views_user.py -from django.shortcuts import render -from django.views.generic.base import View -from django.http import HttpResponseRedirect -from django.contrib.auth import authenticate, login, logout -from django.urls import reverse +import re +import json -from .forms import LoginForm +from django.shortcuts import render, HttpResponse +from django.views.generic.base import View, TemplateView +from django.http import HttpResponseRedirect +from django.contrib.auth import authenticate, login, logout, get_user_model +from django.urls import reverse +from django.contrib.auth.hashers import make_password +from django.shortcuts import get_object_or_404 +from django.db.models import Q + +from .forms import LoginForm, UserCreateForm, UserUpdateForm, PasswordChangeForm from .mixin import LoginRequiredMixin +from .models import Structure, Role + +User = get_user_model() class IndexView(LoginRequiredMixin, View): @@ -51,4 +60,159 @@ class LogoutView(View): def get(self, request): logout(request) - return HttpResponseRedirect(reverse('login')) \ No newline at end of file + return HttpResponseRedirect(reverse('login')) + + +class UserView(LoginRequiredMixin, TemplateView): + template_name = 'system/users/user.html' + + +class UserListView(LoginRequiredMixin, View): + def get(self, request): + fields = ['id', 'name', 'gender', 'mobile', 'email', 'department__name', 'post', 'superior__name', 'is_active'] + filters = dict() + if 'select' in request.GET and request.GET['select']: + filters['is_active'] = request.GET['select'] + ret = dict(data=list(User.objects.filter(**filters).values(*fields))) + return HttpResponse(json.dumps(ret), content_type='application/json') + + +class UserCreateView(LoginRequiredMixin, View): + """ + 添加用户 + """ + + def get(self, request): + users = User.objects.exclude(username='admin') + structures = Structure.objects.values() + roles = Role.objects.values() + + ret = { + 'users': users, + 'structures': structures, + 'roles': roles, + } + return render(request, 'system/users/user_create.html', ret) + + def post(self, request): + user_create_form = UserCreateForm(request.POST) + if user_create_form.is_valid(): + new_user = user_create_form.save(commit=False) + new_user.password = make_password(user_create_form.cleaned_data['password']) + new_user.save() + user_create_form.save_m2m() + ret = {'status': 'success'} + else: + pattern = '.*?(.*?)' + errors = str(user_create_form.errors) + user_create_form_errors = re.findall(pattern, errors) + ret = { + 'status': 'fail', + 'user_create_form_errors': user_create_form_errors[0] + } + return HttpResponse(json.dumps(ret), content_type='application/json') + + +class UserDetailView(LoginRequiredMixin, View): + + def get(self, request): + user = get_object_or_404(User, pk=int(request.GET['id'])) + users = User.objects.exclude(Q(id=int(request.GET['id'])) | Q(username='admin')) + structures = Structure.objects.values() + roles = Role.objects.values() + user_roles = user.roles.values() + ret = { + 'user': user, + 'structures': structures, + 'users': users, + 'roles': roles, + 'user_roles': user_roles + } + return render(request, 'system/users/user_detail.html', ret) + + +class UserUpdateView(LoginRequiredMixin, View): + + def post(self, request): + if 'id' in request.POST and request.POST['id']: + user = get_object_or_404(User, pk=int(request.POST['id'])) + else: + user = get_object_or_404(User, pk=int(request.user.id)) + user_update_form = UserUpdateForm(request.POST, instance=user) + if user_update_form.is_valid(): + user_update_form.save() + ret = {"status": "success"} + else: + ret = {"status": "fail", "message": user_update_form.errors} + return HttpResponse(json.dumps(ret), content_type="application/json") + + +class PasswordChangeView(LoginRequiredMixin, View): + + def get(self, request): + ret = dict() + if 'id' in request.GET and request.GET['id']: + user = get_object_or_404(User, pk=int(request.GET.get('id'))) + ret['user'] = user + return render(request, 'system/users/passwd_change.html', ret) + + def post(self, request): + if 'id' in request.POST and request.POST['id']: + user = get_object_or_404(User, pk=int(request.POST['id'])) + form = PasswordChangeForm(request.POST) + if form.is_valid(): + new_password = request.POST['password'] + user.set_password(new_password) + user.save() + ret = {'status': 'success'} + else: + pattern = '.*?(.*?)' + errors = str(form.errors) + password_change_form_errors = re.findall(pattern, errors) + ret = { + 'status': 'fail', + 'password_change_form_errors': password_change_form_errors[0] + } + return HttpResponse(json.dumps(ret), content_type='application/json') + + +class UserDeleteView(LoginRequiredMixin, View): + """ + 删除数据:支持删除单条记录和批量删除 + """ + + def post(self, request): + ret = dict(result=False) + if 'id' in request.POST and request.POST['id']: + id_list = map(int, request.POST['id'].split(',')) + User.objects.filter(id__in=id_list).delete() + ret['result'] = True + return HttpResponse(json.dumps(ret), content_type='application/json') + + +class UserEnableView(LoginRequiredMixin, View): + """ + 启用用户:单个或批量启用 + """ + + def post(self, request): + if 'id' in request.POST and request.POST['id']: + id_nums = request.POST.get('id') + queryset = User.objects.extra(where=["id IN(" + id_nums + ")"]) + queryset.filter(is_active=False).update(is_active=True) + ret = {'result': 'True'} + return HttpResponse(json.dumps(ret), content_type='application/json') + + +class UserDisableView(LoginRequiredMixin, View): + """ + 启用用户:单个或批量启用 + """ + + def post(self, request): + if 'id' in request.POST and request.POST['id']: + id_nums = request.POST.get('id') + queryset = User.objects.extra(where=["id IN(" + id_nums + ")"]) + queryset.filter(is_active=True).update(is_active=False) + ret = {'result': 'True'} + return HttpResponse(json.dumps(ret), content_type='application/json') \ No newline at end of file diff --git a/db.sqlite3 b/db.sqlite3 index 08338da..1977f0d 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/templates/base-static.html b/templates/base-static.html index f5e53b1..7e4b9a3 100644 --- a/templates/base-static.html +++ b/templates/base-static.html @@ -8,7 +8,7 @@ scratch. This page gets rid of all links and provides the needed markup only. - SandBoxOA + SandBoxMP @@ -52,7 +52,7 @@ scratch. This page gets rid of all links and provides the needed markup only. SandBox - 沙盒协同办公平台 + 沙盒运维管理平台 diff --git a/templates/system/users/passwd_change.html b/templates/system/users/passwd_change.html new file mode 100644 index 0000000..4519f84 --- /dev/null +++ b/templates/system/users/passwd_change.html @@ -0,0 +1,95 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} + +{% block css %} +{% endblock %} + +{% block main %} + + + {% csrf_token %} + + + + + + 基本信息 + + + 姓名 + + + + 用户名 + + + + + + 密码信息 + + + 密码 + + + + 确认密码 + + + + + + + + + + + + + +{% endblock %} + +{% block javascripts %} + + + + +{% endblock %} diff --git a/templates/system/users/user.html b/templates/system/users/user.html new file mode 100644 index 0000000..93176a6 --- /dev/null +++ b/templates/system/users/user.html @@ -0,0 +1,396 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + + +{% endblock %} + +{% block content %} + + + + + + + + + + 刷新 + + +   + + + 新增 + + +   + + + 删除 + + +   + + + 启用 + + + 禁用 + + + + + + 用户状态: + + -----所有----- + 启用 + 禁用 + + + + + + + + + + + ID + 姓名 + 性别 + 手机 + 邮箱 + 部门 + 职位 + 上级 + 状态 + 操作 + + + + + + + + + + + + + +{% endblock %} + +{% block javascripts %} + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/system/users/user_create.html b/templates/system/users/user_create.html new file mode 100644 index 0000000..b414269 --- /dev/null +++ b/templates/system/users/user_create.html @@ -0,0 +1,202 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} + +{% block css %} + + + +{% endblock %} + +{% block main %} + + + {% csrf_token %} + + + + + + 基本信息 + + + 姓名 + + + + 性别 + + + 男 + 女 + + + + + + + 生日 + + + + 用户名 + + + + + + 状态 + + + 启用 + + + 禁用 + + + + + 密码信息 + + + 密码 + + + + 确认密码 + + + + + + 联系信息 + + + 手机 + + + + 邮箱 + + + + + + 职员信息 + + + 入职日期 + + + + 部门 + + + --部门-- + {% for structure in structures %} + {{ structure.name }} + {% endfor %} + + + + + + 岗位 + + + + 上级 + + + --上级-- + {% for user in users %} + {{ user.name }} + {% endfor %} + + + + + + + 所属角色组 + + {% for role in roles %} + + + {{ role.name }} + + {% endfor %} + + + + + + + + + + +{% endblock %} + +{% block javascripts %} + + + + +{% endblock %} diff --git a/templates/system/users/user_detail.html b/templates/system/users/user_detail.html new file mode 100644 index 0000000..b39431e --- /dev/null +++ b/templates/system/users/user_detail.html @@ -0,0 +1,192 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} + +{% block css %} + + +{% endblock %} + +{% block main %} + + + {% csrf_token %} + + + + + + 基本信息 + + + 姓名 + + + + 性别 + + + {{ user.get_gender_display }} + 男 + 女 + + + + + + + 生日 + + + + 用户名 + + + + + + 状态 + + + 启用 + + + 禁用 + + + + + 联系信息 + + + 手机 + + + + 邮箱 + + + + + + 职员信息 + + + 入职日期 + + + + 部门 + + + {{ user.department.name|default:"--部门--" }} + {% for structure in structures %} + {{ structure.name }} + {% endfor %} + + + + + + 岗位 + + + + 上级 + + + {{ user.superior.name|default:"--上级--" }} + {% for user in users %} + {{ user.name }} + {% endfor %} + + + + + + + 所属角色组 + + {% for role in roles %} + + + {{ role.name }} + + {% endfor %} + + + + + + + + + + +{% endblock %} + +{% block javascripts %} + + + +{% endblock %}
沙盒协同办公平台
沙盒运维管理平台