MVC的概述

    Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解译。所谓“模型”说得更直白一些就是数据(的表示)在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,而其中Mysql作为一个免费的数据库为个人开发者和小型公司所青睐。

术语:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写。

用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

配置SQL数据库

我们首先进入我们的项目note1的文件夹,找到settings.py,找到以下代码:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

我们可以看出,默认的数据库引擎是sqlite3,django使用 SQLite 作为默认数据库。如果你不熟悉数据库,或者只是想尝试下 Django,这是最简单的选择。Python 内置 SQLite,所以你无需安装额外东西来使用它。当你开始一个真正的项目时,你可能更倾向使用一个更具扩展性的数据库,例如 PostgreSQL,避免中途切换数据库这个令人头疼的问题。

如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES 'default' 项目中的一些键值:

  • ENGINE -- 可选值有 'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql',或 'django.db.backends.oracle'。其它 可用后端。
  • NAME - 数据库的名称。如果使用的是 SQLite,数据库将是你电脑上的一个文件,在这种情况下, NAME 应该是此文件的绝对路径,包括文件名。默认值 os.path.join(BASE_DIR, 'db.sqlite3') 将会把数据库文件储存在项目的根目录。

如果你不使用 SQLite,则必须添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES 。

SQLite 以外的其它数据库

如果你使用了 SQLite 以外的数据库,请确认在使用前已经创建了数据库。你可以通过在你的数据库交互式命令行中使用 "CREATE DATABASE database_name;" 命令来完成这件事。

另外,还要确保该数据库用户中提供 note1/settings.py 具有 "create database" 权限。这使得自动创建的 test database能被以后的教程使用。

如果你使用 SQLite,那么你不需要在使用前做任何事——数据库会在需要的时候自动创建。

修改后代码:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'note1',#要使用的数据库的名称。对于SQLite,它是数据库文件的完整路径。
        'HOST': '127.0.0.1',#连接到数据库时要使用的主机。mysql默认端口是127.0.0.1
        'PORT': 3306,#连接到数据库时要使用的端口。空字符串表示默认端口。不适用于SQLite。mysql默认端口是3306
        'USER': '数据库用户',
        'PASSWORD': '数据库密码',
    }
}

安装Python操作MySQL的依赖库,Python 3中通常使用PyMySQL,Python 2中通常用MySQLdb。

(venv)$ pip install pymysql

如果使用Python 3需要修改项目目录下的__init__.py文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。

import pymysql

pymysql.install_as_MySQLdb()

然后我们来创建个我们note1项目需要的数据库、账号及密码!

首先我们怎么cmd连接我们的数据库呢?

mysql -u root -p

这行代码的意思我们想要用root用户来连接登录我们的数据库,按下回车键之后就会让我们输入密码,输入完再回车就行了。

我们通过

create database note1 default charset utf8;

这个命令来创建一个note1数据库,然后使用 show databases; 来显示当前的数据库,我们可以看到,note1成功创建了并列了出来。

然后我们可以开始设计了!

我们的项目是一个论坛系统,整个项目的构思是维护几个论坛版块(boards),每个版块像一个分类一样。在指定的版块里面,用户可以通过创建新主题(Topic)开始讨论,其他用户可以参与讨论回复。

我们需要找到一种方法来区分普通用户和管理员用户,因为只有管理员可以创建版块。下图概述了主要的用例和每种类型的用户角色:

  • Board:版块

  • Topic:主题

  • Post:帖子(译注:其实就是主题的回复或评论)

在一个主题(Topic)中,我们需要有一个字段(译注:其实就是通过外键来关联)来确定它属于哪个版块(Board)。同样,帖子(Post)也需要一个字段来表示它属于哪个主题,这样我们就可以列出在特定主题内创建的帖子。最后,我们需要一个字段来表示主题是谁发起的,帖子是谁发的。

用户和版块之间也有联系,谁创建的版块。但是这些信息与应用程序无关。还有其他方法可以跟踪这些信息,稍后您会看到。

