笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。
CONTENTS
- 1. 扩充Django数据库
- 2. 实现获取用户信息
- 3. 渲染登录与注册界面
- 4. 实现登录与登出功能
- 5. 实现注册功能
- 6. 修改获取用户信息
1. 扩充Django数据库
首先我们先在 settings.py
中修改:DEBUG = True
,否则如果服务器端的代码报错时前端不会显示报错详细信息。
Django 自带一个账号系统,在网址后面添加后缀 /admin
即可访问管理员界面,登录之前的超级管理员账号,进入管理员界面后其中的 Users
界面就是自带的账号系统。
每个用户的信息如下图所示:
其中可以填写每个用户的姓名和邮箱,Active
表示用户是否被封,不勾选即无法登录;Staff status
表示用户是否能进入到后台管理页面;Superuser status
表示用户是否具有超级管理员的权限。
这个自带的账号系统不能满足我们的需求,例如需要存储用户的头像,该数据库就需要进行扩充。
我们要在 djangoapp/game/models
目录下创建我们所需的表,即创建类似之前提到的 Users
,我们在该目录中新建一个 player
目录,然后进入该目录,记得需要先创建一个 __init__.py
文件。
接着我们创建 player.py
用来存储 player
这个数据表的信息,创建类时需要继承基类 django.db.models.Model
(开发时有个小 Tips,如果忘记一些关键字怎么写可以在项目根目录执行 python3 manage.py shell
,进入 IPython 交互功能,然后输入代码即具有补全功能):
from django.db import models
from django.contrib.auth.models import User
# Player有两个关键字,user表示是和哪个User对应的,avatar表示头像
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # 当删除User时也将其关联的Player一块删掉
avatar = models.URLField(max_length=256, blank=True) # 头像用链接存
def __str__(self): # 显示每个Player的数据
return str(self.user)
创建好数据表后如果希望让我们的数据表出现在管理员界面,需要将它注册到管理员页面,在 game
目录下可以看到一个 admin.py
文件,对其进行修改:
from django.contrib import admin
from game.models.player.player import Player # 将表导进来
# Register your models here.
admin.site.register(Player)
每次对数据表的定义更新后都需要执行以下两句指令(在项目根目录):
python3 manage.py makemigrations
python3 manage.py migrate
然后我们重启一下项目:uwsgi --ini scripts/uwsgi.ini
,即可看到新创建的表:
在右上角的 ADD PLAYER
选项中即可添加 Player:
从这个例子即可看出数据库中的 Table 对应 Django 中的 Class,Table 中的每一条数据就对应 Class 中的每一个对象。
2. 实现获取用户信息
假设我们将网页刷新后,Clinet 先向后台服务器(Server)发送一个请求获得当前玩家的信息 getinfo
,后台一种是返回用户名和头像,还有一种是返回未登录,现在我们先默认每次都返回玩家的用户名和头像,每次写一个函数时需要写三个部分:views
表示调用数据库的逻辑、urls
表示路由、js
实现调用。
由于有多个前端,因此后端在接收请求的时候需要知道是哪个前端,需要对 AcGame
类进行修改,让其多传入一个参数,如果在 AcWing 打开该项目则会传入该参数:
export class AcGame {
constructor(id, acwingos) {
this.id = id;
this.$ac_game = $('#' + id); // jQuery通过id找对象的方式
this.acwingos = acwingos; // 通过AcWing打开时会传入此参数
this.menu = new AcGameMenu(this);
this.playground = new AcGamePlayground(this);
this.start();
}
start() {
}
}
进入 views
目录,我们将所有用户的信息全部放到 settings
目录中,在 settings
目录创建一个 getinfo.py
文件,内容如下:
from django.http import JsonResponse
from game.models.player.player import Player
def getinfo_acapp(request):
player = Player.objects.all()[0] # 先默认返回第一个玩家的信息
return JsonResponse({
'result': 'success', # 查询结果
'username': player.user.username,
'avatar': player.avatar,
})
def getinfo_web(request):
player = Player.objects.all()[0]
return JsonResponse({
'result': 'success',
'username': player.user.username,
'avatar': player.avatar,
})
def getinfo(request):
platform = request.GET.get('platform') # 是哪个平台发起的请求
if platform == 'ACAPP':
return getinfo_acapp(request)
else:
return getinfo_web(request)
然后进入 urls/settings
目录,修改 index.py
文件:
from django.urls import path
from game.views.settings.getinfo import getinfo
urlpatterns = [
path('getinfo/', getinfo, name='settings_getinfo')
]
现在我们即可访问 https://<项目IP地址>/settings/getinfo/
查看效果。
最后我们需要在游戏的菜单界面之前添加一个界面判断用户是否登录,先将 AcGameMenu
类在初始化操作中 hide
,然后进入 /djangoapp/game/static/js/src/settings
目录中创建 zbase.js
:
class Settings {
constructor(root) {
this.root = root;
this.platform = 'WEB'; // 默认为Web前端
if (this.root.acwingos) this.platform = 'ACAPP';
this.start();
}
start() { // 在初始化时需要从服务器端获取用户信息
this.getinfo();
}
register() { // 打开注册界面
}
login() { // 打开登录界面
}
getinfo() {
let outer = this;
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/getinfo/', // 用AcWing部署
// url: 'http://8.130.54.44:8000/settings/getinfo/', // 用云服务器部署
type: 'GET',
data: {
platform: outer.platform,
},
success: function(resp) { // 调用成功的回调函数,返回的Json字典会传给resp
console.log(resp); // 控制台输出查看结果
if (resp.result === 'success') {
outer.hide();
outer.root.menu.show();
} else { // 如果未登录则需要弹出登录界面
outer.login();
}
}
});
}
hide() {
}
show() {
}
}
然后在 AcGame
类中创建 Settings
对象:
export class AcGame {
constructor(id, acwingos) {
this.id = id;
this.$ac_game = $('#' + id); // jQuery通过id找对象的方式
this.settings = new Settings(this);
this.menu = new AcGameMenu(this);
this.playground = new AcGamePlayground(this);
this.acwingos = acwingos;
this.start();
}
start() {
}
}
此时访问网站即可看到控制台的输出信息:
此时项目的执行顺序为:创建 Settings
类的对象时先执行构造函数,接着会执行 start
中的 getinfo
函数,会向后端(https://app4007.acapp.acwing.com.cn/settings/getinfo/
)发一个请求,然后路由会找到 views.settings.getinfo
文件中的 getinfo
函数,然后判断 platform
为 WEB
,最后通过 getinfo_web
返回用户名和用户头像到 resp
中。
现在我们来判断用户是否登录,修改 views/settings
目录中的 getinfo.py
:
from django.http import JsonResponse
from game.models.player.player import Player
def getinfo_acapp(request):
...
def getinfo_web(request):
user = request.user
if not user.is_authenticated:
return JsonResponse({
'result': 'not login',
})
else:
player = Player.objects.all()[0]
return JsonResponse({
'result': 'success',
'username': player.user.username,
'avatar': player.avatar,
})
def getinfo(request):
...
然后我们需要将用户的信息存下来,将头像渲染到小球里,首先在 js/src/settings
目录中修改 zbase.js
:
class Settings {
constructor(root) {
...
this.username = ''; // 初始用户信息为空
this.avatar = '';
this.start();
}
start() { // 在初始化时需要从服务器端获取用户信息
this.getinfo();
}
register() { // 打开注册界面
}
login() { // 打开登录界面
}
getinfo() {
let outer = this;
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/getinfo/', // 用AcWing部署
// url: 'http://8.130.54.44:8000/settings/getinfo/', // 用云服务器部署
type: 'GET',
data: {
platform: outer.platform,
},
success: function(resp) { // 调用成功的回调函数,返回的Json字典会传给resp
console.log(resp); // 控制台输出查看结果
if (resp.result === 'success') {
outer.username = resp.username;
outer.avatar = resp.avatar;
...
} else { // 如果未登录则需要弹出登录界面
outer.login();
}
}
});
}
hide() {
}
show() {
}
}
然后在 Player
类中渲染用户的头像:
class Player extends AcGameObject {
constructor(playground, x, y, radius, color, speed, is_me) {
...
if (this.is_me) { // 用户自己的头像从服务器端获取
this.img = new Image();
this.img.src = this.playground.root.settings.avatar;
}
}
start() {
...
}
add_listening_events() {
...
}
// 计算两点之间的欧几里得距离
get_dist(x1, y1, x2, y2) {
...
}
// 向(tx, ty)位置发射火球
shoot_fireball(tx, ty) {
...
}
move_to(tx, ty) {
...
}
is_attacked(theta, damage) { // 被攻击到
...
}
update() {
...
}
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.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 {
this.ctx.beginPath();
// 角度从0画到2PI,是否逆时针为false
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
this.ctx.fillStyle = this.color;
this.ctx.fill();
}
}
}
3. 渲染登录与注册界面
首先我们下载所需要的图标资源,进入 static/image/settings
目录执行以下指令:
wget https://cdn.acwing.com/media/article/image/2021/11/18/1_ea3d5e7448-logo64x64_2.png
mv 1_ea3d5e7448-logo64x64_2.png acwing_logo.png
然后完善 Settings
类渲染登录与注册界面并且实现两个界面的跳转功能:
class Settings {
constructor(root) {
this.root = root;
this.platform = 'WEB'; // 默认为Web前端
if (this.root.acwingos) this.platform = 'ACAPP';
this.username = ''; // 初始用户信息为空
this.avatar = '';
this.$settings = $(`
<div class='ac_game_settings'>
<div class='ac_game_settings_login'>
<div class='ac_game_settings_title'>
Login
</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>Login</button>
</div>
</div>
<div class='ac_game_settings_errormessage'>
</div>
<div class='ac_game_settings_option'>
Register
</div>
<br> <!-- inline格式可能有bug,需要加一行回车 -->
<div class='ac_game_settings_acwingoption'>
<img width='30' src='https://app4007.acapp.acwing.com.cn/static/image/settings/acwing_logo.png'>
<br>
<div>AcWing Login</div>
</div>
</div>
<div class='ac_game_settings_register'>
<div class='ac_game_settings_title'>
Register
</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 ac_game_settings_password_first'>
<div class='ac_game_settings_item'>
<input type='password' placeholder='Password'>
</div>
</div>
<div class='ac_game_settings_password ac_game_settings_password_second'>
<div class='ac_game_settings_item'>
<input type='password' placeholder='Confirm Password'>
</div>
</div>
<div class='ac_game_settings_submit'>
<div class='ac_game_settings_item'>
<button>Register</button>
</div>
</div>
<div class='ac_game_settings_errormessage'>
</div>
<div class='ac_game_settings_option'>
Login
</div>
</div>
</div>
`);
this.$login = this.$settings.find('.ac_game_settings_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_errormessage = this.$login.find('.ac_game_settings_errormessage');
this.$login_register = this.$login.find('.ac_game_settings_option');
this.$login.hide();
this.$register = this.$settings.find('.ac_game_settings_register');
this.$register_username = this.$register.find('.ac_game_settings_username input');
this.$register_password = this.$register.find('.ac_game_settings_password_first input');
this.$register_confirm_password = this.$register.find('.ac_game_settings_password_second input');
this.$register_submit = this.$register.find('.ac_game_settings_submit button');
this.$register_errormessage = this.$register.find('.ac_game_settings_errormessage');
this.$register_login = this.$register.find('.ac_game_settings_option')
this.$register.hide();
this.root.$ac_game.append(this.$settings);
this.start();
}
start() { // 在初始化时需要从服务器端获取用户信息
this.getinfo();
this.add_listening_events();
}
add_listening_events() { // 绑定监听函数
this.add_listening_events_login();
this.add_listening_events_register();
}
add_listening_events_login() {
let outer = this;
this.$login_register.click(function() {
outer.register();
});
}
add_listening_events_register() {
let outer = this;
this.$register_login.click(function() {
outer.login();
});
}
register() { // 打开注册界面
this.$login.hide();
this.$register.show();
}
login() { // 打开登录界面
this.$register.hide();
this.$login.show();
}
getinfo() {
let outer = this;
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/getinfo/', // 用AcWing部署
// url: 'http://8.130.54.44:8000/settings/getinfo/', // 用云服务器部署
type: 'GET',
data: {
platform: outer.platform,
},
success: function(resp) { // 调用成功的回调函数,返回的Json字典会传给resp
console.log(resp); // 控制台输出查看结果
if (resp.result === 'success') {
outer.username = resp.username;
outer.avatar = resp.avatar;
outer.hide();
outer.root.menu.show();
} else { // 如果未登录则需要弹出登录界面
outer.login();
}
}
});
}
hide() {
this.$settings.hide();
}
show() {
this.$settings.show();
}
}
game.css
内容如下:
.ac_game_menu {
width: 100%;
height: 100%;
background-image: url('/static/image/menu/background.png'); /* 注意不用带公网IP */
background-size: 100% 100%;
user-select: none;
}
.ac_game_menu_btgroup {
width: 20vw;
position: relative;
top: 30%;
left: 20%;
}
.ac_game_menu_btgroup_bt {
height: 7vh;
width: 15vw;
color: white;
font-size: 3vh;
line-height: 7vh;
font-style: italic;
cursor: pointer;
text-align: center;
background-color: rgba(39, 21, 28, 0.6);
border-radius: 10px;
letter-spacing: 0.5vw;
}
.ac_game_menu_btgroup_bt:hover {
transform: scale(1.2);
transition: 100ms;
}
.ac_game_playground {
height: 100%;
width: 100%;
user-select: none;
}
.ac_game_settings {
width: 100%;
height: 100%;
background-image: url('/static/image/menu/background.png');
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: 5px;
}
.ac_game_settings_register {
height: 39vh;
width: 20vw;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 5px;
}
.ac_game_settings_title {
color: white;
font-size: 3.5vh;
text-align: center;
height: 7vh;
line-height: 7vh;
}
.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_errormessage {
color: red;
font-size: 1.5vh;
display: inline;
float: left;
padding-left: 1vw;
}
.ac_game_settings_option {
color: white;
font-size: 1.5vh;
display: inline;
float: right;
padding-right: 1vw;
cursor: pointer;
}
.ac_game_settings_acwingoption {
display: block;
height: 8vh;
}
.ac_game_settings_acwingoption > img {
display: block;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.ac_game_settings_acwingoption > div {
display: block;
color: white;
font-size: 1.2vh;
text-align: center;
padding-top: 1vh;
}
.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%);
}
.ac_game_settings_item > button {
color: black;
width: 30%;
line-height: 3vh;
font-size: 2vh;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgb(199, 237, 204);
border-radius: 7px;
cursor: pointer;
}
4. 实现登录与登出功能
实现登录与登出功能我们同样要编写 views
、urls
以及 js
文件,首先在 game/views/settings
目录下创建 login.py
文件:
from django.contrib.auth import authenticate, login
from django.http import JsonResponse
def mylogin(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)
return JsonResponse({
'result': 'success',
})
然后进入 game/urls/settings
目录修改 index.py
:
from django.urls import path
from game.views.settings.getinfo import getinfo
from game.views.settings.login import mylogin
urlpatterns = [
path('getinfo/', getinfo, name='settings_getinfo'),
path('login/', mylogin, name='settings_login'),
]
此时重启一下项目后访问 https://<公网IP>/settings/login/
即可看到返回结果。
现在我们实现一下登出函数,在 game/views/settings
目录下创建 logout.py
文件:
from django.contrib.auth import logout
from django.http import JsonResponse
def mylogout(request):
user = request.user
if not user.is_authenticated:
return JsonResponse({
'result': 'success',
})
logout(request)
return JsonResponse({
'result': 'success',
})
然后进入 game/urls/settings
目录修改 index.py
:
from django.urls import path
from game.views.settings.getinfo import getinfo
from game.views.settings.login import mylogin
from game.views.settings.logout import mylogout
urlpatterns = [
path('getinfo/', getinfo, name='settings_getinfo'),
path('login/', mylogin, name='settings_login'),
path('logout/', mylogout, name='settings_logout'),
]
此时重启一下项目后在登录状态下访问 https://<公网IP>/settings/logout/
即可登出。
然后我们在 Settings
类中实现登录登出:
class Settings {
constructor(root) {
...
}
start() { // 在初始化时需要从服务器端获取用户信息
...
}
add_listening_events() { // 绑定监听函数
this.add_listening_events_login();
this.add_listening_events_register();
}
add_listening_events_login() {
let outer = this;
this.$login_register.click(function() {
outer.register();
});
this.$login_submit.click(function() {
outer.login_on_remote();
});
}
add_listening_events_register() {
let outer = this;
this.$register_login.click(function() {
outer.login();
});
}
login_on_remote() { // 在远程服务器上登录
let outer = this;
let username = this.$login_username.val();
let password = this.$login_password.val();
this.$login_errormessage.empty(); // 先清空报错信息
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/login/',
type: 'GET',
data: {
username: username,
password: password,
},
success: function(resp) {
console.log(resp);
if (resp.result === 'success') { // 登录成功
location.reload(); // 刷新页面
} else { // 登录失败
outer.$login_errormessage.html(resp.result); // 显示报错信息
}
}
});
}
register_on_remote() { // 在远程服务器上注册
}
logout_on_remote() { // 在远程服务器上登出
if (this.platform === 'ACAPP') return false; // AcApp应该是直接关闭窗口退出
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/logout/',
type: 'GET',
success: function(resp) {
console.log(resp);
if (resp.result === 'success') {
location.reload();
}
}
});
}
register() { // 打开注册界面
...
}
login() { // 打开登录界面
...
}
getinfo() {
...
}
hide() {
...
}
show() {
...
}
}
其中登出函数 logout_on_remote
需要在 AcGameMenu
类中调用:
class AcGameMenu {
constructor(root) { // root用来传AcGame对象
this.root = root;
this.$menu = $(`
<div class='ac_game_menu'>
<div class='ac_game_menu_btgroup'>
...
<div class='ac_game_menu_btgroup_bt ac_game_menu_btgroup_bt_settings'>
登出
</div>
</div>
</div>
`);
...
this.$settings = this.$menu.find('.ac_game_menu_btgroup_bt_settings');
this.start();
}
start() {
...
}
// 给按钮绑定监听函数
add_listening_events() {
let outer = this;
...
this.$settings.click(function() {
outer.root.settings.logout_on_remote();
});
}
// 显示menu界面
show() {
...
}
// 关闭menu界面
hide() {
...
}
}
5. 实现注册功能
首先在 game/views/settings
目录下创建 register.py
文件:
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.http import JsonResponse
from game.models.player.player import Player
def register(request):
data = request.GET
username = data.get('username', '').strip() # 如果没有的话返回空,且过滤掉空格
password = data.get('password', '').strip()
confirm_password = data.get('confirm_password', '').strip()
if not username or not password: # 用户名或密码为空
return JsonResponse({
'result': 'Username or password can\'t be empty!',
})
elif password != confirm_password: # 两次密码不一致
return JsonResponse({
'result': 'Password inconsistency!'
})
elif User.objects.filter(username=username).exists(): # 用户名已存在
return JsonResponse({
'result': 'Username has existed!'
})
user = User(username=username)
user.set_password(password)
user.save()
Player.objects.create(user=user, avatar='https://cdn.acwing.com/media/article/image/2021/11/18/1_ea3d5e7448-logo64x64_2.png')
login(request, user)
return JsonResponse({
'result': 'success',
})
然后进入 game/urls/settings
目录修改 index.py
:
from django.urls import path
from game.views.settings.getinfo import getinfo
from game.views.settings.login import mylogin
from game.views.settings.logout import mylogout
from game.views.settings.register import register
urlpatterns = [
path('getinfo/', getinfo, name='settings_getinfo'),
path('login/', mylogin, name='settings_login'),
path('logout/', mylogout, name='settings_logout'),
path('register/', register, name='settings_register'),
]
此时重启一下项目后访问 https://<公网IP>/settings/register/
即可看到返回结果。
最后修改前端 Settings
类:
class Settings {
constructor(root) {
...
}
start() { // 在初始化时需要从服务器端获取用户信息
...
}
add_listening_events() { // 绑定监听函数
...
}
add_listening_events_login() {
...
}
add_listening_events_register() {
let outer = this;
this.$register_login.click(function() {
outer.login();
});
this.$register_submit.click(function() {
outer.register_on_remote();
});
}
login_on_remote() { // 在远程服务器上登录
...
}
register_on_remote() { // 在远程服务器上注册
let outer = this;
let username = this.$register_username.val();
let password = this.$register_password.val();
let confirm_password = this.$register_confirm_password.val();
this.$register_errormessage.empty();
$.ajax({
url: 'https://app4007.acapp.acwing.com.cn/settings/register/',
type: 'GET',
data: {
username: username,
password: password,
confirm_password: confirm_password,
},
success: function(resp) {
console.log(resp);
if (resp.result === 'success') {
location.reload();
} else {
outer.$register_errormessage.html(resp.result);
}
}
});
}
logout_on_remote() { // 在远程服务器上登出
...
}
register() { // 打开注册界面
...
}
login() { // 打开登录界面
...
}
getinfo() {
...
}
hide() {
...
}
show() {
...
}
}
6. 修改获取用户信息
之前我们是默认返回第一个用户的信息,我们对 game/views/settings
目录下的 getinfo.py
文件进行修改:
from django.http import JsonResponse
from game.models.player.player import Player
def getinfo_acapp(request):
...
def getinfo_web(request):
user = request.user
if not user.is_authenticated:
return JsonResponse({
'result': 'not login',
})
else:
player = Player.objects.get(user=user) # 获取user的player信息
return JsonResponse({
'result': 'success',
'username': player.user.username,
'avatar': player.avatar,
})
def getinfo(request):
platform = request.GET.get('platform') # 是哪个平台发起的请求
if platform == 'ACAPP':
return getinfo_acapp(request)
# elif platform == 'WEB':
else:
return getinfo_web(request)