day77:luffy:导航栏的实现&DjangoRestFramework JWT&多条件登录
目录
1.导航栏的实现
1.设计导航栏的model模型类
apps/home/models.py
class Nav(BaseModel):
"""导航菜单模型"""
POSITION_OPTION = (
(1, "顶部导航"),
(2, "脚部导航"),
)
title = models.CharField(max_length=500, verbose_name="导航标题")
link = models.CharField(max_length=500, verbose_name="导航链接")
position = models.IntegerField(choices=POSITION_OPTION, default=1, verbose_name="导航位置")
is_site = models.BooleanField(default=False, verbose_name="是否是站外地址") class Meta:
db_table = 'ly_nav'
verbose_name = '导航菜单'
verbose_name_plural = verbose_name # 自定义方法[自定义字段或者自定义工具方法]
def __str__(self):
return self.title
2.在Xadmin中注册导航栏模型类
apps/home/adminx.py
# 导航菜单
class NavModelAdmin(object):
list_display=["title","link","is_show","is_site","position"]
xadmin.site.register(models.Nav, NavModelAdmin)
执行数据库迁移同步指令
python manage.py makemigrations
python manage.py migrate
3.在Xadmin中添加一些导航栏数据

4.注册导航栏的URL
from django.urls import path,re_path
from . import views
urlpatterns = [
......
path("nav/header/", views.HeaderNavListAPIView.as_view()), ]
5.新建导航栏的视图类
from .models import Nav
from .serializers import NavModelSerializer
class HeaderNavListAPIView(ListAPIView):
"""顶部导航菜单视图"""
queryset = Nav.objects.filter(is_show=True, is_deleted=False,position=1).order_by("-orders","-id")[:constants.HEADER_NAV_LENGTH]
serializer_class = NavModelSerializer '''
position=1代表是顶部导航,position=2代表是底部导航
'''
6.新建导航栏的序列化器
from .models import Nav
class NavModelSerializer(serializers.ModelSerializer):
"""导航菜单序列化器"""
class Meta:
model = Nav
fields = ["id","title","link","is_site"]
7.调试一下看是否能获取到数据

