Mapz's Blog

可以递归的函数指针

Unity 2018 的新功能 ECS 现在已经放出了预览版本,我们现在就来尝试一下

—

Hybrid模式

hybrid 就是混合模式,因为 Unity ECS 现在还只有预览版本

对 GameObject 和各种 Render 支持很差

所以混合模式可以看成现在的 GameObject 模式到纯 ECS 之间的过渡模式

Hybrid 模式下,并没有在效率上有非常大的提升

比起纯 ECS 模式

  • 初始化时间(遍历寻找Entity的过程)无法优化
  • 载入时间无法优化
  • 数据在内存中是随机获取的,非线性,执行效率下降
  • 无法利用多核处理器
  • 没有SIMD

但是我们任然可以先尝试通过这种方式,来提高编程效率

并提前熟悉 Unity ECS 的思维模式


资源准备

首先 导入了一组 星际争霸中刺蛇的 Sprite

先在 Sprite Editor 中切好了刺蛇的 走路 Sprite

做好了刺蛇16个方向走路的动作 Animation 和 Animator

Animator 增加参数 Direction Float 控制刺蛇的移动方向

总共 16 方向 做好从 AnyState 到每个方向的 Direction 条件

我们不需要转换动画,所以 每个 translation 的 setting 里面的 duration 都改成 0

并且要把 Can translate to self 取消勾选,不然 永远会卡第一帧


我们先通过简单的键盘操作来控制刺蛇移动

设计我们的系统和实体

实体

单位实体 Unit 包含的组件

  • 位置 Position
  • 速度 Velocity
  • 单位 Unit
  • 输入 PlayerInput
  • 可转向 Directable

系统

逻辑系统

运动系统-需要

  • 位置 Position
  • 速度 Velocity

输入系统-需要 的组件

  • 输入 PlayerInput
  • 速度 Velocity

同步 GameObject 状态的系统

同步 transform 系统

  • 位置 Position
  • Transform

同步刺蛇 Animator 方向状态的系统

  • 速度 Velocity (速度控制朝向)
  • Animator

编写上述组件和系统的代码

组件

位置

1
2
3
4
5
6
using Unity.Mathematics;
using UnityEngine;
public class Position2D : MonoBehaviour {
// 位置 x y
public float2 Value;
}

输入

1
2
3
4
5
6
7
using Unity.Mathematics;
using UnityEngine;

public class PlayerInput : MonoBehaviour {
// 输入 x y
public float2 Move;
}

可转向

1
2
3
4
5
6
using Unity.Mathematics;
using UnityEngine;

public class Directable : MonoBehaviour {

}

单位

1
2
3
4
5
6
using Unity.Mathematics;
using UnityEngine;

public class Unit : MonoBehaviour {

}

速度

1
2
3
4
5
6
7
using Unity.Mathematics;
using UnityEngine;
public class Velocity : MonoBehaviour {
// 速度 x y
public float2 Value;

}

大家可以看到我们这边都是继承的 MonoBehavior ,有的还是一些空的类

继承 MonoBehavior 才能被添加到 GameObject 中,至于空类的作用我们下面在讲系统的时候来讲

系统

ECS 中的 S 意为 System 就是系统了,系统的做法,下面已经比较清楚了

在系统中 可以通过 GetEntities 来自动获得声明好的 结构体数据

结构体数据是从哪里获得的呢?

答案是从游戏中所有的 Entity 来自动获取,由于我们使用的是一个 GameObject ,要获得这个 GameObject 的数据,需要在这个 GameObject 中添加一个内置脚本 GameObjectEntity

表示我们这个 GameObject 是一个 Entity 了

那么大家可能也就知道上面那些空类的作用了

假如我们不对 GameObject 添加 Directable 脚本的话

就无法被 SyncAnimatorDirectionSystem(见下方代码) 中的 GetEntities 来获得

1
GetEntities <T>

的作用就是获取含有 T 中所有字段类型的 所有 Entity

System 可以使用 UpdateAfter 或者 UpdateBefore 来控制其执行顺序

输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class PlayerInputSystem : ComponentSystem {
struct PlayerData {

public PlayerInput Input;
public Velocity Velocity;

}

protected override void OnUpdate () {
float dt = Time.deltaTime;

foreach (var entity in GetEntities<PlayerData> ()) {
var pi = entity.Input;

pi.Move.x = Input.GetAxis ("Horizontal");
pi.Move.y = Input.GetAxis ("Vertical");

entity.Velocity.Value = new float2 (pi.Move.x, pi.Move.y);

}
}
}

