这节将会介绍用户管理功能的实现,包括用户信息浏览、添加、删除和修改等操作,从这一节开始很多功能实现都是和前面组织架构管理功能实现类似,所以通过这一节我们将完整实现用户管理功能。
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