Django框架课
6.创建用户系统
后端部分
可以通过网址/admin
进入管理后台,可以管理用户。如果要创建一个管理员,可以在acapp/
下执行python3 manage.py createsuperuser
,然后根据流程走即可。
Django虽然自带了用户系统,但我们希望能够让用户信息再加一个头像,这时候需要自行利用Django自带的库进行扩充。以下是信息扩充的步骤
acapp/game/models
是用于储存信息的,所以我们可以在这里面储存用户信息。
我们首先要定义一个数据表,首先进入acapp/game/models/
创建一个player
文件夹并进入,再创建py
文件__init__.py
和player.py
,然后开始写player.py
,用来存储Player
这个数据表的信息。
player.py
示例
# 用 Django 自带的基类扩充
from django.db import models # 从django的数据库中引入models
from django.contrib.auth.models import User # 从django中引入这个基本的User类
class Player(models.Model): # 要从models.Model这个类来继承
user = models.OneToOneField(User, on_delete = models.CASCADE) # Player从User扩充,这里是定义一个扩充关系,代表Player都有唯一对应的User,User就是代表User数据表,on_delete代表删除User的时候,把他对应的Player也删掉
photo = models.URLField(max_length = 256, blank = True) # 代表头像的URL,max_length是链接最大长度,blank是是否可空
def __str__(self): # 返回一个对象的描述信息
return str(self.user)
如果我们希望要把这个数据表出现在我们后台的页面左栏,我们就需要将这个数据表注册到管理员页面。
这时候进入到acapp/game/
,在admin.py
上加上两行
admin.py
示例
from django.contrib import admin
from game.models.player.player import Player # 引入自己定义的数据表
admin.site.register(Player) # 注册这个数据表
我们在定义完一个数据表之后,需要将其更新到数据库,这时候我们需要执行两句话:
python3 manage.py makemigrations
python3 manage.py migrate
完成之后进入管理员页面,会发现更新了一个新的数据表,然后可以自己创建这个新类型的对象了。
我们现在如何去让用户登录
流程大概是这样:
用户(Client)每次一刷新,就会先给后台服务器(Server)发送一个请求(Request):getinfo获取用户信息,然后后台就会返回一个响应(Response),表示用户信息(用户名,头像)或者登录失败的响应。
---------- Request ----------
| |----------->| |
| Client | Response | Server |
| |<-----------| |
---------- ----------
我们登录网站的平台可能会不同,比如在AcWingOS,浏览器等等。如果我们要用浏览器登录,就希望有登录的页面;如果要用AcWingOS登录的话,我们就希望直接登录。我们希望能够知道是在哪个平台登陆进来的,我们要修改前面的代码,修改class AcGame
,如下。
class AcGameMenu
{
constructor(root, OS) // 新加一个参数,用来识别平台
{
this.root = root;
this.$menu = $(`
<div class="ac-game-menu" >
</div>
`);
this.OS = OS; // 我就是在这个平台登录的
this.root.$ac_game.append(this.$menu);
}
}
接下来我们首先实现后端根据请求返回用户信息。创建game/views/settings/getinfo.py
,代码如下。
from django.http import JsonResponse
from game.models.player.player import Player
def getinfo_acapp(request): # 在AcWingOS时的getinfo
player = Player.objects.all()[0]
return JsonResponse({
'result': "success",
'username': player.user.username,
'photo': player.photo,
})
def getinfo_web(request): # 在网页端时的getinfo
player = Player.objects.all()[0] # Player.objects是一个数组,代表Player数据表的所有元素的数组,这里为了测试就直接返回第一个人的信息,后面再实现找出这个用户
return JsonResponse({ # 返回Json
'result': "success",
'username': player.user.username, # 用户名
'photo': player.photo, # 头像URL
})
def getinfo(request): # 每个处理请求的函数都要有这个参数'request'
platform = request.GET.get('platform')
# 路由
if platform == "ACAPP":
return getinfo_acapp(request)
elif platform == "WEB":
return getinfo_web(request)
接下来写路由,以调用这些逻辑。进入game/urls/settings/index.py
。修改如下。
from django.urls import path
from game.views.settings.getinfo import getinfo # 引入上面写的getinfo函数
urlpatterns = [
path("getinfo/", getinfo, name = "settings_getinfo"), # 增加这个路由
]
这里y总提到请求类型全部都用GET,GET和POST的功能是完全一样的,但是有一说是GET这个方式不太安全。
现在我们访问网址/settings/getinfo
,成功的话会显示有用户名和头像URL的信息。
下面实现前端部分。我们进入game/static/js/src/settings/zbase.js
,创建一个class Settings
。
class Settings
{
constructor(root)
{
this.root = root; // 根基
this.platform = "WEB"; // 默认平台
if (this.root.OS) this.platform = "ACAPP"; // AcWingOS平台
this.start();
}
register() // 打开注册页面
{
}
login() // 打开登录页面
{
}
getinfo() // 获取信息
{
let outer = this;
$.ajax({ // 发送一个请求
url: "https://域名/settings/getinfo/", // 获取信息的URL
type: "GET", // 获取方式类型
data: {
platform: outer.platform, // 平台信息
},
success: function(resp){ // resp是发送请求之后返回的响应
console.log(resp); // 测试,成功之后写入下面的
if (resp.result === "success")
{
outer.hide(); // 隐藏这个登录页面
outer.root.menu.show(); // 并显示游戏菜单
}
else
{
outer.login(); // 如果没有登录就打开这个登录页面
}
}
})
}
hide()
{
}
show()
{
}
start()
{
this.getinfo(); // 一进入网页就要先获取信息
}
}
我们回到class AcGame
,将这个class Settings
加入进AcGame
里面。
constructor(id, OS)
{
...
this.settings = new Settings(this); // 创建Settings模块,这个一定要放在Menu之前。
...
}
然后保存打包进行测试,刷新一下,发现console
上输出了一个Object
,就是自己的用户信息。为什么会还没登录就直接到菜单了,因为其实前端和后端共用的是同一个用户管理系统,已经在我们登录Django
后台的时候就登录了。
接下来我们实现WEB端未登录的状态的响应,进入game/views/settings/getinfo.py
,修改如下。
def getinfo_web(request):
user = request.user # 从请求里获取这个用户
if not user.is_authenticated: # 没有登录
return JsonResponse({
'result': "未登录",
})
else:
...
接下来我们实现将头像渲染到角色上。
首先进入class Settings
,修改如下。
constructor(root)
{
...;
this.username = ""; // 储存用户名
this.photo = ""; // 储存头像
...; // this.start();
}
getinfo()
{
...;
$.ajax({
...;
success: function(resp)
{
console.log(resp); // 测试输出
if (resp.result === "success") // 如果成功了
{
outer.username = resp.username; // 获取这个用户名
outer.photo = resp.photo; // 获取这个头像
...;
}
else
{
...;
}
}
});
}
如何画这个头像,可以直接照抄。进入class Player
,如下。
constructor(...;)
{
...;
if (this.is_me) // 如果这是自己
{
this.img = new Image(); // 头像的图片
this.img.src = this.playground.root.settings.photo; // 头像的图片的URL
}
}
render()
{
if (this.is_me)
{
// 如果是自己,就画上头像
this.ctx.save();
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0. Math.PI * 2, false);
this.lineWidth = EPS * 10;
this.ctx.stroke();
this.ctx.clip();
this.ctx.drawImage(this.img, this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
this.ctx.restore();
}
else
{
// 否则就是原来的画法
...;
}
}
注意,使用的图片的URL需要完全公开的,有权限的可能在AcWingOS
上打不开。
接下来是重头戏。设计出注册登录界面,首先先画出框框。进入class Settings
。修改如下:
constructor(root)
{
...; // this.photo = ...;
this.$settings = $(`
<div class="ac-game-settings">
<div class="ac-game-settings-login">
</div>
<div class="ac-game-settings-register">
</div>
</div>
`); // 生成settings的html对象
this.$register = this.$settings.find(".ac-game-settings-register"); // 注册界面
this.$login = this.$settings.find(".ac-game-settings-login"); // 登录界面
this.$register.hide(); // 隐藏注册界面
this.$login.hide(); // 隐藏登录界面
this.root.$ac_game.append(this.$settings); // 将这个html对象加入到ac_game
...; // this.start();
}
register() // 点开注册界面
{
this.$login.hide();
this.$register.show();
}
login() // 点开登陆界面
{
this.$register.hide();
this.$login.show();
}
hide() // 隐藏
{
this.$settings.hide();
}
show() // 显示
{
this.$settings.show();
}
进入game.css
修改样式。
.ac-game-settings{
width: 100%;
height: 100%;
background-image: url("/static/image/menu/background.gif");
background-size: 100% 100%;
user-select: none;
}
.ac-game-settings-login{
height: 41vh;
width: 20vw;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 7vh;
}
.ac-game-settings-register{
height: 49vh;
width: 20vw;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 7vh;
}
这些框框是怎么做到居中的呢,首先top
和left
是框框的左上角的点的坐标, transform: translate(-50%, -50%)
是将这个点的x,y坐标向左、向上移动这个框框的宽度和高度的一半,这样就实现了居中的效果。
我们再加入一些细节给那些框框上。继续修改class Settings
。代码最好边抄边看, 这部分最好照着y总的视频写。
我们首先将AcWing的图标下载到自己的服务器上,在讲义部分用wget
下载,下载到game/static/image/settings/
上。然后登录网站/static/image/settings/名称
,看是否能够访问,能够访问说明成功。
然后开始写主体。
constructor(root)
{
...;
this.$settings = $(`
<div class="ac-game-settings">
<div class="ac-game-settings-login"> <!--登录界面-->
<div class="ac-game-settings-title"> <!--标题-->
登录
</div>
<div class="ac-game-settings-username"> <!--用户名输入框-->
<div class="ac-game-settings-item">
<input type="text" placeholder="Username"> <!--输入处-->
</div>
</div>
<div class="ac-game-settings-password"> <!--密码输入框-->
<div class="ac-game-settings-item">
<input type="password" placeholder="Password"> <!--密码输入处-->
</div>
</div>
<div class="ac-game-settings-submit"> <!--按钮-->
<div class="ac-game-settings-item">
<button>登录</button>
</div>
</div>
<div class="ac-game-settings-error-message"> <!--错误信息-->
用户名或密码错误!
</div>
<div class="ac-game-settings-option"> <!--注册选项-->
注册
</div>
<br> <!--这里一定要加上,不然一键登录图标不会居中,前面两行是inline的样式,可能会有bug-->
<div class="ac-game-settings-acwing">
<img src="AcWing图标的url" width="30"> <!--一键登录图标-->
<div> <!--图标下提示信息-->
AcWingOS一键登录
</div>
</div>
</div>
<div class="ac-game-settings-register"> <!--这是注册界面-->
<div class="ac-game-settings-title">
注册
</div>
<div class="ac-game-settings-username"> <!--用户名输入框-->
<div class="ac-game-settings-item">
<input type="text" placeholder="Username"> <!--输入处-->
</div>
</div>
<div class="ac-game-settings-password-first"> <!--密码输入框-->
<div class="ac-game-settings-item">
<input type="password" placeholder="Password"> <!--密码输入处-->
</div>
</div>
<div class="ac-game-settings-password-second"> <!--确认密码输入框-->
<div class="ac-game-settings-item">
<input type="password" placeholder="Password Confirm"> <!--确认密码密码输入处-->
</div>
</div>
<div class="ac-game-settings-submit"> <!--按钮-->
<div class="ac-game-settings-item">
<button>注册</button>
</div>
</div>
<div class="ac-game-settings-error-message"> <!--错误信息-->
用户名或密码不可用!
</div>
<div class="ac-game-settings-option"> <!--注册选项-->
登录
</div>
<br> <!--这里一定要加上,不然一键登录图标不会居中,前面两行是inline的样式,可能会有bug-->
<div class="ac-game-settings-acwing">
<img src="AcWing图标的url" width="30"> <!--一键登录图标-->
<div> <!--图标下提示信息-->
AcWingOS 一键登录
</div>
</div>
</div>
</div>
`); // 生成settings的html对象
this.$login = ...;
this.$login_username = this.$login.find(".ac-game-settings-username input"); // 用户名输入框
this.$login_password = this.$login.find(".ac-game-settings-password input"); // 密码输入框
this.$login_submit = this.$login.find(".ac-game-settings-submit button"); // 提交按钮
this.$login_error_message = this.$login.find(".ac-game-settings-error-message"); // 错误信息
this.$login_register = this.$login.find(".ac-game-settings-option"); // 注册选项
this.$register_username = this.$register.find(".ac-game-settings-register input");
this.$register_password = this.$register.find(".ac-game-settings-password-first input");
this.$register_password_confirm = this.$register.find(".ac-game-settings-password-second input"); // 确认密码输入框
this.$register_submit = this.$register.find(".ac-game-settings-submit button");
this.$register_error_message = this.$register.find(".ac-game-settings-error-message");
this.$register_login = this.$register.find(".ac-game-settings-option"); // 登陆选项
...; // this.$register.hide();
}
start()
{
...;
this.add_listening_events();
}
add_listening_events()
{
this.add_listening_events_register(); // 注册页面的监听
this.add_listening_events_login(); // 登陆页面的监听
}
add_listening_events_register()
{
let outer = this;
this.$register_login.click(function(){ // 在注册页面点击登录选项就打开登录界面
outer.login();
});
}
add_listening_events_login()
{
let outer = this;
this.$login_register.click(function(){ // 在登录页面点击注册选项就打开注册界面
outer.register();
});
}
css
样式。
.ac-game-settings-title{
color: white;
font-size: 3vh;
text-align: center;
height: 7vh;
padding-top: 1vh;
}
.ac-game-settings-username{
display: block;
height: 7vh;
}
.ac-game-settings-password{
display: block;
height: 7vh;
}
.ac-game-settings-submit{
display: block;
height: 7vh;
}
.ac-game-settings-item{
width: 100%;
height: 100%;
}
.ac-game-settings-item > input{
width: 90%;
line-height: 3vh;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 1vh;
}
.ac-game-settings-item > button{
color: white;
background-color: royalblue;
width: 90%;
line-height: 3vh;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 1vh;
cursor: pointer;
}
.ac-game-settings-error-message{
color: red;
font-size: 1vh;
display: inline;
float: left;
padding-left: 1vw;
}
.ac-game-settings-option{
color: white;
font-size: 1.5vh;
display: inline;
float: right;
padding-right: 1.5vw;
cursor: pointer;
}
.ac-game-settings-acwing{
display: block;
height: 7vh;
}
.ac-game-settings-acwing > img{
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.ac-game-settings-acwing > div{
color: white;
font-size: 0.7vw;
text-align: center;
display: block;
}
登陆页面和注册页面基本实现好了,下面实现后端处理注册和登录的逻辑。
首先实现登录。
创建game/views/settings/login.py
并进去,实现登录,代码如下。
from django.http import JsonResponse
from django.contrib.auth import authenticate, login
def signin(request):
data = request.GET # 获取请求的信息
username = data.get('username') # 用户名
password = data.get('password') # 密码
user = authenticate(username = username, password = password) # 从数据库中查找这个用户
if not user: # 如果没有就直接返回不成功
return JsonResponse({
'result': "用户名或密码不正确"
})
login(request, user) # 找到了就登录
创建game/views/settings/logout.py
并进去,实现登出,代码如下。
from django.http import JsonResponse
from django.contrib.auth import logout
def signout(request):
user = request.user
if not user.is_authenticated:
return JsonResponse({
'result': "success",
})
logout(request);
return JsonResponse({
'result':
})
写路由,进入game/urls/settings/index.py
,增加代码如下
from game.views.settings.login import signin # 引入自己写的逻辑
from game.views.settings.logout import signout
urlpatterns = [
...
path("login/", signin, name = "settings_login"), # "settings/login/"
path("logout/", signout, name = "settings_logout"), # "settings/logout/"
]
接下来进入前端,进入class Settings
,修改如下。
add_lisetening_events_login()
{
...;
this.$login_submit.click(function(){
outer.login_on_remote();
});
}
register_on_remote() // 在远程服务器上登录
{
}
login_on_remote() // 在远程服务器上登录
{
let outer = this;
let username = this.$login_username.val(); // 获取输入框中的用户名
let password = this.$login_password.val(); // 获取输入框中的密码
this.$login_error_message.empty(); // 清楚错误信息
$.ajax({
url: "登录URL", // 访问url
type: "GET",
data: {
username: username, // 传输数据
password: password,
},
success: function(resp){
console.log(resp); // 测试输出
if (resp.result === "success")
{
location.reload(); // 如果成功了就刷新
}
else
{
outer.$login_error_message.html(resp.result); // 如果失败了就显示错误信息
}
}
});
}
logout_on_remote() // 在远程服务器上登出
{
if (this.platform === "ACAPP") return false; // 如果在ACAPP退出就直接退出
$.ajax({
url: "登出URL", // 访问url
type: "GET",
success: function(resp){
console.log(resp); // 测试输出
if (resp.result === "success")
{
location.reload(); // 如果成功了就直接刷新
}
}
});
}
进入class AcGameMenu
,修改如下。
constructor(...;)
{
...;
// 把$menu里面,"设置"改为"退出"
}
add_lisetening_events()
{
...;
this.$settings.click(function(){
console.log("logout"); // 测试输出
outer.root.settings.logout_on_remote(); // 点击到退出就登出账号
});
}
现在实现注册。
创建game/views/settings/register.py
并进去,代码如下。
from django.http import JsonResponse
from django.contrib.auth import login
from django.contrib.auth.models import User
from game.models.player.player import Player
def register(request):
data = request.GET
username = data.get("username", "").strip()
password = data.get("password", "").strip()
password_confirm = data.get("password_confirm", "").strip()
if not username or not password:
return JsonResponse({
'result': "用户名或密码不能为空"
})
if password != password_confirm:
return JsonResponse({
'result': "两个密码不一致",
})
if User.objects.filter(username = username).exist():
return JsonResponse({
'result': "用户名已存在"
})
user = User(username = username)
user.set_password(password)
user.save()
Player.objects.create(user = user, photo = "默认头像URL")
login(request, user)
return JsonResponse({
'result': "success",
})
然后加入到路由game/urls/settings/index.py
...
from game.views.settings.register import register
urlpatterns = [
...
path("register/", register, name = "settings_register"),
]
然后进入前端,class Settings
,如下。
add_listening_events_register()
{
...;
this.$register_submit.click(function(){
outer.register_on_remote();
});
}
register_on_remote()
{
let outer = this;
let username = this.$register_username.val();
let password = this.$register_password.val();
let password_confirm = this.$register_password_confirm.val();
this.$register_error_message.empty();
$.ajax({
url: "注册URL",
type: "GET",
data: {
username: username,
password: password,
password: password_confirm,
},
success: function(resp){
console.log(resp);
if (resp.result === "success")
{
location.reload(); // 刷新网页
}
else
{
outer.$regiseter_error_message.html(resp.result);
}
}
});
}
到了这里,本节课需要实现的所有目标都完成了,剩下的都是自己测试的环节了。
我们会发现每次注册新账号,用户名和头像永远都是第一个用户的,这时候我们如何定位到正确的用户信息呢。我们进入game/views/settings/getinfo.py
,修改如下即可。
def getinfo_web(request):
...
if not user.is_authenticated:
...
else:
player = Player.objects.filter(user = user)[0] # 筛选出来这个玩家
谢谢大佬,本来以为直接复制就省事不用debug了,没想到佬常常留一些小错误留给我们订正,dubug学到很多(
谢谢大佬,跟着你的总结,挺清晰的!!!谢谢
开始改的不是AcGame吗,代码贴成AcGameMenu了
game/views/settings/logout.py
要加上success
:还有就是
if User.objects.filter(username = username).exist():
这里,应该是exists()写前端代码的时候,用户名这一块应该是
".ac-game-settings-username input"
是settings的js文件的主体部分那一块
在player下的zbase.js中,判断是自己画上头像这部分,有个错误: this.stroke();应该是 this.ctx.stroke();你看看是不是,我这边报错了
要加上ctx,我这里打错了。
不知道大佬有没有出现在class Settings的register_on_remote和其他监听函数里,url输入自己的url后发生跨域错误,也就是登录框不显示,但替换成y总的app2313.acapp.acwing.com.cn,居然显示了
错误是这个,修了老久了Access to XMLHttpRequest at from origin ‘http://127.0.0.1:8888' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
端口不一样就就会出现跨域,y总的域名能成功是因为用了nginx代理服务器解决了跨域问题。这个可以了解一下。
兄弟 CASADE打错了,是CASCADE
感谢指正
兄弟,你这class Settings构造器里没写this.start()😂
再次感谢指正
好耶,看着巨巨的文章一步步写完项目的
兄弟,第一个好像是
感谢指正