移动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
public class UnitMoveSystem : ComponentSystem {
struct MoveUnit {

public Position2D Position;

public Velocity MoveSpeed;

public Unit unit;
}

protected override void OnUpdate () {
var dt = Time.deltaTime;
foreach (var entity in GetEntities<MoveUnit> ()) {
var pos = entity.Position;
pos.Value += entity.MoveSpeed.Value * dt;
}
}
}

同步位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[UpdateAfter (typeof (UnitMoveSystem))]
public class SyncTransformSystem : ComponentSystem {
public struct Data {

[ReadOnly] public Position2D Position;

public Transform Output;
}

protected override void OnUpdate () {
foreach (var entity in GetEntities<Data> ()) {
float2 p = entity.Position.Value;
entity.Output.position = new float3 (p.x, p.y, 0);
}
}
}
````

#### 同步动画朝向状态
```` C#
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[UpdateAfter (typeof (PlayerInputSystem))]
public class SyncAnimatorDirectionSystem : ComponentSystem {

public struct Data {

[ReadOnly] public Velocity moveSpeed;

public Animator Output;

public Directable directable;
}

protected override void OnUpdate () {
foreach (var entity in GetEntities<Data> ()) {

float2 p = entity.moveSpeed.Value;

var angle = Mathf.Atan2 (p.x, p.y) * Mathf.Rad2Deg;
if (angle < 0) {
angle = 360 + angle;
}

entity.Output.SetFloat ("Direction", angle);

}
}
}
````

然后在 刺蛇的 Prefab 下添加需要的组件脚本

如下图

{% asset_img unity_02.png 刺蛇预制体的脚本添加 %}

加载刺蛇预制体,这里使用 Lua 加载的

