使用gulp4.0搭建前端自动化开发环境

本文要实现的目的是使用gulp整合前端开发环境,当前端scss样式文件,JavaScript文件或者图片资源文件、html文件发生任何改变时,自动触发gulp进行压缩、合并、重命名、并使浏览器自动加载变更内容。

1. npm init

创建一个文件夹用于初始化项目,这里使用front
然后在front目录执行npm init命令,按照提示均默认输入即可。

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
mkdir front
cd front
➜ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (front)
version: (1.0.0)
description: front source
entry point: (index.js)
test command:
git repository:
keywords:
author: moonwhite
license: (ISC)
About to write to /Users/yourusername/front/package.json:

{
"name": "front",
"version": "1.0.0",
"description": "front source",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "moonwhite",
"license": "ISC"
}


Is this OK? (yes) yes
➜ front

关于本文中使用到的package及版本可以参考下面package.json文件,可以将下面代码复制到package.json中,然后执行npm install即可。

1
2
3
4
5
6
7
8
9
10
11
12
"devDependencies": {
"browser-sync": "^2.26.13",
"gulp": "^4.0.2",
"gulp-cache": "^1.1.3",
"gulp-concat": "^2.6.1",
"gulp-cssnano": "^2.1.3",
"gulp-imagemin": "^7.1.0",
"gulp-rename": "^2.0.0",
"gulp-sass": "^4.1.0",
"gulp-uglify": "^3.0.2",
"gulp-watch": "^5.0.1"
}

2. 项目目录结构

初始项目目录结构如下:

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
├── dist                        # 生产发布目录
│   ├── css # 生产css文件存放目录
│   │   └── sample.min.css # gulp处理后的生产css文件
│   ├── images # 生产图片文件存放目录
│   │   └── *.jpg # 压缩后的生产图片文件
│   ├── js # 生产存放js文件的目录
│   │   └── index.min.js # gulp丑化压缩后的生产js文件
│   └── other
├── gulpfile.js # gulpfile
├── node_modules # npm依赖模块目录
│   └── ...
├── package-lock.json
├── package.json # package文件
├── src # 前台源码目录
│   ├── css # css源码
│   ├── images # 存放原图
│   │   └── *.jpg
│   ├── js # JavaScript源码
│   │   └── *.js
│   └── scss # sass源码
│   └── *.scss
├── templates # HTML模版目录
│   ├── moduledir # 模块文件夹
│   └── moduledir
│   └── *.html
└── otherfile

3. 编写gulpfile

最后gulpfile.js 内容如下:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
const { series, parallel } = require('gulp');

const gulp = require("gulp")
const cssnano = require("gulp-cssnano")
const rename = require('gulp-rename')
const uglify = require('gulp-uglify')
const concat = require('gulp-concat')
const sass = require('gulp-sass')
const cache = require('gulp-cache')
const imagemin = require('gulp-imagemin')
const bs = require('browser-sync').create()

// 定义常用路径
var path = {
'css' : './src/css/',
'scss': './src/scss/',
'js' : './src/js/',
'images': './src/images/',
'dist_css': './dist/css/',
'dist_js': './dist/js/',
'dist_images': './dist/images/',
'templates': './templates/'
};


// sass version
function style(){
// 定义scss源文件位置
return gulp.src(path.scss + '**/*.scss')
// 传递给sass编译器生成css文件
.pipe(sass().on('error', sass.logError))
// 压缩css
.pipe(cssnano())
// 重命名为 filename.min.css
.pipe(rename({suffix:'.min'}))
// 3. 保存编译后的css文件
.pipe(gulp.dest(path.dist_css));
// 4. stream changes to all browser.
// 由于watch 中监视了path.dist_css,所以这里不再需要了。
// .pipe(bs.stream());
}

// Javascript任务
function js(){
return gulp.src(path.js + '*.js')
.pipe(concat('index.js'))
.pipe(uglify({
toplevel: true,
compress: {
drop_console : true
}
}))
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(path.dist_js));
}

// 压缩图片
function images(){
gulp.src(path.images + '*.*')
.pipe(cache(imagemin()))
.pipe(gulp.dest(path.dist_images));
}