现在我们的类图有基本的表现形式,我们还要考虑这些模型将承载哪些信息。这很容易让事情变得复杂,所以试着先把重要的内容列出来,这些内容是我们启动项目需要的信息。后面我们再使用 Django 的迁移(Migrations)功能来改进模型,您将在下一节中详细了解这些内容。

但就目前而言,这是模型最基本的内容:

对于 Board 模型,我们将从两个字段开始:name 和 description。 name字段必须是唯一的,为了避免有重复的名称。description 用于说明这个版块是做什么用的。

Topic 模型包括四个字段:subject 表示主题内容,last_update 用来定义话题的排序,starter 用来识别谁发起的话题,board 用于指定它属于哪个版块。

Post 模型有一个 message 字段,用于存储回复的内容,created_at 在排序时候用(最先发表的帖子排最前面),updated_at 告诉用户是否更新了内容,同时,还需要有对应的 User 模型的引用,Post 由谁创建的和谁更新的。

最后是 User 模型。在类图中,我只提到了字段 username,password,email, is_superuser 标志,因为这几乎是我们现在要使用的所有东西。

需要注意的是,我们不需要创建 User 模型,因为Django已经在contrib包中内置了User模型,我们将直接拿来用。

关于类图之间的对应关系(数字 1,0..* 等等),这里教你如何阅读:

一个topic 必须与一个(1)Board(这意味着它不能为空)相关联,但是 Board 下面可能与许多个或者0个 topic 关联 (0..*)。这意味着 Board 下面可能没有主题。(译注:一对多关系)

一个 Topic 至少有一个 Post(发起话题时,同时会发布一个帖子),并且它也可能有许多 Post(1..*)。一个Post 必须与一个并且只有一个Topic(1)相关联。

一个 Topic 必须有一个且只有一个 User 相关联,topic 的发起者是(1)。而一个用户可能有很多或者没有 topic(0..*)。

Post 必须有一个并且只有一个与之关联的用户,用户可以有许多或没有 Post(0..*)。Post 和 User之间的第二个关联是直接关联(参见该行最后的箭头),就是 Post 可以被用户修改(updated_by),updated_by 有可能是空(Post 没有被修改)

画这个类图的另一种方法是强调字段而不是模型之间的关系:

首先,我们需要在主页上显示所有版块:

论坛项目线框主页列出所有可用的版块。

如果用户点击一个链接,比如点击Django版块,它应该列出所有Django相关的主题:

论坛项目线框图列出了Django版块中的所有主题

这里有两个入口:用户点击“new topic“ 按钮创建新主题,或者点击主题链接查看或参与讨论。

“new topic” 页面:

现在,主题页面显示了帖子和讨论:

如果用户点击回复按钮,将看到下面这个页面,并以倒序的方式(最新的在第一个)显示帖子列表:

数据库迁移

Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示:python manage.py migrate

PS:数据库迁移报错的解决办法:django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.

这个 migrate 命令检查 INSTALLED_APPS 设置,为其中的每个应用创建需要的数据表,至于具体会创建什么,这取决于你的 boards/settings.py 设置文件和每个应用的数据库迁移文件(我们稍后会介绍这个)。这个命令所执行的每个迁移操作都会在终端中显示出来。如果你感兴趣的话,运行你数据库的命令行工具,并输入 \dt (PostgreSQL), SHOW TABLES; (MySQL), .schema (SQLite)或者 SELECT TABLE_NAME FROM USER_TABLES; (Oracle) 来看看 Django 到底创建了哪些表。

use note1;

选择中note1数据库,

show tables;

显示note1数据库中的数据表有哪些。

通过python manage.py makemigrations这个命令,Django 在 boards/migrations 目录创建了一个名为 0001_initial.py的文件。它代表了应用程序模型的当前状态。在下一步,Django将使用该文件创建表和列。

迁移文件将被翻译成SQL语句。如果您熟悉SQL,则可以运行以下命令来检验将是要被数据库执行的SQL指令

