一、前面实现的是DjangoAdmin实现的自定义用户认证管理,现在自己来实现管理功能,即在mytestapp中增加用户认证管理功能。
在UserProfile的model中,对password字段增加help_text属性:
password = models.CharField(_('password'), max_length=128,help_text=mark_safe('<a href="password/">重置密码</a>.'))
然后在用户修改页面进行显示: {{ f }}{{ f.help_text }}<span>{{ f.errors }}</span>
然后点击这个重置密码链接,跳转到一个密码修改页面。
在urls中增加对应的路由项:
path('<str:app_name>/<str:table_name>/<int:id_num>/change/password/',views.passwd_reset,name='passwd_reset'),
编写视图函数passwd_reset:
def passwd_reset(req,app_name,table_name,id_num):
admin_class = mytestapp_admin.enable_admins[app_name][table_name]
model_form_class = myutils.create_model_form(req, admin_class)
obj = admin_class.model.objects.get(id=id_num)
errors ={}
if req.method == "POST":
_password = req.POST.get("password")
_password2 = req.POST.get("password2")
if _password == _password2:
obj.set_password(_password) # 借助模型中的函数
obj.save()
return redirect(req.path.rstrip("password/"))
else: # 两次密码输入不相同,报错
errors['invalid_password'] = "two password not same"
return render(req,'mytestapp/passwd_reset.html',{'user_obj':obj,'errors':errors})
前端页面passwd_reset.html:
{% extends 'base.html' %}
{% load tags %}
{% block mybody %}
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#">{{ request.user.userprofile.name }}</a>
</li>
</ul>
</nav>
<div class="container-fluid" style="margin-top: 20px;margin-left: 50px;">
<div class="d-flex p-2 bd-highlight">修改用户【{{ user_obj }}】密码</div>
<div class="d-inline-flex p-2 bd-highlight">
<form method="post">{% csrf_token %}
<div class="form-group">
<label for="exampleInputEmail1">新密码</label>
<input type="password" class="form-control" name="password">
</div>
<div class="form-group">
<label for="exampleInputPassword1">重复密码</label>
<input type="password" class="form-control" name="password2">
</div>
<div>
<ul>
{% for k,v in errors.items %}
<li>{{ k }}:{{ v }}</li>
{% endfor %}
</ul>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</body>
{% endblock %}
二、在ModelForm中排除部分字段:
在modelform形成过程中,在Meta中使用exclude进行排除。
class Meta: model = admin_class.model fields = "__all__" exclude = ('qq',)
这样配置后,在前端就不会形成qq这个字段了。使用exclude排除,主要用在增加新记录时,如果某些字段值是数据库插入时自动生成的,如创建日期等,不需要在前端显示添加,或修改时,有些字段设置为readonly,在前端使用disabled进行了输入禁止,但是在提交时,这个字段不提交,就形成null值,与原值不相等导致校验失败,这时可以直接使用exclude排除这些字段,不显示就可以了,相当于update语句不修改这些字段。
做成配置的,在AdminClass中增加exclude_fields=['qq',]
在生成ModelForm模板中,
class Meta: model = admin_class.model fields = "__all__" if admin_class.exclude_fields: exclude = admin_class.exclude_fields
三、在显示记录列表中,是按照AdminClass中的list_display中的字段显示的,这些字段都是在数据库表中存在的,如果我们要添加一个数据库表中没有的字段,如何实现?如增加一个enrollment字段,内容显示报名,并是一个链接,点击后跳转到其他页面。
class CustomerAdmin(BaseAdmin):
list_display = ['qq','name','phone','source','consultant','referral_from','consult_course','tags','status','enroll']
list_per_page = 4
list_filter = ['qq','source','status','consult_course','tags']
list_search = ['qq','name']
filter_horizontal = ['tags']
readonly_fields = ['qq','consultant','tags']
actions = ['delete_action',]
def enroll(self):
return "what" # enroll字段显示的值
....
添加如上配置后,显示客户表时,会报错:
使用try捕获错误进行处理:
@register.simple_tag
def build_table_row(obj,admin_class,url_path):
row_ele = ""
for row_data in obj:
row_ele = row_ele +'<tr><td colspan="6"><input my_id="obj_checkbox" type="checkbox" value="%s"</td>'%(row_data.id)
for index_ele,column in enumerate(admin_class.list_display):
try:
field_obj = row_data._meta.get_field(column)
if field_obj.choices:
column_data = getattr(row_data,"get_%s_display"%column)()
else:
column_data = getattr(row_data,column)
field_obj1 = getattr(row_data,column)
if hasattr(field_obj1,'values'):
s = ""
dic1 = field_obj1.values()
for i in range(dic1.count()):
for v in dic1[i].values():
s = s + str(v) + ';'
column_data = s
if index_ele == 0: # 若果是第一列,则加上a标签,可以跳转到修改页
row_ele += '<td colspan="6"><a href="%s%s/change/">%s</a></td>'%(url_path,row_data.id,column_data)
else:
row_ele += "<td colspan='6'>%s</td>"%column_data
except FieldDoesNotExist as e:
if hasattr(admin_class,column):
column_func = getattr(admin_class,column)
admin_class.instance = obj
# 将obj传递给admin_class的instance,这样就能取到对应行的对象的id,这里obj是QuerySet对象
column_data = column_func()
row_ele += "<td colspan='6'>%s</td>" % column_data
row_ele = row_ele + "</tr>"
print(row_ele)
return mark_safe(row_ele)
在admin_class中配置:
def enroll(self):
return "<a href='%s/enrollment/'>报名</a>"%self.instance[0].id
# 因为instance是QuerySet对象,对于每一行,只有一个对象,取[0],这样,形成的连接就是id/enrollment/,在路由项中增加对应的路由,跳转到对应view函数处理
点击报名:
增加对应的路由项和视图及前端模板,完成报名的功能。
四、自定义用户登录验证
前面使用的是DjangoAdmin的登录界面:
登陆后进入的是各种表的管理界面,现在要自定义登录界面,登陆后进入业务界面。
自己做一个登录界面:
{% extends 'base.html' %}
{% block mybody %}
<div class="container" >
<form class="form-signin col-sm-3">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input name="email" type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input name="password" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
</div>
{% endblock %}
增加路由项:path('account/login/',views.acc_login),
编写视图函数acc_login:基本框架:
def acc_login(req):
return render(req,'login.html')
此时,访问:http://127.0.0.1:8000/account/login/就可以访问到上面的自定义登录界面
修改完善:
前端
{% extends 'base.html' %}
{% block mybody %}
<div class="container" >
<form class="form-signin col-sm-3" method="post">{% csrf_token %}
<h1 class="h3 mb-3 font-weight-normal">PlswCRM</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input name="email" type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input name="password" type="password" id="inputPassword" class="form-control" placeholder="Password" required>
{% if errors %}
<span style="color: red">{{ errors.error }}</span>
{% endif %}
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
</div>
{% endblock %}
后端视图函数:
from django.shortcuts import render,redirect
from django.contrib.auth import authenticate,login
# 这里的authenticate和login是借助于django的认证和登录功能
def acc_login(req):
errors={}
if req.method == "POST":
_email = req.POST.get('email')
_password = req.POST.get('password')
user = authenticate(username=_email,password=_password)
print(user)
# authenticate接受用户名和密码,如果验证通过,则user为用户对象,否则为空
if user:
# 如果用户存在,即验证通过,需要将用户写入session中做保存,再次访问网站其他页面时使用
# 这个写session的动作,可以使用login()来实现
login(req,user) # 利用Django的login,实现用户写入session,即保存登录
# 验证、登录成功,转到首页
return redirect("/plcrm/")
else:
errors['error'] = "username or password is wrong"
return render(req,'login.html',{"errors":errors,})
对需要登录验证的视图函数增加验证:
如前面的mytestapp的各个视图函数,访问前需要进行实现登录验证,这时还是借助Django的装饰器来实现:
from django.contrib.auth.decorators import login_required
# Create your views here.
@login_required # 加上这个装饰器,就会在访问index函数时进行验证,如果没有登陆,则跳转到登录页面
def index(req):
print(mytestapp_admin.enable_admins['plcrm']['customer'].model)
return render(req,"mytestapp/index.html",{'table_list':mytestapp_admin.enable_admins})
在没有登陆的状态下访问mytestapp:
自动跳转到accounts/login/路径,这个是Django默认的登录路径,而我们自定义的是account/login/,这个选项在settings.py中进行配置:
LOGIN_URL = '/account/login/'
这时,就会自动跳转到自定义的/account/login/这个登录界面。
将其他的视图函数都加上@login_required,这样就实现了所有页面都需要登录认证的功能。
五、登录退出
在用户名称上增加下列框,
实现登录退出:<a class="dropdown-item" href="{% url 'acc_logout' %}">login out</a>
在路由项中增加:path('account/logout/',views.acc_logout,name='acc_logout'),
视图函数:
def acc_logout(req):
logout(req)
return redirect('/account/login/')
logout也是借助如django的功能。