Notice

3.6.8
2.2.10

如果你是一名初学者,强烈建议你将本章节的内容看完并且在自己的本地环境亲手实现下面的实例,这个过程对你熟悉Django项目帮助很大,而且也能帮助你更好的理解后续章节中的详细知识点。

另外,本文创建目录与文件都是使用的linux命令,对于使用windows的小伙伴来说可能不太友好——在windows系统中你可以点击右键新建目录或者文件即可。

虚拟环境可以跳过QuickStart中的前2个步骤

QuickStart

1、首先我们在本机创建一个专门运行Django的虚拟环境(本文使用的是virtualenv):

mkvirtualenv django_test

2、然后进入新创建好的虚拟环境:

workon django_test

3、然后在这个虚拟环境中安装指定版本的Django:

pip3 install django==2.2.10

4、安装好Django后新建一个Django项目:

django-admin startproject whwDjTest

如上所示:我们创建了一个名为whwDjTest的项目。

默认创建
~/Desktop/whwDjTest # 项目目录
├── manage.py # 启动与管理Django项目的模块
└── whwDjTest # 与项目同名的目录
    ├── __init__.py
    ├── settings.py # 项目的本地配置
    ├── urls.py     # 项目的主路由
    └── wsgi.py     # 基于WSGI的socket server
项目目录views.py
touch views.py

新的目录结构如下:

~/Desktop/whwDjTest 
├── manage.py 
├── views.py # 新的视图函数
└── whwDjTest 
    ├── __init__.py
    ├── settings.py 
    ├── urls.py     
    └── wsgi.py    

并且在views.py文件中加入下面代码:

# -*- coding:utf-8 -*-
from django.shortcuts import HttpResponse

def index(request):
    if request.method == "GET":
        return HttpResponse("Hello Django!")

7、在urls中新加一条路由,并且新的路由交给上面写好的views.py中的index方法去处理:

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

# 处理视图的模块
import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 新路由
    path('index/',views.index)
]

8、最后,在项目目录下启动Django程序:

python3 manage.py runserver 127.0.0.1:9001
http://127.0.0.1:9001/index/
Hello Django!

Django的请求生命周期

做web开发必须先搞明白前端浏览器发出请求后服务端的具体处理流程以及后端如何处理请求、返回响应。对于Django框架来说弄明白Django的请求生命周期是搞定一切的基础。

下图是Django的请求生命周期,也就是从用户发出请求后服务端返回响应的具体过程: diango生命周期

也就是说,当用户请求到达Django服务端后,请求经过了下面步骤的处理:

WSGIrequestrequest请求对象中间件request请求对象路由匹配路由分发视图函数视图函数ORM
概览
应用

Django中的应用(application)

模块化
userbook

1、为项目创建应用

manage.py
python3 manage.py startapp user
python3 manage.py startapp book

然后我们可以看到在项目中多了2个目录:user与book:

~/Desktop/whwDjTest 
├── book # 新创建的应用的目录
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── user # 新创建的应用的目录
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── views.py
└── whwDjTest
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
注册
settings.py
与项目同名的目录settings.pyINSTALLED_APPS

我们需要将自己刚刚创建好的应用注册到这个列表中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 自己的应用
    'book.apps.BookConfig',
    'user.apps.UserConfig',
]

这里需要注意了:建议大家使用上面的方法(book.apps.BookConfig'而不是只写'book')去注册应用!

Django的路由匹配与分发

一、路由匹配

与项目同名的目录下的urls.py
Quick Start
from django.contrib import admin
from django.urls import path,re_path

# 处理视图的模块
import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 新路由
    path('index/',views.index)
]
path('admin/', admin.site.urls),
index/处理这条路由的视图
url
re_path
re_path('^index/$',views.index),

关于path方法后面进阶的介绍会提到。

二、路由分发

当然聪明的你看到这里也许会发现一个问题:既然Django可以注册多个app,那么每个app必然会有许多路由,把这些路由都放在一块玩一搞混了怎么办呢?

Django使用路由分发来解决多应用路由可能造成的混乱问题。

urls.py文件应用专属的路由处理文件
cd user
touch urls.py
跟项目同名的urls.py文件中加入路由分发的逻辑
from django.contrib import admin
from django.urls import path,re_path,include

# 处理视图的模块
import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 新路由
    re_path('^index/$',views.index),
    # 路由分发
    re_path('^user/',include('user.urls'))
]
user/home
user应用下的urls.py文件中加入路由匹配的代码
from django.urls import re_path

# 自己处理视图的模块
from user import views

urlpatterns = [
    re_path('^home/$',views.home),
]

user的views.py文件中加上视图的处理逻辑:

from django.shortcuts import HttpResponse