``` Lua
local go = UnityEngine.GameObject.Instantiate(prefab)

效果

按 wasd 来操作刺蛇移动

很多2D老资源是打好成一张图的,我们需要去切成一个一个的sprite

这时候导入图片后,材质类型选择 Sprite and UI , Sprite 类型选择 Mutiple

然后打开 Sprite Editor,自己切就可以了

Sprite Editor 里面选择 Slice 切片

如果图片中有框,选择 自动切应该就 OK

否则还有按 大小切 和 按个数来切

切完之后 点击每个片片,可以手动再修改一下


使用脚本来处理每个切片

我们导入的素材每个sprite有个小黑框,这个小黑框我们不要,怎么办呢?

每个手动去重新校正太麻烦,自己写个Editor脚本来解决吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class SpriteCustomScrips : MonoBehaviour {

[MenuItem ("Sprites/Rect small by 1")]
static void SpritesSmallBy1 () {
// Debug.Log (Selection.

string path = AssetDatabase.GetAssetPath (Selection.activeObject);
Debug.Log (path);

TextureImporter ti = AssetImporter.GetAtPath (path) as TextureImporter;
SpriteMetaData[] spriteSheet = ti.spritesheet;
for (int i = 0; i < spriteSheet.Length; i++) {

if (spriteSheet[i].name.StartsWith ("Hydra_walk_")) {
spriteSheet[i].rect = new Rect (spriteSheet[i].rect.x + 1, spriteSheet[i].rect.y + 1, spriteSheet[i].rect.width - 2, spriteSheet[i].rect.height - 2);
}
}
ti.spritesheet = spriteSheet;
EditorUtility.SetDirty (ti);
ti.SaveAndReimport ();

}
}

上面的脚本大家可以参考一下,修改了一下sprite的rect

然后我们在project视图中选中刚才自动切好的资源,点击菜单处理一下,发现1px的小黑框没有了

UGUI 中的按钮有4个状态,包括 Normal highlighted pressed disabled

而导航属性可以决定这几个状态之间的切换

如果选中了导航属性,则按钮在点击后,会进入选中状态,表现为highlighted, 鼠标移出,也不会回复normal状态

类似于老式录音机的 按下 或者说 单选组 状态,需要按其他的按钮才能回到 normal 状态

所以我们搞单个按钮的时候 应当把按钮的导航关闭


导航的另外一个功能,就是UI按钮的切换高光顺序

类似于我们在UI上按 tab 按键 切换选中的元素一样

至于导航的几个选项,就是字面上的意思啦

Mac 更新系统后原来机器上的 apache 配置丢失,我又不想去重新配置 apache 的虚拟目录和虚拟服务器

对付下测试环境的文件资源服务器,用 Node 还是挺方便的,那么步骤如下


需求 : Node 环境已经装好

作为一个资深API调试员,使用开源库

https://github.com/cloudhead/node-static

使用命令行安装之

1
npm install -g node-static

然后编写 Node 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
var port = 8080;
var doc_root = "/server_root" //文件服根目录


var static = require('node-static');

var fileServer = new static.Server(doc_root);

require('http').createServer(function(request, response) {
request.addListener('end', function() {
fileServer.serve(request, response);
}).resume();
}).listen(8080);

启动 Node 脚本

完成

有时候我们需要从字体文件中直接提取文字并导出成PNG格式

经过Google后找到可以用 ImageMagick 来搞

以下方式都在 Mac 下测试通过,Windows也可以,但安装方式稍有不同


下载 MacPorts

应该是和 homebrew 类似的一个安装工具

安好后经可以使用命令行来获取 ImageMagick

点我去下载

注意看自己的 macOS 版本

安装 ImageMagick

安好 MacPorts 后

命令行获取即可

1
sudo port install ImageMagick

也可以用源码直接安装,参考 这里

基本用法

1
convert -background none -fill black -font 方正像素12.TTF -pointsize 300 label:"主" 主.png

详情参考

这里

原文地址:Mapz的blog

前文目录:

  1. Django项目的搭建
  2. Django项目开发(一)
  3. Django项目开发(二)View层
  4. Django项目开发(三)结合Vue

前文讲了我们怎么把vue和django结合起来,这次我们讲讲vue的基本开发

初见Vue

项目结构

假设我们已经知道了css html 和 JavaScript 基础,那 Vue 对你来说肯定不是难事,他的简单易用性可以快速上手

我们查看我们之前生成的单页应用的src目录

1
2
3
4
5
6
7
8
9
.
├── App.vue # 入口vue组件
├── assets # 资源目录
│   └── logo.png
├── components # 组件目录
│   └── Hello.vue # 组件
├── main.js # 启动代码
└── router # 路由
└── index.js

.vue 文件是 vue 的基本组成形式,一个文件代表一个组件

其中 template 区域表示本组件的模版, script 区是代码区 type 标签里面则是样式声明

type 可以使用多种样式表语言 css less 什么的都可以(需要安装相关支持)

我们再看App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'app'
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

那么这个组件是怎么变成入口的呢

可以看外面的index.html

中间有个 id=app 的div

在 main.js 中有这么一段

1
2
3
4
5
6
7
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})

这里的 el 就是这个 vue 实例要挂载的元素
template 是用来挂在的模版
components 是用来挂在的内容,也就是我们的 App.vue

这样App.vue的内容就自动注入到index.html中了

数据绑定

前面我们讲了,Vue 的特点之一是数据双向绑定,也就是说展示的内容和模型的内容是绑定的,同步的,修改一个,另一个就会一起变化,Vue在实现这个功能的时候,为了将所有数据都收归 Vue 来管理,所以数据的声明必须由 Vue 来操作

我们移步hello.vue

在代码块中有个 data() 函数,这个函数里面就是声明我们的数据区,在这里面声明的内容,Vue会自动给他加上 getter和setter,这个过程对使用者来说事透明的,你不会注意到她,当一个数据读取或者变化的时候,就会调用setter和getter,从而触发 Vue 对绑定这个数据的内容的刷新,所以我们的数据块,都在 data() 区声明,是很重要的

另外,data() 返回的不是一个数据实例,而是一个返回数据副本的函数,这个很重要

为什么呢,因为我们知道 Vue 的组件是可以复用的,所以每次都要返回单独的数据实例呀,否则不是成了相当于 Java 中 static 数据的东西了吗

1
2
3
4
5
6
7
8

<h1>{{ msg }}</h1>

data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}

我们看到,我们声明的数据,在写模版的时候用双层引号来渲染

路由

我们的App.vue是怎么显示出hello.vue组件的内容的呢

我们看到 app 的模版中 有个

1
<router-view></router-view>

这个标签会自动渲染路由的内容

在 main.js 中我们引入了路由

1
import router from './router'

在router的index.js中我们看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/', //配置访问路径
name: 'Hello', //配置路径“页面”名称
component: Hello //配置要渲染的组件
}
]
})

使用了vue-router工具来做路由

导出了一个路由配置

所以我们在访问路径/的时候,Hello.vue就渲染在了 router-view 上


安装vue-admin

ElementUI文档

ElementUI是饿了吗弄的一个模版,适合来做管理工具的前端

我们这里就直接使用一个基于elementui的开源模版vue-admin来开发

开发的时候又问题随时参考文档

vue-admin

下载下来

删掉 frontend 目录里面的所有内容,然后把下载下来的 vue-admin 内容解压 copy 进去

然后在 frontend 中执行

1
npm install --registry=https://registry.npm.taobao.org

因为里面有安装node-sass

如果安装出错了,可以先安装这个东西(支持一种css加强语法的工具),加上镜像

1
SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/ npm install node-sass
1
npm build

我们还需要修改webpack中的一个参数让导出的静态调用地址适配 django中 的设置

1
STATIC_URL = '/static/'

打开 vue 项目中的webpack.config.js文件

修改为

1
2
3
assetsRoot: path.resolve(__dirname, '../dist/static'),
assetsSubDirectory: '',
assetsPublicPath: '/static/',

这样webpack导出的内容中的static的东西,都会以/static/的url前缀去调用,和django中一样了

访问 django 的站点 http://127.0.0.1:8000/qk_deploy/

和 vue 的测试站点 http://127.0.0.1:8010/

现在都可以正确访问我们的内容了

但是为了不每次访问的时候都打包,所以一般我们用 vue 的测试站点来进行测试


对接API:登陆

我们可以看到 vue-admin 里面使用的是 axios 作为 Ajax 库

作为示范,作者使用 mock 来制作了假数据

我们屏蔽掉 mock 开始接入我们写好的 django 登陆 api

1
2
3
// 修改 main.js 屏蔽mock
// import Mock from './mock'
// Mock.bootstrap();

修改 api.js,设置为我们的接口地址

1
2
// let base = '';
let base = 'http://127.0.0.1:8000/qk_deploy/api'

访问 http://127.0.0.1:8080/#/login

测试登陆

我们已经可以请求登陆啦~~

原文地址:Mapz的blog

前文目录:

  1. Django项目的搭建
  2. Django项目开发(一)
  3. Django项目开发(二)View层

前面我们介绍了Django的一些基本安装和使用

因为我们项目将会使用前后端分离的技术来处理,所以我们将会放弃使用Django的View层来处理业务逻辑,而只是用Django做ORM和API接口,并使用 Vue.js 2 来制作前端页面


Vue.js 简介

MVVM

Vue 采用 MVVM 的模式,MVVM 是 MVC 的衍生物

他和 MVC 的区别在哪儿呢

在 MVVM 中 C 变成了 VM

在 MVC 中用户可以直接操作 C 层 或是通过 V 层来操作 Model

C 层一般只有路由的作用 而 V 层则承载了大量的业务逻辑,非常臃肿

而在 MVVM 中 View 和 Model 之间不再直接发生关系了

View 层也不在有任何业务逻辑,他是真的只作为展示层来使用

逻辑去了哪儿呢?答案是 VM 层,就是 ModelView

然后为什么说 MVVM 可以方便的实现数据和逻辑分离呢

关键在 VM 层和 M 层是双向绑定的

也就是说,我在 VM 层经过逻辑处理的数据,会自动表现在 View 层上,也会自动改变 Model 层中的数据

于是我们的数据、逻辑和渲染,就完全分开了

简而言之:

我只需要修改好我的业务逻辑,配置好数据,写好模版,所有的一切就可以自动呈现和更新了

业务逻辑,数据和展示真正做到了分离

Vue.js

Vue React Angular 都是最近比较流行的前端框架

数据的双向绑定,响应式和组件化 都是他们的思维要点

Vue 相比另两个,优点是学习成本低,性能高,并且虽然比不上另外两个,但任然有着比较丰富的生态

另外 Vue 还有他的原生兄弟 Weex

Weex 是阿里的跨平台用户界面开发框架,他兼容 Vue 的语法

也就是说

学会 Vue 之后,你也可以让你的代码跑上 iOS 和 Android

当然啦,React 也有对应的 ReactNative 来做原生应用


集成 Vue 到 Django 中

我们假定你的机器上已经装好了

  • Node.js 4+
  • npm 3+
  • Python 2.7+

以下安装需要基于上述环境

安装 vue-cli

vue 一般使用 vue-cli 来初始化我们的项目

1
npm i vue-cli -g --registry=https://registry.npm.taobao.org

-g 的意思是全局安装

–registry 参数是镜像,我们在国内一般用淘宝的镜像比较快

创建 vue 项目

回到我们的django项目的目录下,用vue-cli 创建一个项目

1
vue-init webpack frontend

其中 frontend 是我们的 vue 工程目录名称

中间的参数是我们要用到的模版

1
npm run build

这个命令可以导出webpack输出,简单说就是web工程打包

build 好的内容导出 dist 目录下,也就是我们django中要用到的内容了

Django下的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# settings.py 需要加载模版,所以要修改我们的模版目录加上dist目录
TEMPLATES = [
{
...
'DIRS': ['frontend/dist/'],
...
},
]

...

# 让Django能去dist下寻找静态文件
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]

# qk_deploy/urls.py
from django.views.generic import TemplateView

# 将我们的文件直接作为模版引入
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name="index.html"))
]

我们现在访问

http://127.0.0.1:8000/qk_deploy/#/

应该可以看到 vue 的欢迎页面了

搭建调试环境

如果我们使用 Django的环境来进行测试呢,也就会导致,每次修改了vue项目的文件都要重新build,这样太慢了

vue.js的开发环境会自动检查文件变化来重新构建,是比较方便的

1
npm run dev

会启动vue的开发环境,但这样有个问题,使用vue的开发环境,就是脱离了Django的开发环境了,访问Django上的API出现了跨域问题

为了能让我们快乐的调试呢,我们使用Django的第三方插件django-cors-headers来解决跨域问题

1
pip install django-cors-headers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# settings.py
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', #注意顺序不能变
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

...

CORS_ORIGIN_ALLOW_ALL = True #添加这个配置

Django 配置 RESTful API

不用Django的后端渲染了,我们使用RESTful API作为前后端交流的工具

安装 djangorestframework 

1
2
3
pip install djangorestframework -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install markdown -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install django-filter -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

这里我们和 npm 安装东西的时候一样,使用了镜像,不然下载的太慢了啦 pip 使用镜像用-i参数
对于不是https的地址,必须用 –trusted-host 才能继续

装好后

1
2
3
4
5
6
7
8
9
10
11
# settings.py
INSTALLED_APPS = (
...
'rest_framework',
)

# 项目根目录下 url.py
urlpatterns = [
...
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

创建model和serializer

我们这里将用用户登陆为例,来看怎么写和使用API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# views.py
from .serializers import *
from rest_framework import permissions,status
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.authentication import BasicAuthentication

class UserLogin(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = LoginSerializer
authentication_classes = (BasicAuthentication,) #这里一定要是 BasicAuthentication,否则会出现CSRF问题,因为我们现在是登陆,登录前么有在session中用CSRF,这个问题我们后面来说

def post(self, request, *args, **kwargs):
try:
username = request.data.get('username')
print(username)
password = request.data.get('password')
print(password)
user = User.objects.get(username__iexact=username)
print(user)
if user.check_password(password):
serializer = LoginSerializer({'id': user.id, 'username': user.username})
return Response(serializer.data)
return Response(status=status.HTTP_401_UNAUTHORIZED)
except User.DoesNotExist:
return Response(status=status.HTTP_401_UNAUTHORIZED)

创建我们的登陆view

创建serializers.py

1
2
3
4
5
6
7
8
9
10
11
12
#coding:utf-8
from django.contrib.auth.models import User
from rest_framework import serializers

class LoginSerializer(serializers.ModelSerializer):

username = serializers.CharField(required=False, max_length=1024)
password = serializers.CharField(required=False, max_length=1024)

class Meta:
model = User
fields = ('id', 'username', 'password')

urls.py中加上

1
2
3
4
urlpatterns = [
...
url(r'^api/login$', UserLogin.as_view()),
]

访问

http://127.0.0.1:8000/qk_deploy/api/login

可以看到 restful_framework的测试界面

在界面测试API

用已有的用户登陆,已经可以看到正确的回应了,否则会返回状态401

原文地址:Mapz的blog

前文目录:

  1. Django项目的搭建
  2. Django项目开发(一)

前面我们已经创建好了我们的数据库表,也可以管理我们的数据内容了,下面就应该是如何来展示我们的网站内容

基本方式

前面大家看例子的时候已经看到了,我们在 views.py 中,写一个函数,返回一个HttpRespnse对象,参数为字符串hello world,并在 urls.py 里面配置好他的路由,就可以通过浏览器来访问我们的页面了,没错,这就是一个最简单的view,返回一个HttpRespnse对象,只包含一段字符串到浏览器。


Url路由

1
2
3
urlpatterns = [
url(r'^$', views.index, name='index'),
]

每增加一个路由,我们都在urlpatterns里面增加一个url()函数生成的对象

我们可以看看这个函数原型

1
def url(regex, view, kwargs=None, name=None, prefix='')
  1. 第一个参数为浏览器访问的url路由正则表达式
  2. 第二个参数是处理这个请求的view
  3. 第三个参数是显式的传入参数
  4. 第四个参数是这个url的名称,常用于渲染模版的时候,避免改需求后模版上url写死了要到处修改用(写模版的时候不要写死跳转的url,而是用name来渲染是一个良好的习惯),另外,后面我们会知道,一个视图view经常对应很多pattern,用name也可在使用的时候加以区分
  5. prefix 在view上加上前缀,一般不会用到这个

正则表达式中用小括号括起来的内容,会作为参数传入到view中的处理函数中

为了测试路由中各种用法,我们创建一个文件专门放我们的测试view

隐式的传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# test_views.py
# -*-coding:utf-8 -*-
from django.http import HttpResponse


def add(request,p1,p2):
p1 = float(p1)
p2 = float(p2)
return HttpResponse("%f + %f = %f" % (p1,p2,p1+p2))



# urls.py
from . import views,test_views
testPatterns = [
url(r'^add(\d+)and(\d+)/$', test_views.add, name='add'),
]

urlpatterns.extend(testPatterns)

我们看url中有两个参数

当我们访问 http://127.0.0.1:8000/qk_deploy/add1and2/ 的时候,他们被顺序的传入test_view.add函数的p1和p2

需要注意的是,通过正则穿进去的参数,都是字符串形式的,要转型需要我们在代码中处理

最后我们在浏览器中得到了返回

1.000000 + 2.000000 = 3.000000

显式的传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# test_views.py
def southparkB(request,stan_power,cartman_power):
stan_power = float(stan_power)
cartman_power = float(cartman_power)
return HttpResponse('Stan win' if stan_power > cartman_power else 'Cartman win')



# urls.py
testPatterns = [
...
url(r'^southpark-b/stan(?P<stan_power>\d+)/cartman(?P<cartman_power>\d+)/$', test_views.southparkB,name='southparkB')
]

urlpatterns.extend(testPatterns)

用括号括起来的部分依然是参数,但是我们用__?P<参数名>__来传递参数
然后在 test_view.southparkB 中,用相同的参数名来取出

访问

http://127.0.0.1:8000/qk_deploy/southpark-b/stan19/cartman10/

获得结果

Stan win


结合数据层使用

1
2
3
4
5
6
7
8
9
10
11
# test_views.py
def project_list(request):
p_list = Project.objects.order_by('project_name')
output = ', '.join([q.project_name for q in p_list])
return HttpResponse(output)

#urls.py
testPatterns = [
...
url(r'^projectlist/$',test_views.project_list,name='project_list'),
]

访问 http://127.0.0.1:8000/qk_deploy/projectlist/

可以看到打印出了我们已经创建的项目列表


加入模版系统

我们现在已经可以输出我们需要的内容到页面了,但是还不够美观,我们想要输出的是一个页面,而不是简单的字符串。

这时候我们就要用到 Django 的服务端渲染-模版技术

题外话:如果你想使用前后端分离

现在前后端分离的开发方式比较热门,笔者也研究过 vue.js 前端框架

要实现前后端分离,我们的后端渲染就算是用不上啦,至于业务逻辑,也需要放到前端来处理,view也不用处理业务逻辑了

我们只需要把API接口做好就可以了

我们只需要保留Django的controller部分,也就是urlconf这一块

这样一来做到了前后端分离

二来呢,django可以是很灵活的,同一个网站你可以部分页面采用 vue 部分采用 react,部分用django模版来渲染也行呀~

我们的项目将会基于vue.js 2来进行开发,搭建将在后面的文章中详细解说

在这之前,我们还是先简单讲讲 Django 自身的模版系统

创建模版

我们在app目录下创建templates目录

模版的具体设置在settings中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

当 APP_DIRS 为 True 的时候,Django会所有已经安装了的APP目录下的templates中寻找模版

我们继续在templates目录下创建目录qk_deploy目录,并在里面创建project_list.html

1
2
3
4
5
6
7
8
9
{% if p_list %}
<ul>
{% for p in p_list %}
<li>{{ p.project_name }}</li>
{% endfor %}
</ul>
{% else %}
<p>还没有创建项目</p>
{% endif %}

修改我们的view

1
2
3
4
5
6
7
8
9
10
11
12
# test_views.py
from django.template import loader

...

def project_list(request):
p_list = Project.objects.order_by('project_name')
template = loader.get_template('qk_deploy/project_list.html')
context = {
'p_list':p_list,
}
return HttpResponse(template.render(context,request))

重新访问 http://127.0.0.1:8000/qk_deploy/projectlist/

我们会发现我们的列表已经按模版的形式重新渲染了

  • 项目1
  • 项目2
  • 等等

render还可以简写为

1
2
3
4
5
6
7
8
from django.shortcuts import render
...
def project_list(request):
p_list = Project.objects.order_by('project_name')
context = {
'p_list':p_list,
}
return HttpResponse(render(request,'qk_deploy/project_list.html',context))

其他模版相关内容

我们这里就不赘述其他的 django 模版相关的内容了

有兴趣可以参考 官方教程

原文地址:Mapz的blog

这是系列第二篇,第一篇请移步

Django项目的搭建

上一次我们介绍了如何搭建django项目,本次我们继续学习django项目的开发


数据库的接入

我们这里使用mySql数据库

先在虚拟环境中装 pymysql (MySQLdb在Python3中已经不能用)

1
pip install pymysql

打开settings.py

修改

1
2
3
4
5
6
7
8
9
10
11
12
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
'ENGINE': 'django.db.backends.mysql', #数据库引擎
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': 'proj_mgr_kits', #你的数据库名称
'USER':'***', #数据库用户
'PASSWORD':'***', #数据库密码
'HOST':'***', #数据库主机
'PORT':'3306', #数据库端口
}
}

详细修改方式参见 官网文档

另外针对 pymysql 还需要配置站点的_init_.py

1
2
import pymysql
pymysql.install_as_MySQLdb()

其他配置

设置时区和语言

1
2
3
LANGUAGE_CODE = 'zh-Hans'

TIME_ZONE = 'PRC'

Django结构简介

django是一个MVC框架,数据上,使用模型model做ORM映射数据库

view层使用django的模版来做渲染

控制层用灵活的url路由来实现,后面我们陆续来介绍这些内容


创建数据库表

1
python manage.py migrate

这个命令会创建所有已安装的APP所需的数据表

所有已经安装的APP被配置在 settings.py
INSTALLED_APPS 属性下


创建model

Model 是 Django做对象映射的模型,你创建的 Model 类会被diango映射到数据库中

我们创建一个自己的model

修改 models.py

1
2
3
from django.db import models
class Project(models.Model):
project_name = models.CharField(max_length=50)

Model 支持的数据类型和创建参数参见 官方文档

常用的参数比如choices,可以让参数在一个枚举中选择

例如

1
2
3
4
5
6
class DeployProfile(models.Model):
deploy_type_choice = (
('Server','服务器'), #第一个参数为数据库保存值,第二个为显示在界面的值
('CLient','客户端')
)
deploy_type = models.CharField(max_length=20, choices=deploy_type_choice, verbose_name='部署类型')

其他的类型就不再赘述


把我们自己的app加入到项目app中

编辑settings.py

1
2
3
4
5
INSTALLED_APPS = [
'xxxxx'
'qk_deploy.apps.QkDeployConfig',
'xxxxx'
]

重新创建表

1
python manage.py makemigrations qk_deploy

makemigrations 命令告诉系统,我的模型已经发生了变化,需要制作一个迁移

迁移做好之后,我们再用

1
python manage.py migrate

重新制作数据表


创建管理员

1
python manage.py createsuperuser

然后按提示输入,来创建一个管理员

启动服务器后

进入页面

http://127.0.0.1:8000/admin/

就可以用你的管理员来登陆管理界面了


把我们的app加入到管理界面中来

修改app目录下的admin.py

1
2
3
4
5
from django.contrib import admin

# Register your models here.
from .models import Project
admin.site.register(Project)

就可以了

register参数也接收一个可迭代容器

1
admin.site.register([Project,p1,p2,p3])

然后我们进入管理页面,发现上面的model已经可以管理了

但是如果我们想有一个自定义的名称,而不是类的名字,怎么办呢?

答案是在model中加上 meta

编辑Project model

1
2
3
4
5
6
class Project(models.Model):
project_name = models.CharField(max_length=50)

class Meta:
verbose_name = '项目'
verbose_name_plural = verbose_name

其中 verbose_name 是显示的名称,而 plural是复数形式,我们中文没有复数形式,所以就直接一样就ok了

双薪管理页面,我们发现管理中显示的内容已经是我们写的’项目’了

至于模型的字段要显示我们想要的名字呢,也是一样的,修改model

1
2
3
4
5
6
class Project(models.Model):
project_name = models.CharField(max_length=50, verbose_name='项目名称')

class Meta:
verbose_name = '项目'
verbose_name_plural = verbose_name

好奇的同学又要问了,APP名字在管理界面怎么改呀

我们修改apps.py

在QkDeployConfig类下面增加一个属性

1
2
3
class QkDeployConfig(AppConfig):
name = 'qk_deploy'
verbose_name = '部署管理器'

刷新管理界面再看看~~

我们用管理工具创建新项目后,新项目在列表里面显示为 project object,这显然不是我们想要的,怎么办咧

为model加上字符串返回函数就可以了

1
2
3
4
5
6
7
8
9
class Project(models.Model):
project_name = models.CharField(max_length=50, verbose_name='项目名称')

def __str__(self):
return self.project_name

class Meta:
verbose_name = '项目'
verbose_name_plural = verbose_name

刷新管理界面,显示的项目已经是我们设定的项目名称了


文本编码问题

如果遇到中文无法录入管理系统的问题,请检查数据库编码是否和程序中一致哦

不一致的话,请先修改数据库编码,然后删除数据表,重新migrate


到此,我们的数据库管理功能已经完成,也就是MVC中的M

下次我们继续说MVC中的V ,也就是 view 展示层

原文地址 Mapz的Blog

前言

Django作为成熟的 Python Web框架,有很多第三方模块,比如djangoPackages就是一个发布django模块的站点。

Django作为快速开发,还是比较适合的。

安装Django

官网文档

我们用Python3.5来安装Django

老路子,Python工程,先创建虚拟环境,避免包环境污染

1
virtualenv django_env

Django本体安装

我这里默认环境是3.5python,如果你不是默认3.5的话,需要指定 virtualenv 参数,这里就不赘述了

进入虚拟环境

1
source ./django_env/bin/activate

使用pip安装django,我们就安装最新的版本吧

1
pip install Django==1.11.1

安装后校验是否成功

进入python控制台

1
python

校验安装版本

1
2
3
>>> import django
>>> django.get_version()
'1.11.1'

或者简单的在shell中检查

1
python -m django --version

数据库安装

我们这里就用mysql来作数据库,安装过程略去


项目搭建

创建项目

在合适的目录下

1
django-admin startproject proj_mgr_kits

django会创建一个目录树

1
2
3
4
5
6
7
8
9
10
11
12
13
proj_mgr_kits/   #可以随意改名,这个目录名不影响django的逻辑
manage.py #django的管理命令行工具
proj_mgr_kits/ #这是一个python包目录了
__init__.py
settings.py #配置文件
urls.py
wsgi.py
```