function watch(){
bs.init({
server:{
baseDir: './'
}
});
// gulp.watch('./css/**/*.css', css);
// 监视path.scss 目录,如果有变动则执行style任务。
gulp.watch(path.scss + '**/*.scss', style);

// 监视path.js 目录,如果有变动则执行js任务。
gulp.watch(path.js + '**/*.js', js);

// 监视path.images 目录,如果有变动则执行images任务。
gulp.watch(path.images + '*.*', images);

// 监视path.templates 目录,如果有变动则通知浏览器reload。
gulp.watch(path.templates + '**/*.html').on('change', bs.reload);

// 监视path.dist_is 目录,如果有变动则通知浏览器reload。
gulp.watch(path.dist_js + '*.js').on('change', bs.reload);

// 监视path.dist_css 目录,如果有变动则通知浏览器reload。
gulp.watch(path.dist_css + '*.css').on('change', bs.reload);

// 监视path.dist_images 目录,如果有变动则通知浏览器reload。
gulp.watch(path.dist_images + '*.*').on('change', bs.reload);
}

// 导出任务,使得可以在命令行使用。
exports.style = style;
exports.js = js;
exports.images = images;
exports.watch = watch;

最后,在front目录执行gulp watch命令即可,效果如下:

前端自动化开发环境搭建笔记

1. nvm的安装

nvm(node version manager): 是node.js的版本管理工具,

1.1 安装nvm

官方安装说明文档
安装命令:

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash

或者

1
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash

1.2 配置环境变量

然后还需要把下面环境变量内容添加到.bash_profile中,如果使用oh-my-zsh的话配置到.zshrc中

1
2
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

1.3 常用命令

1
2
3
4
5
6
7
8
9
10
11
12
nvm install 8.0.0                     安装特定版本的node
nvm use 8.0 使用最新可用的8.0.x的release版本
nvm run 6.10.3 app.js 使用node 6.10.3运行app.js
nvm exec 4.8.3 node app.js 使用4.8.3版本的node运行 app.js
nvm alias default 8.1.0 设定node默认版本
nvm alias default node 总是默认设置最新可用的node版本
nvm install node 安装最新可用版本的node
nvm use node 使用最新可用版本的node
nvm install --lts 安装最新的LTS版本的node
nvm use --lts 使用最新的LTS版本的node
nvm set-colors cgYmW 设置文本颜色为: cyan, green, bold yellow, magenta, and white

2. 安装node和npm

安装完nvm之后,我们就可以使用nvm来安装node了。

1
2
3
4
# 安装最新可用版本的node
nvm install node
# 安装最新的LTS版本的node
nvm install --lts

npm类似于python的pip,可以方便的安装一些前端开发的包。
在安装完node之后npm就也自动的被安装了

3. gulp的安装及使用

gulp用来自动化开发流程,比如saas转css,css和js压缩等。

3.1 安装gulp

1
2
3
4
5
6
# 本地安装
npm install gulp
# --save-dev表示写入package.json文件
npm install gulp --save-dev
# 参数-g表示global,使得gulp可以在全局环境比如系统命令行中使用。
npm install gulp -g

3.2 gulp的使用

在项目根目录创建一个gulpfile.js,然后在gulpfile.js中插入如下代码:

1
2
3
4
5
6
7
8
9
10
var gulp = require("gulp")
var cssnano = require("gulp-cssnano")

// 定义css文件改动任务
gulp.task("css", done => {
gulp.src("./css/*.css")
.pipe(cssnano())
.pipe(gulp.dest("./dist/css"))
done();
});

3.3 gulp常用命令及插件

1
gulp task #执行gulp命令

常用gulp包

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
# 文件重命名
npm install gulp-rename --save-dev

# css压缩
npm install gulp-cssnano --save-dev

# js压缩
npm install gulp-uglifu --save-dev

# 合并多个文件
npm install gulp-concat --save-dev

# 压缩图片
npm install gulp-imagemin --save-dev

# 图片缓存
npm install gulp-cache --save-dev

# 文件监控自动执行任务
npm install gulp-watch --save-dev

# 浏览器自动同步
npm install browser-sync --save-dev


3.4 gulp常见问题

任务未完成错误

使用gulp css命令的时候,提示如下信息:

1
2
[21:41:30] The following tasks did not complete: css
[21:41:30] Did you forget to signal async completion?

错误代码:

1
2
3
4
5
6
7
8
9
var gulp = require("gulp")
var cssnano = require("gulp-cssnano")

// 定义css文件改动任务
gulp.task("css", function() {
gulp.src("./css/*.css")
.pipe(cssnano())
.pipe(gulp.dest("./dist/css"))
});

修改为如下格式即可解决:

1
2
3
4
5
6
7
8
9
10
var gulp = require("gulp")
var cssnano = require("gulp-cssnano")

// 定义css文件改动任务
gulp.task("css", done => {
gulp.src("./css/*.css")
.pipe(cssnano())
.pipe(gulp.dest("./dist/css"))
done();
});

Oracle Partition相关操作