def home(request):
    if request.method == "GET":
        return HttpResponse("user home!")
http://127.0.0.1:9001/user/home/
user home!

Django的视图系统简介

上面在介绍路由系统的时候,对于每一个路由都有一个函数(也可以使用类)去处理,这个函数(后面会介绍类)或者类就是Django的视图函数(或者叫视图类)。

请求对象request
CV工程师CURD搬运工
承上启下

视图系统之FBV与CBV简介

说白了,FBV就是视图中以函数的方式处理请求,CBV就是以面向对象的方式处理请求。

上面给大家介绍的是FBV的写法,下面简单介绍一下CBV的写法:

1、还拿第一个index路由举例,在views.py中加入CBV处理的逻辑:

# -*- coding:utf-8 -*-
from django.shortcuts import HttpResponse
from django.views import View


class Index(View):
    def get(self,request):
        return HttpResponse("Hello Django!")

2、这时路由的写法需要改成这种形式:

from django.contrib import admin
from django.urls import path,re_path,include

# 处理视图的模块
import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 新路由 —— CBV的写法
    re_path('^index/$',views.Index.as_view()),
]

然后像上面那样访问index页面就好了。

Django与MySQL的交互

与Flask等其他Python框架不同的是,Django的模型层与数据库交互用的是自己的ORM框架。

本文简单介绍一下Django与MySQL数据库交互的过程以及ORM的基本使用,后续章节会详细展开说明。

一、创建数据库

test
mysql -uroot -p

mysql> create database test;
Query OK, 1 row affected (0.01 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| xxx                |
| xxxx               |
| xxxxx              |
| xxxxxx             |
| xx123              |
| test               |
+--------------------+

二、创建模型Model

useruser/models.py
from django.db import models

# Create your models here.

class User(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=22)
    age = models.IntegerField()

    def __str__(self):
        return self.name

可以看到:我们创建了一个user模型,里面有3个属性:id(主键)、name、age。

三、在settings中配置数据库的连接

DATABASES
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',#引擎,选mysql
        'NAME':'test',#要连接的数据库,连接前需要创建好
        'USER':'root',#连接数据库的用户名
        'PASSWORD':'123',#连接数据库的密码
        'HOST':'127.0.0.1',#连接主机,默认本机
        'PORT':3306,#端口 默认3306
        #Django中设置数据库的严格模式
        'OPTIONS':{
            'init_command':"set sql_mode='STRICT_TRANS_TABLES' ",
            }
        }
    }

如果此时就启动项目会报错:

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?

提示我们没有安装mysql客户端,MySQLdb对于py3有很大问题,所以我们需要的驱动是pymysql。

__init__.py

由于新创建的虚拟环境中没有pymysql模块,我们需要先安装一下:

pip3 install pymysql

然后加入MySQLdb:

import pymysql

pymysql.install_as_MySQLdb()

五、如果使用Django2版本需要修改2处源码

(1)如果此时运行项目的话会报错:

django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.

这是Django2内部的一个版本限制的问题,我们需要修改一下源码。

找到当前这个虚拟环境的目录下的django对应的配置,具体的目录是:

/(你自己的家目录)/venvs/django_test/lib/python3.6/site-packages/django/db/backends/mysql/base.py
base.py
if version < (1, 3, 13):
    raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)
把这两行代码注释掉!

(2)再运行项目还会报错:

AttributeError: 'str' object has no attribute 'decode'

对应报错的文件在这里:

/(你自己的家目录)/venvs/django_test/lib/python3.6/site-packages/django/db/backends/mysql/operations.py

这个文件的146行:

query = query.decode(errors='replace')
改成encode:
query = query.encode(errors='replace')

这样就没问题了!

六、执行数据库迁移指令

在项目的跟目录下执行数据库的迁移指令:

python3 manage.py makemigrations
'''
Migrations for 'user':
  user/migrations/0001_initial.py
    - Create model User
'''

python3 manage.py migrate
'''
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, user
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
  Applying user.0001_initial... OK
'''

每次有上面这样的提示说明数据库迁移成功了!

七、查看数据库生成的表

进入数据库我们看看生成的表:

mysql -uroot -p


use test;
show tables;
'''
+----------------------------+
| Tables_in_test             |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| user_user                  |
+----------------------------+
'''
authdjangouser_useruser应用表的名字

八、查看user_user的表结构

mysql> desc user_user;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(22) | NO   |     | NULL    |                |
| age   | int(11)     | NO   |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

表结构是根据我们上面的Model类的语法创建的。具体Model的属性与MySQL表结构的对应关系后面详细讲解。

九、往数据库中写入一些数据

这一步略去,当然实际中的方法非常多,由于本文只是一个大纲,这里我使用insert语句往数据库中写入3条数据:

mysql> insert into user_user(name,age) values("wanghw",18),("naruto",28),("sasuke",38);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from user_user;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | wanghw |  18 |
|  2 | naruto |  28 |
|  3 | sasuke |  38 |
+----+--------+-----+
3 rows in set (0.00 sec)

Django2.1版本没有报错

Django2.1.15版本

我又重新创建了一个Django2.1.15版本的项目,步骤跟上面的一样。

1、先看一下虚拟环境中安装的包

dj21

2、进行跟上面相同的配置,不用改变源码,执行数据库迁移的操作并没有报错

dj21

dj21

Django2版本与MySQL交互报错原理探究 ***

结论就是:在Django中在进行MySQL连接相关的配置后,是否会报错取决于Django与pymysql的版本!

上面演示的Django2.1版本不会报错是因为pymysql版本是0.9.3,它支持Django2.1版本,但是对于高版本的Django2.2来说就不支持,所以我们只能通过修改Django源码的方式解决这个问题。

至于其中的原理请大家看我的博客,这里有最详细的源码分析:

实际中建议大家选择合适的Django版本进行开发。

ORM的简单使用

ORM(Object Relational Mapping)对象关系映射,简单而言其实就是让程序员使用面向对象的方式操作数据库中的数据,虽然有些极端的情况下性能上要低于执行原生SQL的效率,但是绝大多数的情况下都可以满足用户的需求(在ORM中也可以执行原生SQL,后面会介绍)。

Django与MySQL的交互
Django路由匹配与分发user/home/http://127.0.0.1:8001/user/home/
from django.shortcuts import HttpResponse

# 导入Model
from user import models

def home(request):
    if request.method == "GET":
        # 查询
        ret1 = models.User.objects.filter(name="wanghw")
        print(ret1,type(ret1)) #<QuerySet [<User: wanghw>]> <class 'django.db.models.query.QuerySet'>

        ret2 = models.User.objects.first()
        print(ret2,type(ret2)) # wanghw <class 'user.models.User'>

        return HttpResponse("user home!")
QuerySet对象model对象

当然我们也可以插入一条数据:

from django.shortcuts import HttpResponse

# 导入Model
from user import models

def home(request):
    if request.method == "GET":
        # 插入数据
        user_obj = models.User.objects.create(name="张三",age=66)
        print(user_obj)

        return HttpResponse("user home!")

在数据库中检验一下是否插入成功:

mysql> select * from user_user;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | wanghw |  18 |
|  2 | naruto |  28 |
|  3 | sasuke |  38 |
|  4 | 张三   |  66 |
+----+--------+-----+
4 rows in set (0.00 sec)

修改与删除数据也是可以的:

from django.shortcuts import HttpResponse

# 导入Model
from user import models

def home(request):
    if request.method == "GET":
        # 修改数据
        models.User.objects.filter(name="张三").update(age=100)

        # 删除数据
        models.User.objects.filter(name="sasuke").delete()

        return HttpResponse("user home!")

再看一下MySQL中的结果:

mysql> select * from user_user;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | wanghw |  18 |
|  2 | naruto |  28 |
|  4 | 张三   | 100 |
+----+--------+-----+
3 rows in set (0.00 sec)

Django的模板系统

对于前后端不分离的项目来说,Django的模板系统是与前端交互的关键,这里先简单介绍一下Django模板的使用。

templates
mkdir templates
index.html
cd templates
touch index.html

index.html中的内容如下:

settings中的配置

settings.pyTEMPLATES'DIRS'
'DIRS': [os.path.join(BASE_DIR, "templates")],
http://127.0.0.1:9001/index/

对应的views.py中加入下面的代码:

from diango.shortcuts import render 
    #这个函数必须要带一个形参request
    def index(request):
        import datetime
        now = datetime.datetime.now()
        ctime = now.strftime('%Y-%m-%d %X')
        #注意这里必须得return 而且第一个参数必须是request
        return render(request,'index.html',{'ctime':ctime})

最后访问index这个路由就可以看到当前页面返回的信息了。

{{ ctime }}

Django中静态文件的配置

前后端不分离的项目部署之前在本地调试的时候,需要配置一下静态文件(css、js等文件)。

创建静态文件目录及静态文件

staticfiles
mkdir staticfiles

然后进入这个目录,在里面加上一个css文件:

cd staticfiles
touch my_style.css

再在这个css文件中加入一个类与样式:

vim my_style.css

# 加入样式
.class1{color:red}

settings中的配置

settings.py
STATICFILES_DIRS = [
            os.path.join(BASE_DIR,'staticfiles')
            ]

模板中引入静态文件

最后访问index页面,可以发现所有文字都变成了红色——说明静态文件引入成功!