### 启动测试服

``` bash
python manage.py runserver

浏览器打开 http://127.0.0.1:8000/

出现

1
2
3
4
It worked!
Congratulations on your first Django-powered page.
Next, start your first app by running python manage.py startapp [app_label].
You're seeing this message because you have DEBUG = True in your Django settings file and you haven't configured any URLs. Get to work!

启动成功

runserver 后面接上IP端口号,可以指定启动的IP和端口

测试服启动期间,修改了python代码后,会自动加载

如果有不能加载的,请手动restart

Django的App和Project概念

Django中 的一个Project,简单说就是一个网站

一个网站中可以有很多的App , 也就是功能模块

一个App可以在多个项目中使用

创建我们的APP

我们创建一个名为 qk_deploy 的APP

1
python manage.py startapp qk_deploy

将会在目录中创建 qk_deploy 目录

1
2
3
4
5
6
7
8
9
qk_deploy
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py

创建我们的第一个页面

这里包含我们写Django的几个基本步骤

  1. 创建页面
  2. 配置路由

创建页面

修改 view.py

1
2
3
4
5
from django.http import HttpResponse


def index(request):
return HttpResponse("Hello, world.")

配置路由

修改 app下的 urls.py(没有就自己创建一个)

1
2
3
4
5
6
7
from django.conf.urls import url

from . import views

urlpatterns = [
url(r'^$', views.index, name='index'),
]

修改 工程下的 urls.py

1
2
3
4
5
6
7
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
url(r'^qk_deploy/', include('qk_deploy.urls')),
url(r'^admin/', admin.site.urls),
]

访问页面

浏览器打开 http://127.0.0.1:8000/qk_deploy/

出现

Hello, world.

一个页面就创建成果了,是否很简单哇