常用语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--查看表分区
select partition_name, high_value from user_tab_partitions t where table_name = 'tablename'

--查询表分区绑定的字段名
select * from user_part_key-columns t where name='tablename'

-- 查看当前表分区的具体状况
select * from user_tab_partitions t where table_name = 'tablename'

-- 增加表分区
alter table tablename add partition partitionname values
less than ('20201212') logging nocompress;

-- 修改分区名称
alter table tablename rename partition oldpartname to newpartname;




Django笔记:7.views视图

Paginator常用属性和方法

  1. count:总共有多少条数据。
  2. num_pages:总共有多少页。
  3. page_range:页面的区间,比如有三页,那么就range(1,4)

Page 常用属性和方法

  1. has_next: 是否还有下一页。
  2. has_previous: 是否还有下一页。
  3. next_page_number: 下一页的页码。
  4. previous_page_number: 上一页的页码。
  5. number: 当前页。
  6. start_index: 当前页面的第一条数据的索引值。
  7. end_index: 当前页面最后一条数据的索引值。

Django笔记:6.ORM

ORM对数据库的基本操作

添加数据

只要使用ORM模型创建一个对象。然后再调用这个ORM模型的save方法就可以保存了。
示例代码如下:

1
2
book = Book(name='西游记',author='吴承恩',price=100)
book.save()

查找数据

所有的查找工作都是使用模型上的objects属性来完成的。当然也可以自定义查询对象。这部分功能会在后面讲到。

  1. 根据主键进行查找:使用主键进行查找。可以使用objects.get方法。然后传递pk=xx的方式进行查找。示例代码如下:

    1
    book = Book.objects.get(pk=2)
  2. 根据其他字段进行查找:可以使用objects.filter方法进行查找。示例代码如下:

    1
    books = Book.objects.filter(name='三国演义')

    使用filter方法返回来的是一个QuerySet对象。这个对象类似于列表。我们可以使用这个对象的first方法来获取第一个值。

删除数据

首先查找到对应的数据模型。然后再执行这个模型的delete方法即可删除。示例代码如下:

1
2
book = Book.objects.get(pk=1)
book.delete()

修改数据

首先查找到对应的数据模型。然后修改这个模型上的属性的值。再执行save方法即可修改完成。示例代码如下:

1
2
3
book = Book.objects.get(pk=2)
book.price = 200
book.save()

常用Field笔记

naive时间和aware时间

什么是naive时间?什么是aware时间?

  1. naive时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
  2. aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。

pytz库

专门用来处理时区的库。这个库会经常更新一些时区的数据,不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz的方式进行安装。

astimezone方法

将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware类型的时间调用。不能被navie类型的时间调用。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
import pytz
from datetime import datetime
now = datetime.now() # 这是一个navie类型的时间
utc_timezone = pytz.timezone("UTC") # 定义UTC的时区对象
utc_now = now.astimezone(utc_timezone) # 将当前的时间转换为UTC时区的时间
>> ValueError: astimezone() cannot be applied to a naive datetime # 会抛出一个异常,原因就是因为navie类型的时间不能调用astimezone方法


now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
utc_now = now.astimezone(utc_timezone)
# 这时候就可以正确的转换。

replace方法

可以将一个时间的某些属性进行更改。

django.utils.timezone.now方法

会根据settings.py中是否设置了USE_TZ=True获取当前的时间。如果设置了,那么就获取一个aware类型的UTC时间。如果没有设置,那么就会获取一个navie类型的时间。

django.utils.timezone.localtime方法

会根据setting.py中的TIME_ZONE来将一个aware类型的时间转换为TIME_ZONE指定时区的时间。

DateField

日期类型。在Python中是datetime.date类型,可以记录年月日。在映射到数据库中也是date类型。使用这个Field可以传递以下几个参数:

  1. auto_now:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
  2. auto_now_add:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True

DateTimeField

日期时间类型,类似于DateField。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_nowauto_now_add两个属性。

TimeField

时间类型。在数据库中是time类型。在Python中是datetime.time类型。

https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/

EmailField

类似于CharField。在数据库底层也是一个varchar类型。最大长度是254个字符。

FileField

用来存储文件的。这个请参考后面的文件上传章节部分。

ImageField

用来存储图片文件的。这个请参考后面的图片上传章节部分。

FloatField

浮点类型。映射到数据库中是float类型。

IntegerField

整形。值的区间是-2147483648——2147483647

BigIntegerField

大整形。值的区间是-9223372036854775808——9223372036854775807

PositiveIntegerField

正整形。值的区间是0——2147483647

SmallIntegerField

小整形。值的区间是-32768——32767

PositiveSmallIntegerField