Model模型代码实现

from django.db import models
from django.contrib.auth.models import User

class Board(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)

class Topic(models.Model):
    subject = models.CharField(max_length=255)
    last_updated = models.DateTimeField(auto_now_add=True)
    board = models.ForeignKey(Board, related_name='topics')
    starter = models.ForeignKey(User, related_name='topics')

class Post(models.Model):
    message = models.TextField(max_length=4000)
    topic = models.ForeignKey(Topic, related_name='posts')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(null=True)
    created_by = models.ForeignKey(User, related_name='posts')
    updated_by = models.ForeignKey(User, null=True, related_name='+')

所有模型都是django.db.models.Model类的子类。每个类将被转换为数据库表。每个分区由django.db.models.Field子类(内置在Django core中)的实例表示,它们称为被转换为数据库的列。

前端CharField,DateTimeField等,都是django.db.models.Field的子类,包含在Django的核心里面-随时可以使用。

在这里,我们仅使用CharField,TextField,DateTimeField,和ForeignKey插入来定义我们的模型。在需要的时候提及它们。

我们应该始终设置一个max_length。这些信息将用于创建数据库列。Django需要知道数据库列需要多大。该max_length参数也将被Django Forms API用于验证用户输入。

Board模型定义中,更简化,在name分区中,我们设置了参数unique=True---独特性=真,确保唯一性,不同名。

Post模型中,created_at变量有一个可选参数,auto_now_add设置为True。这将告诉Django创建Post对象时为当前日期和时间。

模型之间的关系使用ForeignKey【外键】。并且在模型之间创建一个连接,并在数据库级别创建适当的关系(译注:外键关联)。该ForeignKey变量需要一个位置参数related_name,用于引用它关联的模型。译注:例如created_by是外键分区,关联的用户模型,表明这个帖子是谁创建的,related_name = posts表示在User那边可以使用user.posts来查看这个用户创建了一些帖子)

例如,在Topic模型中,board字段的英文Board模型的ForeignKey。它告诉Django的,一个Topic实例只涉及一个局实例。related_name参数将用于创建反向关系,Board实例通过属性topics访问属于这个版块下的Topic列表。

Django的自动创建这种反向关系,related_name是可选项,但是,如果我们不为它设置一个名称,则Django会自动生成它。(class_name)_set。例如,在Board模型中,所有Topic列表将用topic_set。属性表示而这里我们将其重新命名为了topics,以使其感觉更自然。

Post模型中,该updated_by分支设置related_name='+'。这指示Django我们不需要这种反向关系,所以它会被忽略(译注:导致我们不需要关心用户修改过某些帖子)。

如果我们没有为模型指定主键,Django会自动为我们生成它。那么现在一切正常。

迁移模型

首先得把 boards 应用安装到我们的项目里。

设计哲学

Django 应用是“可插拔”的。你可以在多个项目中使用同一个应用。除此之外,你还可以发布自己的应用,因为它们并不会被绑定到当前安装的 Django 上。

为了在我们的工程中包含这个应用,我们需要在配置类 INSTALLED_APPS 中添加设置。因为 BoardsConfig类写在文件 boards/apps.py中,所以它的点式路径是 'boards .apps.BoardsConfig'。在文件 note1/settings.py 中 INSTALLED_APPS 子项添加点式路径后,它看起来像这样:

INSTALLED_APPS = [
    'boards.apps.BoardsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

现在你的 Django 项目会包含 boards  应用。接着运行下面的命令:

python manage.py makemigrations boards

但是由于新老版本问题,你又会碰见:Django报错:__init__() missing 1 required positional argument: 'on_delete'

原因:在django2.0后,定义外键和一对一关系的时候需要加on_delete选项,此参数为了避免两个表里的数据不一致问题,不然会报错:
TypeError: __init__() missing 1 required positional argument: 'on_delete'
举例说明:
user=models.OneToOneField(User)
owner=models.ForeignKey(UserProfile)
需要改成:
user=models.OneToOneField(User,on_delete=models.CASCADE) --在老版本这个参数(models.CASCADE)是默认值
owner=models.ForeignKey(UserProfile,on_delete=models.CASCADE) --在老版本这个参数(models.CASCADE)是默认值
参数说明:
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值
CASCADE:此值设置,是级联删除。
PROTECT:此值设置,是会报完整性错误。
SET_NULL:此值设置,会把外键设置为null,前提是允许为null。
SET_DEFAULT:此值设置,会把设置为外键的默认值。
SET():此值设置,会调用外面的值,可以是一个函数。
一般情况下使用CASCADE就可以了。

你将会看到类似于下面这样的输出:

此时,Django 在 boards/migrations 目录创建了一个名为 0001_initial.py的文件。它代表了应用程序模型的当前状态。在下一步,Django将使用该文件创建表和列。【也就是说:执行迁移操作,先通过模型生成迁移文件,再执行迁移创建二维表,这就意味着:在上面的  数据库迁移 操作里根本没必要迁移[尴尬],可以先通过本命令创建迁移文件,然后再来迁移到数据表!

迁移文件将被翻译成SQL语句。如果您熟悉SQL,则可以运行以下命令来检验将是要被数据库执行的SQL指令

python manage.py sqlmigrate boards 0001

如果你不熟悉SQL,也不要担心。在本系列教程中,我们不会直接使用SQL。所有的工作都将使用Django ORM来完成,它是一个与数据库进行通信的抽象层。

下一步是将我们生成的迁移文件应用到数据库:

python manage.py migrate

输出内容本来应该是这样的:

Operations to perform:
  Apply all migrations: admin, auth, boards, contenttypes, sessions
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 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 boards.0001_initial... OK
  Applying sessions.0001_initial... OK

因为这是我们第一次迁移数据库,所以migrate命令把Django contrib app 中现有的迁移文件也执行了,这些内置app列在了INSTALLED_APPS。这是预料之中的。

Applying boards.0001_initial... OK是我们在上一步中生成的迁移脚本。

但是,因为我是我们之前瞎迁移了一次,本次的结果将会是这样:

好了!我们的数据库已经可以使用了。

需要注意的是SQLite是一个产品级数据库。SQLite被许多公司用于成千上万的产品,如所有Android和iOS设备,主流的Web浏览器,Windows 10,MacOS等。

但这不适合所有情况。SQLite不能与MySQL,PostgreSQL或Oracle等数据库进行比较。大容量的网站,密集型写入的应用程序,大的数据集,高并发性的应用使用SQLite最终都会导致问题。

我们将在开发项目期间使用SQLite,因为它很方便,不需要安装其他任何东西。当我们将项目部署到生产环境时,再将切换到PostgreSQL(译注:后续,我们后面可能使用MySQL)。对于简单的网站这种做法没什么问题。但对于复杂的网站,建议在开发和生产中使用相同的数据库。

manage.py相关命令

python manage.py makemigrations <app_name> 
  在对应app下的migrations目录下,生成XXXX_initial.py文件,该XXXX_initial.py文件中的脚本是根据最新修改后的模型生成的数据库脚本

python manage.py migrate <app_name> 
  将XXXX_initial.py文件里的脚本在数据库中执行

python manage.py sqlmigrate polls XXXX 
  该命令用于查看迁移命令会执行哪些 SQL 语句,执行后这些sql会输出到屏幕上
  XXXX表示迁移的序号,如0001_initial.py,则命令写为python manage.py sqlmigrate polls 0001

python manage.py check 
  检查迁移脚本,并且在检查过程中不会对数据库进行任何操作

python manage.py shell 
  进入django的python交互界面

python manage.py createsuperuser 
  创建一个管理员账号

试验 Models API

使用Python进行开发的一个重要优点是交互式shell。我一直在使用它。这是一种快速尝试和试验API的方法。

您可以使用manage.py 工具加载我们的项目来启动 Python shell :

python manage.py shell
Python 3.6.2 (default, Jul 17 2017, 16:44:45)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

这与直接输入python指令来调用交互式控制台是非常相似的,除此之外,项目将被添加到sys.path并加载Django。这意味着我们可以在项目中导入我们的模型和其他资源并使用它。

让我们从导入Board类开始:

from boards.models import Board

要创建新的 boarrd 对象,我们可以执行以下操作:

board = Board(name='Django', description='This is a board about Django.')

为了将这个对象保存在数据库中,我们必须调用save方法:

board.save()

save方法用于创建和更新对象。这里Django创建了一个新对象,因为这时Board 实例没有id。第一次保存后,Django会自动设置ID:

board.id
1

您可以将其余的字段当做Python属性访问:

board.name
'Django'
board.description
'This is a board about Django.'

要更新一个值,我们可以这样做:

board.description = 'Django discussion board.'
board.save()

每个Django模型都带有一个特殊的属性; 我们称之为模型管理器(Model Manager)。你可以通过属性objects 来访问这个管理器,它主要用于数据库操作。例如,我们可以使用它来直接创建一个新的Board对象:

board = Board.objects.create(name='Python', description='General discussion about Python.')
board.id
2
board.name
'Python'

所以,现在我们有两个版块了。我们可以使用objects列出数据库中所有现有的版块:

Board.objects.all()
<QuerySet [<Board: Board object>, <Board: Board object>]>

结果是一个QuerySet。稍后我们会进一步了解。基本上,它是从数据库中查询的对象列表。我们看到有两个对象,但显示的名称是 Board object。这是因为我们尚未实现 Board 的__str__ 方法。

__str__方法是对象的字符串表示形式。我们可以使用版块的名称来表示它。

首先,退出交互式控制台:

exit()

现在编辑boards app 中的 models.py 文件:

class Board(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)

    def __str__(self):
        return self.name

让我们重新查询,再次打开交互式控制台:

 python manage.py shell
from boards.models import Board

Board.objects.all()
<QuerySet [<Board: Django>, <Board: Python>]>

好多了,对吧?

我们可以将这个QuerySet看作一个列表。假设我们想遍历它并打印每个版块的描述:

boards_list = Board.objects.all()
for board in boards_list:
    print(board.description)

结果是:

Django discussion board.
General discussion about Python.

同样,我们可以使用模型的 管理器(Manager) 来查询数据库并返回单个对象。为此,我们要使用 get 方法:

django_board = Board.objects.get(id=1)

django_board.name
'Django'

但我们必须小心这种操作。如果我们试图查找一个不存在的对象,例如,查找id=3的版块,它会引发一个异常:

board = Board.objects.get(id=3)

boards.models.DoesNotExist: Board matching query does not exist.

get方法的参数可以是模型的任何字段,但最好使用可唯一标识对象的字段来查询。否则,查询可能会返回多个对象,这也会导致异常。

Board.objects.get(name='Django')
<Board: Django>

请注意,查询区分大小写,小写“django”不匹配:

Board.objects.get(name='django')
boards.models.DoesNotExist: Board matching query does not exist.

总结

下面是我们在本节中关于模型学到的方法和操作,使用Board模型作为参考。大写的 Board指的是类,小写的boardBoard的一个实例(或对象)

操作代码示例
创建一个对象而不保存board = Board()
保存一个对象(创建或更新)board.save()
数据库中创建并保存一个对象Board.objects.create(name=’…’, description=’…’)
列出所有对象Board.objects.all()
通过字段标识获取单个对象Board.objects.get(id=1)

在下一节中,我们将开始编写视图并在HTML页面中显示我们的版块。


致谢,以上内容参考粘贴了下面列表的文章:

1.Python之禅——一个完整的Django入门指南

2.django官方文档

3.MySQL 选择数据库 | 菜鸟教程

4.Python-100-Days/Day41-55 at master · jackfrued/Python-100-Days

更多推荐

【菜鸡入门:django从零搭建论坛系列-Windows】之第二天:数据库的配置与迁移