这节将会介绍用户管理功能的实现,包括用户信息浏览、添加、删除和修改等操作,从这一节开始很多功能实现都是和前面组织架构管理功能实现类似,所以通过这一节我们将完整实现用户管理功能。
1 用户列表展示
为了能够在列表中展示所有的用户信息,我们需要写两个视图,一个是用来提供模板页的基础视图,另外一个是用来访问用户数据列表的接口视图,然后通过使用ajax将数据组合填充到datatables中进行展示。
1.1 视图配置
打开sandboxMP/apps/system/views_user.py, 添加如下内容
import json from django.views.generic.base import TemplateView from django.shortcuts import HttpResponse from django.contrib.auth import get_user_model User = get_user_model() 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'] ret = dict(data=list(User.objects.values(*fields))) return HttpResponse(json.dumps(ret), content_type='application/json')
知识点介绍:
1、UserView:继承了TemplateView基本类视图,通过template_name指定返回的模板页 2、UserListView:通过QuerySet的values方法来获取指定字段列的数据内容,转换QuerySet类型最终序列化成json串,返回数据访问接口 3、User = get_user_model():使用自定义用户模型的方法 4、department__name:departent是外键,默认存储在数据库里面的是department_id, 使用这种写法可以直接通过QuerySet的values方法获取department的name值,类似的还有superior__name
1.2 URL配置
打开 sandboxMP/apps/system/urls.py, 添加新的url:
from . import views_user app_name = 'system' urlpatterns = [ ...原有内容省略... path('basic/user/', views_user.UserView.as_view(), name='basic-user'), path('basic/user/list/', views_user.UserListView.as_view(), name='basic-user-list'), ] # 完成以上配置第一个url : http://127.0.0.1:8000/system/basic/user/是会报错的, # 错误信息:TemplageDoesNotExist,因为我肯还没有配置模板; # 第二个URL是可以访问的:http://127.0.0.1:8000/system/basic/user/list/ # 因为UserListView返回的是一个json数据接口
1.3 模板配置
复制sandboxMP/template/index.html 文件到sandboxMP/template/system/users/目录下,重命名为user.html, 并修改内容如下:
{% extends "base-left.html" %} {% load staticfiles %} {% block css %} {% endblock %} {% block content %} <!-- Main content --> <section class="content"> 当前访问页:用户管理, 这里是用户管理的基本页,用户管理所有模板页内容将会在这里添加 </section> <!-- /.content --> {% endblock %} {% block javascripts %} {% endblock %}
这时候就可以访问我们的用户管理页面了:http://127.0.0.1:8000/system/basic/user/
接下来继续修改我们的user.html模板页,使用datatables来展示我们的用户数列表
1、修改user.html文件,删除section标签中刚才添加的临时内容,然后在section标签中写入下面的内容:
<div id="devlist"> <div class="box box-primary" id="liebiao"> <div class="box-header"> <div class="btn-group pull-left"> <button type="button" id="btnRefresh" class="btn btn-default"> <i class="glyphicon glyphicon-repeat"></i>刷新 </button> </div> <div class="btn-group pull-left"> </div> <div class="btn-group pull-left"> <button type="button" id="btnCreate" class="btn btn-default"> <i class="glyphicon glyphicon-plus"></i>新增 </button> </div> <div class="btn-group pull-left"> </div> <div class="btn-group pull-left"> <button type="button" id="btnDelete" class="btn btn-default"> <i class="glyphicon glyphicon-trash"></i>删除 </button> </div> <div class="btn-group pull-left"> </div> <div class="btn-group pull-left"> <button type="button" id="btnEnable" class="btn btn-default"> <i class="glyphicon glyphicon-ok-circle"></i>启用 </button> <button type="button" id="btnDisable" class="btn btn-default"> <i class="glyphicon glyphicon-ban-circle"></i>禁用 </button> </div> <div class="btn-group pull-right"> <form class="form-inline"> <div class="form-group"> <label>用户状态:</label> <select id="select" name="select" class="form-control"> <option style='text-align:center' value=''>-----所有-----</option> <option value="True">启用</option> <option value="False">禁用</option> </select> </div> </form> </div> </div> <div class="box-body"> <table id="dtbList" class="display" cellspacing="0" width="100%"> <thead> <tr valign="middle"> <th><input type="checkbox" id="checkAll"></th> <th>ID</th> <th>姓名</th> <th>性别</th> <th>手机</th> <th>邮箱</th> <th>部门</th> <th>职位</th> <th>上级</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> </tbody> </table> <br> <br> </div> </div> </div>
模板内容说明:
- 定义了用户管理的一些操作按钮,包含 新建、删除、启用、停用等,按钮都定义了id,用来关联js方法
- 定义了用户列表基本表格,表头和UserListView中的fields内容是一致的
页面访问效果(http://127.0.0.1:8000/system/basic/user/):
2、接着修改user.html文件,使用datatables来展示用户数据:
引用样式文件,写到{% block css %}标签下:
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}"> <link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
引用js文件并进行表格初始化,写到{% block javascripts %}标签下:
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script> <script src="{% static 'plugins/datatables/dataTables.const.js' %}"></script> <script src="{% static 'js/plugins/layer/layer.js' %}"></script> <script type="text/javascript"> var oDataTable = null; $(function () { oDataTable = initTable(); function initTable() { var oTable = $('#dtbList').DataTable($.extend(true, {}, DATATABLES_CONSTANT.DATA_TABLES.DEFAULT_OPTION, { ajax: { "url": "{% url 'system:basic-user-list' %}", }, columns: [ DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX, { data: "id", width: "5%", }, { data: "name",//parent width: "10%", }, { data: "gender", width: "10%", render: function (data, type, row, meta) { if (data == 'male') { return "男"; } else { return "女"; } } }, { data: "mobile", }, { data: "email", }, { data: "department__name", }, { data: "post", }, { data: "superior__name", }, { data: "is_active", render: function (data) { if (data == true) { return "启用"; } else { return "禁用"; } } }, { data: "id", width: "12%", bSortable: "false", render: function (data, type, row, meta) { var ret = ""; var ret = "<button title='详情-编辑' onclick='doUpdate(" + data + ")'><i class='glyphicon glyphicon-pencil'></i></button>"; ret = ret + "<button name='btnChangepasswd' title='修改密码' onclick='doChangepasswd(" + data + ")'><i class='glyphicon glyphicon-asterisk'></i></button>"; ret = ret + "<button name='btnConfig' title='删除' onclick='doDelete(" + data + ")'><i class='glyphicon glyphicon-trash'></i></button>"; return ret; } }], "order": [ [2, 'desc'] ], })); return oTable; } }); </script>
Ctrl+F5刷新用户管理页面,就可以看到用户数据已经通过datatables在页面上展示了。
知识点介绍:
我们在初始化datatables配置的js中写到: ajax: { "url": "{% url 'system:basic-user-list' %}", }, 1、通过ajax请求/system/base/user/userlist接口来获取数据; 2、{% url 'system:basic-user-list' %}: 是使用了DJANGO URL的反向解析功能,避免URL的硬编码,URL变更,不会影响到模板中的URL解析 3、在django后台也可以使用reverse()函数来进行URL的反响解析,实际上我们在用户登出的LogoutView视图已经使用到了reverse()函数 4、columns:datatables的一个属性方法,用来将通过ajax获取到的json数据渲染到表格中去,其中data指定的字段需和后台传递过来的数据字段名称一致,并和html中定义的表头顺寻一致
2 添加用户
在添加用户时,我们需要对输入的数据进行有效性验证,包括:密码长度和有效性验证、关键字段的有效性验证、用户名重复性验证、手机号码有效性验证、手机号码重复性验证等等,同时还要对错误输入提供有效的错误提示信息,看起来要求很多,不过好在django表单功能提供了各种验证方法。
2.1 创建UserCreateForm
打开sandboxMP/apps/system/forms.py, 添加如下内容:
import re from django.contrib.auth import get_user_model User = get_user_model() 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))
知识点介绍:
1、error_messages:表单字段的关键参数,通过覆盖字段引发的异常中的默认信息,实现自定义错误提示信息。 2、clean()方法:重写clean()方法可以实现额外的验证功能。 3、ValidationError:当form验证的数据有问题都会引发ValidationError,并将相关的错误信息传递给 ValidationError,项目中通过重写clean()方法对输入的数据进行额外验证,不合规的数据输入将会触发ValidationError,返回错误信息。
2.2 用户添加视图
import re from django.contrib.auth.hashers import make_password from .forms import UserCreateForm from .models import Structure, Role 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 = '<li>.*?<ul class=.*?><li>(.*?)</li>' 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')
知识点介绍:
- 通过get()方法返回添加用户的模板页,同时传递了ret上下文内容,用来作为添加用户时的选择字段。
- exclude(**kwargs):QuerySet方法,排除给定的对象,返回不包含给定对象的QuerySet。
- user_create_form.save(commit=False):在添加组织架构一节,已经使用到form.save()方法来存储数据,这里使用save(commit=False),指定commit=False,当通过表单获取模型数据后,在调用save()方法时不会将数据存储到数据库,而是返回一个当前对象,这样我们就可以添加表单以外的数据,然后在一再存储到数据库。
- make_password:django自带加密模块,这里使用时为了将密码以密文形式存储到数据库。
- save_m2m(): 用来存储多对多的关系,添加用户时绑定的角色组为多对多关系,实际上使用form.save()方法是可以直接存储多对多关系的,因为我们前面使用了save(commit=False)方法,所以这里要使用save_m2m()方法。
- user_create_form.errors:获取表单验证的错误信息,默认获得一个错误信息的字典格式,也可以使用form.errors.as_json()来返回JSON序列化后的错误信息。
- user_create_form_errors[0]:这里对错误信息进行了处理,只返回文字信息,并且每次只返回一个错误信息。
2.3 URL配置
打开sandboxMP/apps/system/urls.py,添加新增用户的URL:
urlpatterns = [ '''原有内容省略''' path('basic/user/create/', views_user.UserCreateView.as_view(), name='basic-user-create'), ]
2.4 添加用户的模板配置
新建添加模板:sandboxMP/templates/system/users/user_create.html,内容如下:
{% extends 'base-layer.html' %} {% load staticfiles %} {% block css %} <link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}"> <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}"> <link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}"> {% endblock %} {% block main %} <div class="box box-danger"> <form class="form-horizontal" id="addForm" method="post"> {% csrf_token %} <input type="hidden" name='id' value="{{ user.id }}"/> <input type="hidden" name='user' value="save"/> <div class="box-body"> <fieldset> <legend> <h4>基本信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">姓名</label> <div class="col-sm-3"> <input class="form-control" name="name" type="text" value=""/> </div> <label class="col-sm-2 control-label">性别</label> <div class="col-sm-3"> <select class="form-control" name="gender"> <option value="male">男</option> <option value="female">女</option> </select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">生日</label> <div class="col-sm-3"> <input type="text" class="form-control pull-right form_datetime" name="birthday"/> </div> <label class="col-sm-2 control-label">用户名</label> <div class="col-sm-3"> <input type="text" class="form-control" name="username"/> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">状态</label> <div class="col-sm-6"> <label class="control-label"> <input type="radio" class="minimal" name="is_active" value="True" checked>启用 </label> <label class="control-label"> <input type="radio" class="minimal" name="is_active" value="False">禁用 </label> </div> </div> <legend> <h4>密码信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">密码</label> <div class="col-sm-3"> <input class="form-control" name="password" type="password" value=""/> </div> <label class="col-sm-2 control-label">确认密码</label> <div class="col-sm-3"> <input class="form-control" name="confirm_password" type="password" value=""/> </div> </div> <legend> <h4>联系信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">手机</label> <div class="col-sm-3"> <input class="form-control" name="mobile" type="text"/> </div> <label class="col-sm-2 control-label">邮箱</label> <div class="col-sm-3"> <input class="form-control" name="email" type="text"/> </div> </div> <legend> <h4>职员信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">入职日期</label> <div class="col-sm-3"> <input type="text" class="form-control pull-right form_datetime" name="joined_date"/> </div> <label class="col-sm-2 control-label">部门</label> <div class="col-sm-3"> <select class="form-control select2" style="width:100%;" name="department"> <option value="">--部门--</option> {% for structure in structures %} <option value="{{ structure.id }}">{{ structure.name }}</option> {% endfor %} </select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">岗位</label> <div class="col-sm-3"> <input class="form-control" name="post" type="text"/> </div> <label class="col-sm-2 control-label">上级</label> <div class="col-sm-3"> <select class="form-control select2" style="width:100%;" name="superior"> <option value="">--上级--</option> {% for user in users %}<option value="{{ user.id }}">{{ user.name }}</option> {% endfor %}</select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">所属角色组</label> <div class="col-sm-6"> {% for role in roles %}<label class="control-label"> <input type="checkbox" class="minimal" name="roles" value="{{ role.id }}" {% if role in user_roles %}checked{% endif %}> {{ role.name }}</label> {% endfor %}</div> </div> </fieldset> </div> <div class="box-footer "> <div class="row span7 text-center "> <button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button> <button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button> </div> </div> </form> </div> {% endblock %}{% block javascripts %}<script src="{% static 'bootstrap/js/bootstrap-datetimepicker.js' %}"></script> <script src="{% static 'plugins/select2/select2.full.min.js' %}"></script> <script type="text/javascript"> $("#btnSave").click(function () { var data = $("#addForm").serialize(); $.ajax({ type: $("#addForm").attr('method'), url: "{% url 'system:basic-user-create' %}", data: data, cache: false, success: function (msg) { if (msg.status == 'success') { layer.alert('用户添加成功!', {icon: 1}, function (index) { parent.layer.closeAll(); //关闭所有弹窗 }); } else if (msg.status == 'fail') { layer.alert(msg.user_create_form_errors, {icon: 5}); //$('errorMessage').html(msg.message) } return; } }); }); /*点取消刷新新页面*/ $("#btnCancel").click(function () { window.location.reload(); }) /*input 时间输入选择*/ $(".form_datetime").datetimepicker({ language: 'zh', minView: 'month', //选择范围只到日期,不选择时分 //weekStart: 1, //todayBtn: 1, autoclose: 1, todayHighlight: 1, //startView: 2, forceParse: 0, showMeridian: 1, format: 'yyyy-mm-dd' }).on('changeDate', function (ev) { $(this).datetimepicker('hide'); }); $(function () { //Initialize Select2 Elements $(".select2").select2(); }); </script> {% endblock %}
知识点介绍:
- 该模板用户添加弹窗,继承了base-layer.html模板,定义了条件用户的form表单
- $("#btnSave").click(function ():定义了保存按钮的点击事件,通过判断后端返回的status来提示响应信息。
2.5 为添加按钮绑定事件
我们在用户管理页面已经定义了添加用户的按钮,打开sandboxMP/templates/system/users/user.html,在 {% block javascripts %}标签下添加如下内容(放到datatables初始化配置的后面):
<script type="text/javascript"> $("#btnCreate").click(function () { var div = layer.open({ type: 2, title: '新增', shadeClose: false, maxmin: true, area: ['800px', '720px'], content: '/system/basic/user/create', end: function () { //关闭时做的事情 oDataTable.ajax.reload(); } }); layer.full(div) }); </script>
至此,访问用户管理页面:http://127.0.0.1:8000/system/basic/user/, 点击新增按钮就可以添加新的用户了,大家也可以在添加用户的时候打上断点Debug一下,了解form验证和错误信息的提示效果。
3 用户详情
用户详情页用于查看用户详情信息和修改用户信息。
3.1 用户详情视图
打开sandboxMP/apps/system/views_user.py,添加下面内容:
from django.shortcuts import get_object_or_404 from django.db.models import Q 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)
知识点介绍:
- 用户详情视图同时也用于用户数据的更新视图,上下文数据除了包括选中用户的详情信息外,同时还传递了,修改用户数据时的一些选择项,例如角色组,组织架构等.
- UserDetailView视图中只定义了get()方法,并未定义用来接收前端传递的修改数据信息和存储修改数据的post()方法,这是因为用户数据修改分为两种类型:一种是管理员通过用户管理来修改指定的用户信息,另外一种是已登录用户通过用户中心修改个人用户信息,所以这里把post()方法拿出去单独新建了一个更新的视图.
- users = User.objects.exclude(Q(id=int(request.GET['id'])) | Q(username='admin')):修改用户数据时,可以通过select选择用户的上级,这里在做查询的时候我们使用exclude()方法排除了当前选中的用户和系统的默认用户,其中Q()对象就是用来构建负载的数据库查询他,它支持使用 | (OR) 和 & (AND).
3.2 用户详情URL配置
打开sandboxMP/apps/system/urls.py,添加用户详情访问URL:
urlpatterns = [ '''原有内容省略''' path('basic/user/detail/', views_user.UserDetailView.as_view(), name='basic-user-detail'), ]
3.3 用户详情页模板配置
修建用户详情页模板:sandboxMP/templates/system/users/user_detail.html,内容如下:
{% extends 'base-layer.html' %} {% load staticfiles %} {% block css %} <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}"> <link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}"> {% endblock %} {% block main %} <div class="box box-danger"> <form class="form-horizontal" id="addForm" method="post"> {% csrf_token %} <!-- 请查看下面知识点介绍中注释1:--> <input type="hidden" name='id' value="{{ user.id }}"/> <input type="hidden" name='user' value="save"/> <div class="box-body"> <fieldset> <legend> <h4>基本信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">姓名</label> <div class="col-sm-3"> <input class="form-control" name="name" type="text" value="{{ user.name }}"/> </div> <label class="col-sm-2 control-label">性别</label> <div class="col-sm-3"> <select class="form-control" name="gender"> <option value={{ user.gender }}> {{ user.get_gender_display }} </option> <option value="male">男</option> <option value="female">女</option> </select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">生日</label> <div class="col-sm-3"> <input type="text" class="form-control pull-right form_datetime" name="birthday" value="{{ user.birthday|date:"Y-m-d" }}"/> </div> <label class="col-sm-2 control-label">用户名</label> <div class="col-sm-3"> <input type="text" class="form-control" name="username" readonly="readonly" value="{{ user.username }}"/> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">状态</label> <div class="col-sm-6"> <label class="control-label"> <input type="radio" class="minimal" name="is_active" value="True" {% ifequal user.is_active True %}checked{% endifequal %}>启用 </label> <label class="control-label"> <input type="radio" class="minimal" name="is_active" value="False" {% ifequal user.is_active False %}checked{% endifequal %}>禁用 </label> </div> </div> <legend> <h4 clase="">联系信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">手机</label> <div class="col-sm-3"> <input class="form-control" name="mobile" readonly="readonly" type="text" value="{{ user.mobile }}"/> </div> <label class="col-sm-2 control-label">邮箱</label> <div class="col-sm-3"> <input class="form-control" name="email" type="text" value="{{ user.email }}"/> </div> </div> <legend> <h4>职员信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">入职日期</label> <div class="col-sm-3"> <!-- 请查看下面知识点介绍中注释2:--> <input type="text" class="form-control pull-right form_datetime" name="joined_date" value="{{ user.joined_date|date:"Y-m-d" }}"/> </div> <label class="col-sm-2 control-label">部门</label> <div class="col-sm-3"> <select class="form-control select2" style="width:100%;" name="department"> <option value="{{ user.department.id }}">{{ user.department.name|default:"--部门--" }}</option> {% for structure in structures %} <option value="{{ structure.id }}">{{ structure.name }}</option> {% endfor %} </select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">岗位</label> <div class="col-sm-3"> <input class="form-control" name="post" type="text" value="{{ user.post|default_if_none:"" }}"/> </div> <label class="col-sm-2 control-label">上级</label> <div class="col-sm-3"> <select class="form-control select2" style="width:100%;" name="superior"> <option value="{{ user.superior.id }}">{{ user.superior.name|default:"--上级--" }}</option> {% for user in users %}<option value="{{ user.id }}">{{ user.name }}</option> {% endfor %}</select> </div> </div> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">所属角色组</label> <div class="col-sm-6"> {% for role in roles %}<label class="control-label"> <input type="checkbox" class="minimal" name="roles" value="{{ role.id }}" {% if role in user_roles %}checked{% endif %}> {{ role.name }}</label> {% endfor %}</div> </div> </fieldset> </div> <div class="box-footer "> <div class="row span7 text-center "> <button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button> <button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button> </div> </div> </form> </div> {% endblock %}{% block javascripts %}<script src="{% static 'bootstrap/js/bootstrap-datetimepicker.js' %}"></script> <script src="{% static 'plugins/select2/select2.full.min.js' %}"></script> <script type="text/javascript">/*点取消刷新新页面*/ $("#btnCancel").click(function () { window.location.reload(); }) /*input 时间输入选择*/ $(".form_datetime").datetimepicker({ language: 'zh', minView: 'month', //weekStart: 1, //todayBtn: 1, autoclose: 1, todayHighlight: 1, //startView: 2, forceParse: 0, showMeridian: 1, format: 'yyyy-mm-dd' }).on('changeDate', function (ev) { $(this).datetimepicker('hide'); }); $(function () { //Initialize Select2 Elements $(".select2").select2(); }); </script> {% endblock %}
知识点介绍(对应代码中注释部分):
- 注释1:我们在所有修改数据信息的视图都会写一条隐藏的input用来向后台传递修改数据条目的id, 然后后台通过POST方法获取到id,在通过QuerySet查询到该id对应的实例,保存修改信息。
- 注释2:这里使用了django模板内置的过滤器date根据给定的格式对一个date变量进行格式化,上面代码中我们还使用了default_if_none过滤器,当value为None,则使用给定的默认值,django还提供了很多好用的过滤器,有兴趣可以查看下官方文档。
3.4 为用户详情-修改按钮绑定点击事件
打开sandboxMP/templates/system/users/user.html,在{% block javascripts %}标签中的$("#btnCreate")方法后面添加如下内容:
// 跳转到用户详情页面 function doUpdate(id) { var div = layer.open({ type: 2, title: '编辑', shadeClose: false, maxmin: true, area: ['800px', '650px'], content: ["{% url 'system:basic-user-detail' %}" + '?id=' + id, 'no'], end: function () { oDataTable.ajax.reload(); } }); layer.full(div) }
在datatables初始化配置的时候,已经为表格中每一条数据生成了【详情-修改】按钮,同时绑定了doUpdate()函数,同时将选中数据id传递给改函数,当我们点击该按钮时候,系统会把当前选中用户数据id传递到后台,获取用户详细数据。
运行系统,访问用户管理页面:http://127.0.0.1:8000/system/basic/user/ :
点击数据条目后面的详情-编辑按钮就可以访问用户详情页面:
4 用户更新
前面已经实现用户详情信息的访问,并且在详情页可以修改用户信息,接下来还需要写一个更新视图,用来接收用户修改信息,保存到数据库
4.1 创建UserUpdateForm
打开sandboxMP/apps/system/forms.py,添加如下内容:
class UserUpdateForm(forms.ModelForm): class Meta: model = User fields = [ 'name', 'gender', 'birthday', 'username', 'mobile', 'email', 'department', 'post', 'superior', 'is_active', 'roles' ]
我们定义了一个form类用来验证用户更新输入的数据,比起添加用户的验证要简单的多,因为我们将会在前端限制关键字段的修改,包括:用户名、手机号码和邮箱等
4.2 用户更新视图
打开sandboxMP/apps/system/views_user.py,新增用户更新视图
from .forms import UserUpdateForm 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")
实现思路:
1、从request.POST中获取前端传递过来的需要修改的用户id
2、从用户模型中通过id查找改用户实例
3、将该用户实例出传递给UserUpdateForm
4、通过form.is_valid()方法验证输入数据是否合法
5、使用form.save()方法保存数据
6、返回最终执行结果
注意:如果request.POST中没有传递用户id,则默认用户是修改当前登陆的用户信息,所以会查找当前登陆用户的实例,这样做是为了让用户更新视图同时可以用于用户中心,修改个人信息。
4.3 添加用户更新URL
打开sandboxMP/apps/system/urls.py,添加用户更新URL
urlpatterns = [ '''原有内容省略''' path('basic/user/update/', views_user.UserUpdateView.as_view(), name='basic-user-update'), ]
4.3 为用户详情页的保存按钮绑定事件
用户信息修改和用户详情页使用的是同一个页面,在用户详情页已经定义好了保存按钮,接下来只需要当定提交事件即可,修改 sandboxMP/templates/system/users/user_detail.html,在{% block javascripts %}标签下添加如下内容:
$(function () { //Initialize Select2 Elements $(".select2").select2(); }); //分界线,下面是新添加的内容 $("#btnSave").click(function () { var data = $("#addForm").serialize(); $.ajax({ type: $("#addForm").attr('method'), url: "{% url 'system:basic-user-update' %}", data: data, cache: false, success: function (msg) { if (msg.status == 'success') { layer.alert('数据保存成功!', {icon: 1}, function (index) { parent.layer.closeAll(); //关闭所有弹窗 }); } else if (msg.status == 'fail') { layer.alert('数据保存失败', {icon: 5}); //$('errorMessage').html(msg.message) } return; } }); });
到这里运行系统,访问用户管理页:http://127.0.0.1:8000/system/basic/user/, 选择表格中的 【详情-修改】按钮,在打开的弹窗中就可以修改我们的用户信息,然后通过保存按钮来保存数据。
注意:系统一开始创建的admin用户是没法修改的, 因为关键参数手机字段在前端是禁止修改的,admin用户添加的时候没有写入手机号码,所以提交的时候这个字段是空的,数据无法保存,可以通过数据库先添加下手机号码。
5 修改用户密码
用户管理页表格最后一列中间一个按钮是用来修改密码的按钮,管理员可以通过这个按钮来修改对应用户的密码。
5.1 创建PasswordChangeForm
在sandboxMP/apps/system/forms.py中创建PasswordChangeForm用来验证密码数据:
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("两次密码输入不一致")
实现思路:
1、使用django表单功能对用户两次输入的密码进行验证,包括空值验证和长度验证
2、通过重写clean()方法类定义额外的验证功能,判断两次输入密码如果不一样则触发ValidationError
5.2 密码修改视图
from .forms import PasswordChangeForm 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 = '<li>.*?<ul class=.*?><li>(.*?)</li>' 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')
实现思路:
1、通过get()方法返回用户修改的模板页
2、通过post()方法来验证保存新的密码信息
3、通过request.POST获取前端传递过来的用户id,通过id查找用户模型中的实例
4、通过form.is_valid()方法验证密码有效性
5、调用set_password()方法修改用户密码
6、调用save()方法保存密码信息到数据库
7、返回执行结果
5.3 修改密码URL配置
在sandboxMP/apps/system/urls.py文件中添加修改密码的url:
urlpatterns = [ '''原有内容省略''' path('basic/user/password_change/', views_user.PasswordChangeView.as_view(), name='basic-user-password_change'), ]
5.4 模板配置
1、新建sandboxMP/templates/system/users/passwd_change.html, 内容如下:
{% extends 'base-layer.html' %} {% load staticfiles %} {% block css %} {% endblock %} {% block main %} <div class="box box-danger"> <form class="form-horizontal" id="addForm" method="post"> {% csrf_token %} <input type="hidden" name='id' value="{{ user.id }}"/> <input type="hidden" name='user' value="save"/> <div class="box-body"> <fieldset> <legend> <h4>基本信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">姓名</label> <div class="col-sm-3"> <input class="form-control" name="name" type="text" readonly="readonly" value="{{ user.name }}"/> </div> <label class="col-sm-2 control-label">用户名</label> <div class="col-sm-3"> <input type="text" class="form-control" name="username" readonly="readonly" value="{{ user.username }}"/> </div> </div> <h4>密码信息</h4> </legend> <div class="form-group has-feedback"> <label class="col-sm-2 control-label">密码</label> <div class="col-sm-3"> <input class="form-control" name="password" type="password" value=""/> </div> <label class="col-sm-2 control-label">确认密码</label> <div class="col-sm-3"> <input class="form-control" name="confirm_password" type="password" value=""/> </div> </div> </fieldset> </div> <div class="box-footer "> <div class="row span7 text-center "> <button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button> <button type="button" id="btnSave" class="btn btn-info margin-right ">确定</button> </div> </div> </form> </div> {% endblock %} {% block javascripts %} <script src="{% static 'plugins/combo-select/jquery.combo.select.js' %}"></script> <script src="{% static 'bootstrap/js/bootstrap-datetimepicker.js' %}"></script> <script type="text/javascript"> $("#btnSave").click(function () { var data = $("#addForm").serialize(); $.ajax({ type: $("#addForm").attr('method'), url: "{% url 'system:basic-user-password_change' %}", data: data, cache: false, success: function (msg) { if (msg.status == 'success') { layer.alert('密码修改成功!', {icon: 1}, function (index) { parent.layer.closeAll(); }); } else if (msg.status == 'fail') { layer.alert(msg.password_change_form_errors, {icon: 5}); //$('errorMessage').html(msg.message) } return; } }); }); /*点取消刷新页面*/ $("#btnCancel").click(function () { window.location.reload(); }) </script> {% endblock %}
注意:密码和确认密码的input标签中name字段值是和form里面定义的一样。
2、修改用户管理页面模板sandboxMP/templates/system/users/user.html,在{% block javascripts %}标签中添加密码按钮绑定的函数:
// 新增内容放到doUpdate()函数后面 function doChangepasswd(id) { layer.open({ type: 2, title: '修改密码', shadeClose: false, maxmin: true, area: ['850px', '350px'], content: ["{% url 'system:basic-user-password_change' %}" + '?id=' + id, 'no'], end: function () { oDataTable.ajax.reload(); } }); }
在项目中我们弹窗组建使用的是layer,一个jquery弹窗组建,有关layer更多使用方法可以参考官方网站:https://layer.layui.com/
6、用户启用、禁用和删除
用户的启用、禁用和删除(支持批量操作),这个三部分功能的实现是一样的,所以这里把它们放到一起。
6.1 视图实现
打开sandboxMP/apps/system/views_user.py, 添加如下内容
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')
这三个视图实现的逻辑是一样的,首先通过request.POST获取前端提交过来的一组用户id,然后进行批量查找后进行删除或更新用户状态。
为了更多的让大家了解django QuerySet方法,上面三个视图中使用了两种方法进行批量查找: 1、User.objects.filter(id__in=id_list):使用filter方法查找数据时,在查找字段后加上双下划线和in,后面可以跟上列表,在给定的列表中进行查找,效果等同于SQL:SELECT...where in id_list; 2、extra(): 可以用来实现django查询语法难以表达的复杂的WHERE子句
6.2 URL配置
打开sandboxMP/apps/system/urls.py,添加新的URL:
urlpatterns = [ '''原有内容省略''' 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'), ]
6.3 模板配置
在用户管理的模板页中,已经添加了删除、启用、停用按钮,现在只需要给按钮绑定事件,将请求传递给对应的接口即可。
打开sandboxMP/templates/system/users/user.html,在{% block javascripts %}标签中添加如下代码:
// 以下代码添加到 doChangepasswd()函数后面 //checkbox全选 $("#checkAll").on("click", function () { if ($(this).prop("checked") === true) { $("input[name='checkList']").prop("checked", $(this).prop("checked")); $('#example tbody tr').addClass('selected'); } else { $("input[name='checkList']").prop("checked", false); $('#example tbody tr').removeClass('selected'); } }); //批量删除 $("#btnDelete").click(function () { if ($("input[name='checkList']:checked").length == 0) { layer.msg("请选择要删除的记录"); return; } var arrId = new Array(); $("input[name='checkList']:checked").each(function () { //alert($(this).val()); arrId.push($(this).val()); }); sId = arrId.join(','); layer.alert('确定删除吗?', { title: '提示' , icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸 , time: 0 //不自动关闭 , btn: ['YES', 'NO'] , yes: function (index) { layer.close(index); $.ajax({ type: "POST", url: "{% url 'system:basic-user-delete' %}", data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'}, cache: false, success: function (msg) { if (msg.result) { layer.alert("操作成功"); oDataTable.ajax.reload(); } else { //alert(msg.message); layer.alert("操作失败"); } return; } }); } }); }); //批量启用 $("#btnEnable").click(function () { if ($("input[name='checkList']:checked").length == 0) { layer.msg("请选择要启用的用户"); return; } var arrId = new Array(); $("input[name='checkList']:checked").each(function () { //alert($(this).val()); arrId.push($(this).val()); }); sId = arrId.join(','); layer.alert('确定启用吗?', { title: '提示' , icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸 , time: 0 //不自动关闭 , btn: ['YES', 'NO'] , yes: function (index) { layer.close(index); $.ajax({ type: "POST", url: "{% url 'system:basic-user-enable' %}", data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'}, cache: false, success: function (msg) { if (msg.result) { layer.alert("启用用户成功", {icon: 1}); oDataTable.ajax.reload(); } else { //alert(msg.message); layer.alert("启用用户失败", {icon: 5}); } return; } }); } }); }); //批量禁用 $("#btnDisable").click(function () { if ($("input[name='checkList']:checked").length == 0) { layer.msg("请选择要禁用的用户"); return; } var arrId = new Array(); $("input[name='checkList']:checked").each(function () { //alert($(this).val()); arrId.push($(this).val()); }); sId = arrId.join(','); layer.alert('确定禁用吗?', { title: '提示' , icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸 , time: 0 //不自动关闭 , btn: ['YES', 'NO'] , yes: function (index) { layer.close(index); $.ajax({ type: "POST", url: "{% url 'system:basic-user-disable' %}", data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'}, cache: false, success: function (msg) { if (msg.result) { layer.alert("禁用用户成功", {icon: 1}); oDataTable.ajax.reload(); } else { //alert(msg.message); layer.alert("禁用用户失败", {icon: 5}); } return; } }); } }); }); //删除单个用户 function doDelete(id) { layer.alert('确定删除吗?', { title: '提示' , icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸 , time: 0 //不自动关闭 , btn: ['YES', 'NO'] , yes: function (index) { layer.close(index); $.ajax({ type: "POST", url: "{% url 'system:basic-user-delete' %}", data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'}, //防止post数据时报 csrf_token 403 cache: false, success: function (msg) { if (msg.result) { layer.alert('删除成功', {icon: 1}); oDataTable.ajax.reload(); } else { //alert(msg.message); layer.alert('删除失败', {icon: 5}); } return; } }); } }); }
项目中大部分操作都是通过jquery-ajax和后端进行交互的,这里只需要掌握项目中的几种常用写法,在使用的时候会套用即可,当然如果你对jquery感兴趣,可以深入学习下。
运行项目,访问用户管理页就可以对用户进行批量删除、启用、停用操作了,你也可以通过表格中最后一列删除按钮来删除单个用户。
7 用户状态过滤
在用户管理也提供了一个用户状态过滤功能,可以快速通过状态过滤用户信息
7.1 修改UserListView视图
打开sandboxMP/apps/system/views.user.py,用户信息都是通过UserListView视图接口获取的,视图内容如下:
class UserListView(LoginRequiredMixin, View): def get(self, request): fields = ['id', 'name', 'gender', 'mobile', 'email', 'department__name', 'post', 'superior__name', 'is_active'] ret = dict(data=list(User.objects.values(*fields))) return HttpResponse(json.dumps(ret), content_type='application/json')
UserListView 通过QuserSet查询返回了用户模型中所有实例信息,想要查询状态为启用或者禁用的用户信息,可以通过filter方法来实现,下面是改写后的代码:
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')
判断request.POST中是否包含'select'数据,如果包含则获取'select'值赋值给'is_active'(数据库中用户状态字段),通过filter方法查询符合的用户信息。
7.2 模板配置
1、先来看下sandboxMP/templates/system/users/user.html中用户状态过滤选择框的代码内容(user.html第42行开始):
<div class="btn-group pull-right"> <form class="form-inline"> <div class="form-group"> <label>用户状态:</label> <select id="select" name="select" class="form-control"> <option style='text-align:center' value=''>-----所有-----</option> <option value="True">启用</option> <option value="False">禁用</option> </select> </div> </form> </div>
定义了一个form表单,表单提供过了一个select选择框,id和name为select,其中name名称也就是我们在后台通过request.GET['select']捕获该字段值时使用的内容,option标签中value值是实际传递到后台的数据。
2、打开sandboxMP/templates/system/users/user.html,在{% block javascripts %}标签中添中 datatables初始化配置的 ajax:代码段中添加下面内容:
$(function () { oDataTable = initTable(); function initTable() { var oTable = $('#dtbList').DataTable($.extend(true, {}, DATATABLES_CONSTANT.DATA_TABLES.DEFAULT_OPTION, { ajax: { "url": "{% url 'system:basic-user-list' %}", // 下面三行是新增加的内容, "data": function (d) { d.select = $("#select").val(); } },
上面的ajax中 url为请求的地址,data为传递的参数,这个参数是通过$("#select").val()来获取用户状态过滤框中的内容,这个时候选择用户状态,表格中的数据是不会变化的,我们还需要将请求发送给后台,重新获取用户列表。
3、监控select选择框的变化 将下面代码放到user.html文件{% block javascripts %}标签中doDelete()函数后面
$("#select").change(function () { //alert($("#select").val()) oDataTable.ajax.reload(); });
这样我们只要在用户状态框中选中用户状态,通过$("#select").change()方法监控到select状态变化了刷新datatables,这样datatables就通过ajax重新去用列表接口请求数据,同时传递select值,后端视图接收到请求,获取request.GET['select']值,通过filter查询返回数据结果。到这里完整的用户操作就全部实现了,通过上面方法你也可以尝试下做一些组合查询功能的实现。
来源:https://www.cnblogs.com/jameslove/p/11043810.html