正小整形。值的区间是0——32767

TextField

大量的文本类型。映射到数据库中是longtext类型。

UUIDField

只能存储uuid格式的字符串。uuid是一个32位的全球唯一的字符串,一般用来作为主键。

URLField

类似于CharField,只不过只能用来存储url格式的字符串。并且默认的max_length是200。

Field常用的参数

null

如果设置为TrueDjango将会在映射表的时候指定是否为空。默认是为False。在使用字符串相关的Field(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False。因为Django在处理字符串相关的Field的时候,即使这个Fieldnull=False,如果你没有给这个Field传递任何值,那么Django也会使用一个空的字符串""来作为默认值存储进去。因此如果再使用null=TrueDjango会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。如果你的FieldBooleanField,那么对应的可空的字段则为NullBooleanField

blank

标识这个字段在表单验证的时候是否可以为空。默认是False
这个和null是有区别的,null是一个纯数据库级别的。而blank是表单验证级别的。

db_column

这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。

default

默认值。可以为一个值,或者是一个函数,但是不支持lambda表达式。并且不支持列表/字典/集合等可变的数据结构。

primary_key

是否为主键。默认是False

unique

在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。

更多Field参数请参考官方文档:https://docs.djangoproject.com/zh-hans/2.0/ref/models/fields/

模型中Meta配置

对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table的属性。示例代码如下:

1
2
3
4
5
6
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")

class Meta:
db_table = 'book_model'

以下将对Meta类中的一些常用配置进行解释。

db_table

这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。

ordering

设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:

1
2
3
4
5
6
7
8
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
pub_date = models.DateTimeField(auto_now_add=True)

class Meta:
db_table = 'book_model'
ordering = ['pub_date']

更多的配置后面会慢慢介绍到。
官方文档:https://docs.djangoproject.com/en/2.0/ref/models/options/

表关系笔记

一对多

  1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
  2. 实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。
1
2
3
4
5
6
7
8
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)

class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("User",on_delete=models.CASCADE)

那么以后在给Article对象指定author,就可以使用以下代码来完成:

1
2
3
4
5
6
article = Article(title='abc',content='123')
author = User(username='zhiliao',password='111111')
# 要先保存到数据库中
author.save()
article.author = author
article.save()

并且以后如果想要获取某个用户下所有的文章,可以通过article_set来实现。示例代码如下:

1
2
3
4
5
user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
print(article)

并且如果想要将文章添加到某个分类中。可以使用一下的方式:

1
2
3
4
5
6
category = Category.objects.first()

article = Article(title='bbb',content='vvv')
article.author = FrontUser.objects.first()

category.article_set.add(article,bulk=False)
  • 使用bulk=False,那么Django会自动的保存article,而不需要在添加到category之前先保存article。
  • 或者是另外一种解决方式是,在添加到category.article_set中之前,先将article保存到数据库中。但是如果article.category不能为空,那么就产生一种死循环了,article没有category不能保存,而将article添加到cateogry.artile_set中,又需要article之前是已经存储到数据库中的。
  • 如果是上面的那种需求,建议使用bulk=False的解决方案。

一对一

  1. 在Django中一对一是通过models.OnetToOneField来实现的。这个OneToOneField其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key),来实现一对一。

  2. 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class FrontUser(models.Model):
    username = models.CharField(max_length=200)

    class UserExtension(models.Model):
    school = models.CharField(max_length=100)
    user = models.OneToOneField("FrontUser",on_delete=models.CASCADE)

    # 通过userextension来访问UserExtension对象
    user = FrontUser.objects.first()
    print(user.userextension)

    UserExtension的对象,可以通过user来访问到对应的user对象。并且FrontUser对象可以使用userextension来访问对应的UserExtension对象。
    如果不想使用Django默认的引用属性名字。那么可以在OneToOneField中添加一个related_name参数。示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class FrontUser(models.Model):
    username = models.CharField(max_length=200)

    class UserExtension(models.Model):
    school = models.CharField(max_length=100)
    user = models.OneToOneField("FrontUser",on_delete=models.CASCADE,related_name='extension')

    # 通过extension来访问到UserExtension对象
    user = FrontUser.objects.first()
    print(user.extension)

    那么以后就FrontUser的对象就可以通过extension属性来访问到对应的UserExtension对象。

多对多

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。

  2. 实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。还是拿文章和标签为例进行讲解。示例代码如下:

1
2
3
4
5
6
7
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
tags = models.ManyToManyField("Tag",related_name="articles")

class Tag(models.Model):
name = models.CharField(max_length=50)

在数据库层面,实际上Django是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到articletag两张表的主键。