8.编写导航栏vue组件的代码
1.Vheader组件加载时,通过axios的get请求去请求后端的数据
<script>
export default {
name: "Header",
data(){
return {
......
nav_data_list:[],
}
},
methods:{
......
get_nav_data(){
this.$axios.get(`${this.$settings.Host}/home/nav/header/`,).then((res)=>{
this.nav_data_list = res.data;
})
.catch((error)=>{
console.log(error);
})
}
}, // 注意:一定要写created方法来触发函数
created() {
this.get_nav_data();
} } </script>
2.后端数据已经拿到,接下来要在前端展示出来
思路:for循环取出导航栏所有的数据,判断导航栏的标题是站内跳转还是站外跳转
如果是站内跳转就用router-link,如果是站外跳转就用a href
value就是你从后端获取到的数据 value.link value.is_site value.title就可以取到相应的值
<el-col class="nav" :span="10">
<el-row>
<el-col :span="3" v-for="(value,index) in nav_data_list" :key="index"> <a :href="value.link" class="active" v-if="value.is_site">{{value.title}}</a> <router-link :to="value.link" v-else>{{value.title}}</router-link> </el-col> </el-row> </el-col>
2.登录前戏:用户表初始化
1.Login.vue
<template>
<div class="login box">
<img src="../../static/img/Loginbg.3377d0c.jpg" alt="">
<div class="login">
<div class="login-title">
<img src="../../static/img/Logotitle.1ba5466.png" alt="">
<p>帮助有志向的年轻人通过努力学习获得体面的工作和生活!</p>
</div>
<div class="login_box">
<div class="title">
<span @click="login_type=0">密码登录</span>
<span @click="login_type=1">短信登录</span>
</div>
<div class="inp" v-if="login_type==0">
<input v-model = "username" type="text" placeholder="用户名 / 手机号码" class="user">
<input v-model = "password" type="password" name="" class="pwd" placeholder="密码">
<div id="geetest1"></div>
<div class="rember">
<p>
<input type="checkbox" class="no" name="a" v-model="remember"/>
<span>记住密码</span>
</p>
<p>忘记密码</p>
</div>
<button class="login_btn" @click="loginHandle">登录</button>
<p class="go_login" >没有账号 <span>立即注册</span></p>
</div>
<div class="inp" v-show="login_type==1">
<input v-model = "username" type="text" placeholder="手机号码" class="user">
<input v-model = "password" type="text" class="pwd" placeholder="短信验证码">
<button id="get_code">获取验证码</button>
<button class="login_btn">登录</button>
<p class="go_login" >没有账号 <span>立即注册</span></p>
</div>
</div>
</div>
</div>
</template> <script>
export default {
name: 'Login',
data(){
return {
login_type: 0,
username:"",
password:"",
remember:false,
}
}, methods:{
loginHandle(){
this.$axios.post(`${this.$settings.Host}/users/login/`,{
username:this.username,
password:this.password, }).then((res)=>{
console.log(res);
// console.log(this.remember);
if (this.remember){
localStorage.token = res.data.token;
localStorage.username = res.data.username;
localStorage.id = res.data.id;
sessionStorage.removeItem('token');
sessionStorage.removeItem('username');
sessionStorage.removeItem('id'); }else {
sessionStorage.token = res.data.token;
sessionStorage.username = res.data.username;
sessionStorage.id = res.data.id;
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('id');
}
this.$router.push('/'); }).catch((error)=>{
this.$alert('用户名password error', 'error msg', {
confirmButtonText: '确定', });
}) } }, };
</script> <style scoped>
.box{
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.box img{
width: 100%;
min-height: 100%;
}
.box .login {
position: absolute;
width: 500px;
height: 400px;
top: 0;
left: 0;
margin: auto;
right: 0;
bottom: 0;
top: -338px;
}
.login .login-title{
width: 100%;
text-align: center;
}
.login-title img{
width: 190px;
height: auto;
}
.login-title p{
font-family: PingFangSC-Regular;
font-size: 18px;
color: #fff;
letter-spacing: .29px;
padding-top: 10px;
padding-bottom: 50px;
}
.login_box{
width: 400px;
height: auto;
background: #fff;
box-shadow: 0 2px 4px 0 rgba(0,0,0,.5);
border-radius: 4px;
margin: 0 auto;
padding-bottom: 40px;
}
.login_box .title{
font-size: 20px;
color: #9b9b9b;
letter-spacing: .32px;
border-bottom: 1px solid #e6e6e6;
display: flex;
justify-content: space-around;
padding: 50px 60px 0 60px;
margin-bottom: 20px;
cursor: pointer;
}
.login_box .title span:nth-of-type(1){
color: #4a4a4a;
border-bottom: 2px solid #84cc39;
} .inp{
width: 350px;
margin: 0 auto;
}
.inp input{
border: 0;
outline: 0;
width: 100%;
height: 45px;
border-radius: 4px;
border: 1px solid #d9d9d9;
text-indent: 20px;
font-size: 14px;
background: #fff !important;
}
.inp input.user{
margin-bottom: 16px;
}
.inp .rember{
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
margin-top: 10px;
}
.inp .rember p:first-of-type{
font-size: 12px;
color: #4a4a4a;
letter-spacing: .19px;
margin-left: 22px;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
/*position: relative;*/
}
.inp .rember p:nth-of-type(2){
font-size: 14px;
color: #9b9b9b;
letter-spacing: .19px;
cursor: pointer;
} .inp .rember input{
outline: 0;
width: 30px;
height: 45px;
border-radius: 4px;
border: 1px solid #d9d9d9;
text-indent: 20px;
font-size: 14px;
background: #fff !important;
} .inp .rember p span{
display: inline-block;
font-size: 12px;
width: 100px;
/*position: absolute;*/
/*left: 20px;*/ }
#geetest{
margin-top: 20px;
}
.login_btn{
width: 100%;
height: 45px;
background: #84cc39;
border-radius: 5px;
font-size: 16px;
color: #fff;
letter-spacing: .26px;
margin-top: 30px;
}
.inp .go_login{
text-align: center;
font-size: 14px;
color: #9b9b9b;
letter-spacing: .26px;
padding-top: 20px;
}
.inp .go_login span{
color: #84cc39;
cursor: pointer;
}
</style>
2.在index.js中添加Login组件的路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Login from '@/components/Login' Vue.use(Router) export default new Router({
mode:'history',
routes: [
......
{
path:'/user/login',
component: Login
}
]
})
3.将导航栏Vheader组件的登录按钮设置一个跳转连接
<!-- 点击登录按钮 跳转到登录的页面:/user/login -->
<router-link to="user/login"><button class="signin">登录</button></router-link>
4.将导航栏Vheader组件中的登录状态token改为false
将token值改为false,让首页是未登录状态,这样才能显示登录注册的按钮
<script>
export default {
name: "Header",
data(){
return { token:false,
}
5.创建users应用
1.创建user应用
python manage.py startapp users
2.在dev.py配置文件的INSTALL_APPS加上users
3.为user应用配置总路由
path('users/',include("users.urls")),
6.创建user模型类
在django的auth模块是自带一个用户表的,我们写用户表时可以继承django自带的用户表.
from django.db import models
from django.contrib.auth.models import AbstractUser # AbstractUser是django自带的一个用户表 class User(AbstractUser):
phone = models.CharField(max_length=16, null=True, blank=True)
wechat = models.CharField(max_length=16, null=True, blank=True) class Meta:
db_table = 'ly_user'
verbose_name = '用户表'
verbose_name_plural = verbose_name
7.关于用户表要做的两件事
我们想做的两件事:
1.使用django自带的Abstractuser表加自己的扩展字段作为项目的用户表【已经完成】
2.后台登录Xadmin的后台管理系统的那些用户不再使用原来django自带的用户表,而是使用自己创建的用户表
Xadmin使用自己创建的用户表
需要在setting中配置一下,让django别再使用自带的那个user表,而是使用我们自己编写的user表
dev.py
AUTH_USER_MODEL = 'users.User'
注意:如果现在执行数据库迁移指令会报错。
上面的操作应该建立在第一次数据库迁移之前。
但是我们之前已经进行过几次数据库迁移了。所以需要做以下几步操作:
// 0. 先把现有的数据库导出备份,然后清掉数据库中所有的数据表。 // 1. 把开发者创建的所有子应用下面的migrations目录下除了__init__.py以外的所有迁移文件,只要涉及到用户的,一律删除,并将django-migrations表中的数据全部删除。 // 2. 把django.contrib.admin.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。 // 3. 把django.contrib.auth.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。 // 4. 把reversion.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。这个不在django目录里面,在site-packages里面,是xadmin安装的时候带的,它会记录用户信息,也需要删除 // 5. 把xadmin.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。 // 6. 删除我们当前数据库中的所有表 // 7. 接下来,执行数据迁移(makemigrations和migrate),回顾第0步中的数据,将数据导入就可以了,以后如果要修改用户相关数据,不需要重复本次操作,直接数据迁移即可。//
这些完成之后,创建一个超级用户
python3 manage.py createsuperuser
会发现xadmin超级用户的用户名和密码存到了自己的ly_user表中。我们达到了目的。
3.DjangoRestFramework JWT
1.jwt的安装
pip install djangorestframework-jwt -i https://mirrors.aliyun.com/pypi/simple/
2.jwt的配置
dev.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
注:JWT_EXPIRATION_DELTA 指明token的有效期
3.为jwt配置路由
apps/users/urls.py
为jwt设置路由 ,用来前端访问url 获取jwt token值
from rest_framework_jwt.views import obtain_jwt_token
from django.urls import path urlpatterns = [
path(r'login/', obtain_jwt_token),
]
访问 www.lyapi.com:8001/users/login 添加用户名和密码字段 post提交后可得token值,如图所示

4.前端通过axios请求获取后端传过来的token
用户在登录界面输入用户名和密码,点击登录 往后台发送post请求
后端返回给前端一个token值,前端接收token值并存储起来
1.获取用户在前端输入的用户名和密码
methods:{
loginHandle(){
this.$axios.post(`${this.$settings.Host}/users/login/`,{
username:this.username,
password:this.password,
2.为login.vue的登录按钮绑定一个LoginHandle事件
<button class="login_btn" @click="loginHandle">登录</button>
这个时候我们访问www.lycity.com/user/login 输入用户名和密码
查看console 可以看到data中已经存放着token值 ==>这个时候前端已经拿到token值
那么接下来的问题是前端如何将token值保存起来呢?
3.引入session storage 和 local storage===>实现前端对token的存储
session storage和local storage的区别
session storage 是临时存储 关闭浏览器就没有了
local storage 是永久存储
5.前端存储token值
登录页面有一个记住密码的选项,我们就可以使用session storage 和 loca storage 来做一些事情
1.将记住密码checkbox设置为v-model
当用户勾选/没有勾选 记住密码 这个选项时,remember的值发生改变
Login.vue
<p>
<input type="checkbox" class="no" name="a" v-model="remember"/>
<span>记住密码</span>
</p>
2.我们先让remember的值默认为false
export default {
name: 'Login',
data(){
return {
...
remember:false,
}
},
当用户勾选记住密码 remember的值为true 反之为false
所以根据 remember的值 就可以做if判断了
6.扩展默认的返回值
默认的返回值仅有token值 ,但是我们还需在返回值中增加username和id ,方便在客户端页面中显示当前登录用户。
所以需要扩展一下
通过修改该视图的返回值可以完成我们的需求
user/utils.py
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定义jwt认证成功返回数据
"""
return {
'token': token,
'id': user.id,
'username': user.username
}
除了扩展一下这个,还要修改一下下面的配置
settings/dev.py
# JWT
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
这个配置的作用是:告诉jwt 响应结果的时候,按照我指定的 jwt_response_payload_handler这个方法来返回
jwt 认证成功返回了三个字段 token id username 响应给前端
前端也应该接収这几个字段
methods:{
loginHandle(){
this.$axios.post(`${this.$settings.Host}/users/login/`,{
username:this.username,
password:this.password,
}).then((res)=>{
console.log(res);
if (this.remember){
localStorage.token = res.data.token;
localStorage.username = res.data.username;
localStorage.id = res.data.id;
...
}else {
sessionStorage.token = res.data.token;
sessionStorage.username = res.data.username;
sessionStorage.id = res.data.id;
...
}
final
'''
实现思路:
如果用户标选了记住密码 就将token值存储在local storage中
如果用户没有标记 记住密码 就将token值存储在session storage中
''' methods:{
loginHandle(){
this.$axios.post(`${this.$settings.Host}/users/login/`,{
username:this.username,
password:this.password, }).then((res)=>{
console.log(res);
// console.log(this.remember);
if (this.remember){
localStorage.token = res.data.token;
localStorage.username = res.data.username;
localStorage.id = res.data.id;
sessionStorage.removeItem('token');
sessionStorage.removeItem('username');
sessionStorage.removeItem('id'); }else {
sessionStorage.token = res.data.token;
sessionStorage.username = res.data.username;
sessionStorage.id = res.data.id;
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('id');
}
this.$router.push('/');
Tip:关于session storage和local storage的使用方法
sessionStorage.变量名 = 变量值 // 保存数据
sessionStorage.setItem("变量名","变量值") // 保存数据
sessionStorage.变量名 // 读取数据
sessionStorage.getItem("变量名") // 读取数据
sessionStorage.removeItem("变量名") // 清除单个数据
sessionStorage.clear() // 清除所有sessionStorage保存的数据 localStorage.变量名 = 变量值 // 保存数据
localStorage.setItem("变量名","变量值") // 保存数据
localStorage.变量名 // 读取数据
localStorage.getItem("变量名") // 读取数据
localStorage.removeItem("变量名") // 清除单个数据
localStorage.clear() // 清除所有sessionStorage保存的数据
4.多条件登录
扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。
我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。
官方说:修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。
authenticate(self, request, username=None, password=None, **kwargs)方法的参数说明:
request 本次认证的请求对象
username 本次认证提供的用户账号
password 本次认证提供的密码
我们想要让用户既可以以用户名登录,也可以以手机号登录,那么对于authenticate方法而言,username参数即表示用户名或者手机号。
重写authenticate方法的思路:
根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
若查找到User对象,调用User对象的check_password方法检查密码是否正确
users/utils.py
def get_user_by_account(account):
"""
根据帐号获取user对象
:param account: 账号,可以是用户名,也可以是手机号
:return: User对象 或者 None
"""
try: # 查询用户名是否存在
user = models.User.objects.filter(Q(username=account)|Q(mobile=account)).first()
except models.User.DoesNotExist:
return None
else:
return user # 存在返回用户名 from . import models
from django.db.models import Q
from django.contrib.auth.backends import ModelBackend
class UsernameMobileAuthBackend(ModelBackend):
"""
自定义用户名或手机号认证
""" def authenticate(self, request, username=None, password=None, **kwargs):
user = get_user_by_account(username)
#if user is not None and user.check_password(password) :
if user is not None and user.check_password(password) and user.is_authenticated:
#user.is_authenticated是看他有没有权限的,这里可以不加上它
return user
在配置文件settings/dev.py中告知Django使用我们自定义的认证后端
AUTHENTICATION_BACKENDS = [
'users.utils.UsernameMobileAuthBackend',
]
以上就实现了我们通过用户名或者手机号的一个多条件登录。
5.登录状态的判断和退出登录
在Vheader组件中添加如下内容
<script>
export default {
name: "Header",
data() {
return {
.....
token: true,
..... }
},
methods: {
......
check_login() {
// 二者只要其中之一有值就是true 代表用户已经登录
this.token = localStorage.token || sessionStorage.token;
}, logout() {
// 用户注销时,无论是临时存储还是永久存储都应该清除掉
sessionStorage.removeItem('token');
sessionStorage.removeItem('username');
sessionStorage.removeItem('id');
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('id');
this.check_login();
}
}, created() {
this.get_nav_data();
}
day77:luffy:导航栏的实现&DjangoRestFramework JWT&多条件登录的更多相关文章
- SAP CRM 将组件整合至导航栏中
到现在,我们已经可以让组件独立地显示.我们只是运行它.让它显示在Web UI中.让我们把组件整合进导航栏,使我们可以在正常登录Web UI时访问它. 步骤一: 为你的UI组件主窗体创建一个内向插件. ...
- 谈谈一些有趣的CSS题目(八)-- 纯CSS的导航栏Tab切换方案
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- ABP(现代ASP.NET样板开发框架)系列之22、ABP展现层——导航栏设置
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之22.ABP展现层——导航栏设置 ABP是“ASP.NET Boilerplate Project (ASP.NE ...
- 原生JS实现全屏切换以及导航栏滑动隐藏及显示——重构前
思路分析: 向后滚动鼠标滚轮,页面向下全屏切换:向前滚动滚轮,页面向上全屏切换.切换过程为动画效果. 第一屏时,导航栏固定在页面顶部,切换到第二屏时,导航条向左滑动隐藏.切换回第一屏时,导航栏向右滑动 ...
- css3制作炫酷导航栏效果
今天主要利用hover选择器.鼠标滑过查看效果. 一.普通导航栏 Home Content Service Team Contact 对于这种普通的导航栏,只是鼠标滑过的时候颜色会变,所以思路变得很简 ...
- 分别用ToolBar和自定义导航栏实现沉浸式状态栏
一.ToolBar 1.在build.gradle中添加依赖,例如: compile 'com.android.support:appcompat-v7:23.4.0' 2.去掉应用的ActionBa ...
- jquery版滑块导航栏
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <script src ...
- 美团HD(2)-设置导航栏内容
DJHomeViewController.m #import "DJHomeViewController.h" #import "DJConstantValue.h&qu ...
- 通过html和css做出下拉导航栏的效果
通过观察了百度的首页,对于更多产品一栏,觉得可以不涉及JS便可写出下拉导航栏的效果 1.先设计出大体的框架 <div class="nav"> <ul> & ...
随机推荐
- python的快捷键
常用快捷键 1.Ctrl + Enter:在下方新建行但不移动光标 2.Shift + Enter:在下方新建行并移到新行行首 3.Ctrl + /:注释(取消注释)选择的行 4.Ctrl + Alt ...
- spring:bean的生命周期
1.spring中bean的生命周期 (1)概念 在spring框架中,所有的bean对象都有生命周期,就是指bean的创建.初始化.服务.销毁的一个过程. (2)bean的生命周期 bean的定义 ...
- 重启springboot
前言:springboot项目开发时,会遇到项目重新启动的情况.在百度上资料比较零碎需要整理,实践时需要踩坑,自己在项目中已经实现的功能拿出来与大家分享.希望每一位coder能在编程的路上少走一些弯路 ...
- mysql-9-limit
#进阶9:分页查询 /* 当要显示的数据,一页显示不全,需要分页提交sql请求 SELECT FROM JOIN ON WHERE GROUP BY HAVING ORDER BY LIMIT off ...
- MDK内的KEEP关键字以及$$Base $$Limit
使用mdk编程,假如有一个有用的函数你定义了但是没有显式的调用,mdk在默认方式下,将会把这个函数从整个程序总删除掉,以节省ROM. 比如,你在ROM的0x00002000处定位了一个函数,假设为vo ...
- centos7修改ssh端口及添加ssh监听端口
ssh 修改默认端口 [root@node-1 ~]# vi /etc/ssh/sshd_config 修改port 为 5522 重启[root@node-1 ~]# systemctl resta ...
- Lesktop开源IM移动端:接入LayIM移动端UI
在<开源企业即时通讯和在线客服>中已介绍了Lesktop的桌面模式和Web模式,但是没有移动端.评论中 dotnetcms.org工作室 提到了LayIM,看了一下官网的演示和文档,如果用 ...
- spring框架bean注入
今天学习了spring框架,刚刚入门简单的了解了spring并学习了bean的注入IOC:IOC(Inversion of Control,控制反转)不是什么技术,而是一种设计思想.它的目的是指导我们 ...
- 快速解读linq语法
在说LINQ之前必须先说说几个重要的C#语言特性 一:与LINQ有关的语言特性 1.隐式类型 (1)源起 在隐式类型出现之前, 我们在声明一个变量的时候, 总是要为一个变量指定他的类型 甚至在fore ...
- 基于python实现链式栈
""" 链式栈 linkstack.py 思路分析: 1.源于链表结构 2.封装栈的操作方法(入栈,出栈,栈空,栈顶) 3.链表的开头作为栈顶(不用每次遍历,效率高,怎样 ...