Mac下不同版本Python

官网下载升级安装了Python3.9,之前brew安装过python3.8,Mac自带了2.7,Pycharm使用virtualenv安装过3.7,各种情况的默认目录如下:

来源 python安装路径
系统默认(2.7) /System/Library/Frameworks/Python.framework/Versions/2.x
pycharm(3.7) /Users/username/PycharmProjects/venv/python3.7
brew(3.8) /usr/local/Cellar/python@3.8/3.8.5/bin/
官网pkg安装(3.9) /Library/Frameworks/Python.framework/Versions/3.x

写入环境变量

1
2
vi ~/.bash_provile
export PATH=$PATH:/usr/local/bin/python3

preference -> project Interpreter -> 右侧的Add按钮
添加System Interpreter

添加Pipenv Environment

Django笔记:3.创建模型

建库建用户

1
2
3
4
5
6
7
8
-- 建库
create database djtest default charset=utf8;

-- 创建用户
CREATE USER 'djadmin'@'%' IDENTIFIED BY 'yourpasswd';

-- 为用户添加权限
GRANT ALL ON djtest.* TO 'djadmin'@'%';

项目配置

在项目的 settings.py 文件中找到 DATABASES 配置项,将其信息修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
'default' : {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'NAME': 'djtest', # 数据库名称
'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1
'PORT': 3316, # 端口
'USER': 'djadmin', # 数据库用户名
'PASSWORD': 'yourpasswd', # 数据库密码
}
}

接下来,告诉 Django 使用 pymysql 模块连接 mysql 数据库:

1
2
3
# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置
import pymysql
pymysql.install_as_MySQLdb()

如果没有pymysql模块,请使用pip安装

定义模型

创建app

Django中如果要使用模型,必须先创建一个app,使用下面命令创建一个TestModel的app

1
django-admin.py startapp TestModel

安装mysqlclient,报找不到mysql_config的错误请参考本文最后的常见错误及解决方法1

在命令行中运行python3 manage.py migrate 来创建表结构

执行成功后生成了这些表结构

1
2
# 让 Django 知道我们在我们的模型有一些变更
python3 manage.py makemigrations TestModel

1
2
# 创建表结构
python3 manage.py migrate TestModel

数据库上也可以看到已经生成了新的数据表:TestModel_test,表名的组成结构为:应用名_类名(如:TestModel_test)

添加数据

djtest/djtest/目录下新建testdb.py,内容如下

1
2
3
4
5
6
7
8
9
10
from django.http import HttpResponse

from TestModel.models import Test


# 数据库操作
def insdb(request):
test1 = Test(name='moonwhite')
test1.save()
return HttpResponse("<p>数据添加成功!</p>")

urls.py中添加如下内容:

1
path('insdb/', testdb.insdb),

浏览器访问localhost:8080/testdb,即可向数据库中添加一条记录

登陆数据库查看:

获取数据

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
26
27
28
29
30
31
32
33
34
35
36
37
38
def seldb(request):
# 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM
list = Test.objects.all()
str = ""
for var in list:
str += var.name + " "
return HttpResponse("<p>" + str +"</p>")

def seldb1(request):
# filter相当于SQL中的WHERE,可设置条件过滤结果
qs = Test.objects.filter(id=1)
str=""
for var in qs:
str += var.name + " "
return HttpResponse("<p>" + str +"</p>")

def seldb2(request):
# 获取单个对象
test2 = Test.objects.get(id=2)

return HttpResponse("<p>" + test2.name +"</p>")

def seldb3(request):
# 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2;
# Test.objects.order_by('name')[0:2]
# # 数据排序
# qs = Test.objects.order_by("id")

# 上面的方法可以连锁使用
qs = Test.objects.order_by("id")[0:4]

str = ""
for var in qs:
str += var.name + " <br>"


return HttpResponse("<p>" + str +"</p>")

更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13

def upddb(request):
# 修改其中一个ID=2的记录的name字段,再save,相当于SQL: update table set name='newvalue' where id = 2
test1 = Test.objects.get(id = 2)
test1.name = 'moonwhite02'
test1.save()
# 另外一种方式
# Test.objects.filter(id=1).update(name='Google')

# 修改所有的列
# Test.objects.all().update(name='Google')
return HttpResponse('<p>' + "update success " + '</p>')

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
def deldb(request):
# 删除id=1的数据
test1 = Test.objects.get(id=1)
test1.delete()

# 另外一种方式
# Test.objects.filter(id=1).delete()

# 删除所有数据
# Test.objects.all().delete()

return HttpResponse("<p>删除成功</p>")

常见错误及解决方法

1. pip install mysqlclient 报找不到mysql_config

pip install mysqlclient 后报错信息如下:

1
2
3
4
5
6
7
8
9
10
11
Collecting mysqlclient
Using cached mysqlclient-2.0.1.tar.gz (87 kB)
......
File "/private/var/folders/7p/r7xrt8r50l1cd6dh0dtz2n940000gp/T/pycharm-packaging/mysqlclient/setup_posix.py", line 65, in get_config
libs = mysql_config("libs")
File "/private/var/folders/7p/r7xrt8r50l1cd6dh0dtz2n940000gp/T/pycharm-packaging/mysqlclient/setup_posix.py", line 31, in mysql_config
raise OSError("{} not found".format(_mysql_config_path))
OSError: mysql_config not found
----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

解决方法:

1
2
3
4
brew install mysql-client
echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile
export PATH="/usr/local/opt/mysql-client/bin:$PATH"
pip install mysqlclient

2

运行时可能报下面的错误:

1
2
3
4
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/django/db/backends/mysql/base.py", line 36, in <module>
raise ImproperlyConfigured('mysqlclient 1.4.0 or newer is required; you have %s.' % Database.__version__)
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.4.0 or newer is required; you have 0.10.1.

解决方法是在__init__.py文件中添加下面三行:

1
2
3
import pymysql
pymysql.version_info = (1, 4, 13, "final", 0)
pymysql.install_as_MySQLdb()

常用MySQL操作及问题备忘

常见安装配置问题

安装

ubuntu安装命令:

1
sudo apt-get install mysql-server mysql-common

安装后使用/etc/mysql/debian.cnf中记录的用户(debian-sys-maint)和密码登陆

1
mysql -u debian-sys-maint -p

常见登陆问题

1. 不可以使用密码登陆的问题

1
2
3
4
5
6
7
8
--修改root用户属性
update mysql.user set authentication_string=PASSWORD('yourpassword'),plugin='mysql_native_password' where user='root';

-- (1)修改host允许远程登录
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'yourpassword' WITH GRANT OPTION;

-- (2)修改验证方式允许密码登录
update mysql.user set authentication_string=PASSWORD('yourpassword'),plugin='mysql_native_password' where user='root';

注意:!!!上面(1)和(2)都需要执行,而且需要按顺序执行

2. 连接报10060错误

使用服务商提供的服务器还需要在服务上控制台开通3306端口的入站规则(否则连接会报10060错误),比如腾讯云的在实例->安全组->入站规则里面,选择添加规则。

1
0.0.0.0/0    TCP:3306    允许

2. 连接报10061错误

解决方法:修改配置文件mysqld.cnf ,注释掉下面这行:
bind-address = 127.0.0.1

上面允许远程登陆、使用默认3306端口和解除绑定IP等配置是存在一定安全隐患和风险的,生产环境不建议这样使用。

常用SQL操作

1
2
3
4
5
6
7
8
9
10
-- 创建数据库
create database dbname default charset=utf8;

-- 创建用户并授权;
use mysql;
CREATE USER 'username'@'%' IDENTIFIED BY 'yourpassword';

-- 为用户添加权限
GRANT ALL ON pwnner.* TO 'pwnner'@'%';

表相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 建表
DROP TABLE IF EXISTS `yx_user`;
CREATE TABLE `yx_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`create_time` int(11) NOT NULL DEFAULT 0,
`update_time` int(11) NOT NULL DEFAULT 0,
`delete_time` int(11) NULL DEFAULT NULL,
`time` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- 修改表结构
ALTER TABLE `yx_user` ADD COLUMN `exam_cnt_s` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '10道' COMMENT '考试模式单选题数量';

-- 建索引
CREATE INDEX yx_user_index ON yx_user (`cid`,`username`);

Django笔记:2.视图和URL配置

视图一般都写在app的views.py中,视图的第一个参数永远都是request(一个HttpRrequest对象),这个对象存储了请求过来的所有信息。返回结果必须是HttpResponseBase对象或者子类的对象。

添加views视图

工程目录下新建一个views.py,添加如下内容

1
2
3
4
from django.http import HttpResponse

def hello(request):
return HttpResponse("Hello Django!")

urls.py文件中添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12

from django.contrib import admin
from django.urls import path
from django.conf.urls import url

from . import views

urlpatterns = [
path('admin/', admin.site.urls),
url('^$', views.hello),
url('hello/', views.hello),
]

浏览器访问localhost:8080或者localhost:8080/hello查看效果:

项目目录

1
2
3
4
5
6
7
8
9
$ tree
.
|-- HelloWorld # 项目容器
| |-- __init__.py # 一个空文件,告诉 Python 该目录是一个 Python 包
| |-- settings.py # 项目的设置/配置文件
| |-- urls.py # url 配置
| |-- views.py # 视图文件
| |-- wsgi.py # 一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
`-- manage.py # 一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互

添加templates模版

在templates目录下新建一个about.html,添加如下内容:

1
{{ about }}

在views.py中添加如下代码:

1
2
3
4
5
6
from django.shortcuts import render

def about(request):
context = {}
context['about'] = 'Hello Django in the about page!'
return render(request, 'about.html', context)

urls.py中添加映射

1
2
3
4
urlpatterns = [
...
path('about/', views.about),
]

访问localhost:8000/about,可以看到模版中的内容被view的内容替换了。

如何在URL中添加参数

传统的Http的GET和POST方式了。

1
2
3
4
5
6
7
8
# views.py
def paramtest1(request):
text = "您输入的URL - id是:{}".format(request.GET.get('id'))
return HttpResponse(text)

#urls.py
path('paramtest1', views.paramtest1),

另外一种传递参数的方式为采用在url中使用变量的方式,使用<参数名>的方式可以传递参数,参数名称必须和视图中保持一致,可以传递多个参数。

1
2
3
4
5
6
7
# views.py
def paramtest(request,req_id='123'):
text = "您输入的id是:%s" % req_id
return HttpResponse(text)

# urls.py
path('paramtest/<req_id>', views.paramtest),

可以使用<type:param>的方式来指定参数的类型,比如<str:param><int:param>或者<uuid:param>等。支持的类型转换器如下:

  • str : 除了斜杠’/‘以外的所有的字符都是可以的;
  • int :一个或者多个阿拉伯数字;
  • path :所有的字符都可以;
  • uuid :只有满足uuid.uuid4()这个函数返回的字符串的格式;
  • slug :英文中的横杠或者英文字符或者阿拉伯数字或者下划线。

另一个例子:

1
2
3
4
5
urlpatterns = [
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail),
]

URL模块化

当项目中存在多个应用时,URL映射的管理如果都放在主urls.py文件中的话会比较杂乱不好管理。可以采用模块化的方式分散到各个app自己的urls.py中来管理。方法如下:

主urls.py中添加include使app01相关的url映射都转到app01应用下的urls.py中。应该使用include函数包含子urls.py,并且这个urls.py的路径是相对于项目的路径。

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

urlpatterns = [
path('app01/', include('app01.urls')),
]

然后在app01下的urls.py文件中,所有url匹配也要放在一个urlpatterns中,添加如下内容就可以了

1
2
3
4
5
6
7
8
9
from django.urls import path

from . import views

urlpatterns = [
path('', views.book),
path('list/', views.book_list),
path('add_book/', views.add_book),
]

url是会根据主urls.py和app中的urls.py进行拼接,因此注意不要多加斜杠。

URL命名

为什需要URL命名?

因为url是经常变化的,如果代码中写死,当url需要变更是会比较麻烦,可能会需要经常批量修改。给url取个名字,以后使用url的时候就使用它的名字进行反转,这样当url变更时不需要大量修改代码中写死的url内容。实现方式是在path函数中传递一个name参数就可以了。

1
2
3
4
5
6
7
8
9
10
11
# urls.py
urlpatterns = [
path('login/', views.login, name='login'),
]

# 使用的地方,比如原来使用
from django.shortcuts import redirect
redirect('/login/')
# 修改为
from django.shortcuts import redirect, reverse
redirect(reverse('login'))

使用应用命名空间,避免不同app之间url命名重复的问题

定义应用命名空间非常简单,只要在app的urls.py中定义一个叫做app_name的变量,来指定这个应用的命名空间,这样在做反转的时候就可以使用 应用命名空间:URL名称的方式进行反转。

1
2
3
4
5
6
7
8
9
10
# urls.py
app_name = 'front' # 定义app的命名空间为front

urlpatterns = [
path('login/', views.login, name='login'),
]

# 使用的地方
from django.shortcuts import redirect, reverse
redirect(reverse('front:login'))

使用实例命名空间

一个app可以创建多个实例,可以使用多个url映射同一个app,同一个app下有两个实例的情况,在做反转的时候,如果使用应用命名空间,就会发生混淆,比如:

1
2
3
path('cms/', include('cms.urls')),
path('cms1/', include('cms.urls')),
path('cms2/', include('cms.urls')),

如果访问:http://localhost:8000/cms1,会重定向到http://localhost:8000/cms/login 而不是http://localhost:8000/cms1/login

可以使用实例命名空间来解决这个问题,只需要在include函数中传递一个namespace变量即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# urls.py
path('cms/', include('cms.urls', namespace='cms')),
path('cms1/', include('cms.urls', namespace='cms1')),
path('cms2/', include('cms.urls', namespace='cms2')),

# views.py
from django.shortcuts import render, redirect, reverse
def index(request):
username = request.GET.get('username')
if username:
return HttpResponse("后台主页")
else:
# 取得实例命名空间
current_namespace = request.resolver_match.namespace
# 在做反转的时候,就可以根据实例命名空间来指定具体的url
login_url = reverse('{}:login'.format(current_namespace))
return redirect(login_url)

inlcude函数的用法

  1. include(module, namespace=None)
    • module : 子url的模块字符串
    • namespace : 实例命名空间,这个地方需要注意一点,如果指定实例命名空间,那么前提必须要先指定应用命名空间,也就是在子urls.py中添加app_name变量。
  2. include((pattern_list), app_namespace), namespace=None)
  3. include(pattern_list)

re_path 的用法

re_path的作用和path一样,不同的是re_path可以使用正则表达式,功能更强大。推荐使用原生字符串(字符串前面加r)。正则表达式中定义变量,需要使用圆括号括起来,如果参数有名字,需要使用(?P<参数名称>)的格式。

1
2
3
4
5
6
7
8
9
10
11
12
# urls.py
urlpatterns = [
re_path(r'^$', views.article),
re_path(r"^list/(?P<year>\d{4})/$", views.article_list),
]

# views.py
def article(request):
return HttpResponse("文章主页")

def article_list(request, year):
return HttpResponse("{} 年文章列表".format(year))

reverse 传递参数

试想实现一个功能,满足条件时redirect到一个需要传递参数的页面。
比如下面的功能:当输入http://localhost:8080/article/index时,若参数username为空则自动跳转到 http://localhost:8080/article/detail/1,而detail的URL是需要一个article_id作为参数的,这个时候就可以使用reverse函数的第二个参数kwargs来传递参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# urls.py
path('detail/<article_id>', views.article_detail, name='detail'),
path('login/', views.login, name='login'),
path('index/', views.index, name='index'),


# views.py
def index(request):
username = request.GET.get('username')
if username:
return HttpResponse("文章首页")
else:
# kwarg : keyword arguments.
detail_url = reverse('detail', kwargs={"article_id": 1}) # /detail/1
return redirect(detail_url)

Django笔记:1. 开发环境及常用命令

1.Pycharm搭建开发环境

pycharm 中新建工程,选择Existing interpreter。

如果没有Django环境,Pycharm会自动下载安装:

设置一下项目的interpreter,如果设置过了可省略

运行/调试配置,勾选run browser选项:

点击Pycharm右上角的运行按钮,或者使用快捷键Control + R 运行项目,正常就可以看到下面的厨师画面了:

如果出现了下面的错误,在settings.py文件中加上import os就可以了

2. 常用命令

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
# 新建项目
django-admin startproject [项目名称]
django-admin startproject djproject

# 新建应用
django-admin.py startapp [应用名称]
# 或者
python manage.py startapp [应用名称]

# 运行项目
python manage.py runserver localhost:8000
# 这里如果需要使项目在本机意外可以访问的话,需要把localhost修改为0.0.0.0
# 同时在settings.py中设置ALLOWED_HOSTS添加本机IP。


# 让 Django 知道我们的模型有一些变更,自动生成迁移脚本:
# 该 makemigrations 命令查找所有可用的models,为任意一个在数据库中不存在对应数据表的model创建 migrations 脚本文件。
# migrate 命令则运行这些 migrations 自动创建数据库表。
python manage.py makemigrations TestModel
python manage.py makemigrations
常用选项:
app_label : 后面可以跟一个或多个app,那么就只会针对这几个app生成迁移脚本。
-name:指定迁移脚本的名字
-empty:生成一个空的迁移脚本。

# 执行迁移脚本,创建表结构
python manage.py migrate
# 或者后面添加app_label,只执行指定app的迁移脚本。
python manage.py migrate TestModel

python manage.mp migrate app_label migrationname # 将某个app下指定名字的migration文件映射到数据库中。

--fake : 可以讲指定的迁移脚本名字添加到数据库中。但是并不会真正的执行迁移脚本。
--fake-initial : 将第一次生成的迁移文件版本号记录在数据库中,但是并不会真正的执行迁移脚本。

# 使用已有数据库表生成ORM模型
python manage.py inspectdb > models.py

# 查看某个app下的迁移文件。
python manage.py showmigrations