最近因为学校要用,需要写一个 web 服务器,但是我不想学 java 系列的 spring 框架,又有之前使用 python socket 框架的老底,于是打算学习一下 Python 下的重量级框架 Django。
我几乎是完全看着 Django 的官方教程学的,Django 官方教程实属良心,翻译得也非常得符合中文语言环境。
1.1 检查 Python 版本与 Django 版本
我在本地 Windows 10 (Windows Subsystem Linux Ubuntu 20.04 LTS)
安装了 Python 3.8.5
。腾讯云云服务器 Ubuntu 18.04 LTS
上,默认安装了 Python 2.7.17
和 Python 3.6.9
(这两个版本我决定不了)。最终决定在本地和云端均部署 Django 3.2.x
。
查询本地已经部署的 python
版本,命令行输入: python3 --version
。 Windows Subsystem Linux
的用户一定要注意, WSL
中的 Python
版本可能与 Windows
下的 Python
版本不同。
1.2 使用 python venv 建立并切换到虚拟环境
为什么需要安装虚拟环境:虚拟环境简介(英文)。虚拟环境简介一文比较完整地介绍了如何建立虚拟环境,以及如何切换到虚拟环境,下文中关于虚拟环境的内容都可以在其中找到。
第一步:切换到想要安置虚拟环境的目录
第二步:在命令行执行 python3 -m venv [环境名]
例如:执行 python3 -m venv tutorialEnv
后, python
会在在生成的 ./tutorialEnv
目录下部署一个相同版本的 python
虚拟环境。
如果当前 python
并没有安装 venv
包,执行上述指令时 python
解释器会报错,根据报错信息安装 venv
包即可。
第三步,切换到虚拟环境:
Windows 下: tutorial-env\Scripts\activate.bat
Linux 下: source tutorial-env/bin/activate
(bash / zsh)
在 Linux 下,如果你使用
csh
或者fish
作为自己的shell
,切换到虚拟环境需要使用命令如下:
csh
:source tutorial-env/bin/activate.csh
fish
:source tutorial-env/bin/activate.fish
1.3 在虚拟环境中部署 Django
为什么一定要在虚拟环境中部署 Django
如何判断你是否处于虚拟环境中:
[En]
How to judge whether you are in a virtual environment:
- 观察命令行中命令提示符的变化:
在虚拟环境外:目录$
在虚拟环境中:(虚拟环境名) 目录$
- 在
python
命令行中输入以下语句:
>>> import sys; print(sys.path)
观察到site-package
目录在虚拟环境目录内,则说明已经切换到了虚拟环境。
确保自己在虚拟环境中后,使用 pip
安装 Django
。(注:如果您的电脑上之前有 python2
与 python3
两个版本的 python
,在虚拟环境中直接输入 python
,执行的就是虚拟环境中的 python
解释器,不需要再区分 python
与 python3
两种命令。同理,不需要区分 pip
与 pip3
)
python -m pip install django==3.2.11
检查是否成功安装了 Django
: pip freeze
可以列出当前 python
环境下所有已经被安装的包,在其中寻找 Django
对应的行即可。观察到如下内容:
Django==3.2.11
说明已经成功安装了 Django 3.2.11
。
1.4 检查 Django 版本
在 python
命令行中检查 Django
的版本:
>>> import django
>>> print(django.get_verion())
3.2.11
在命令行中检查 Django
版本:
$ python -m django --version
2.1 创建 Django 项目
第一步:切换到安装了 Django
的虚拟环境(具体方法见:”1.2. 使用 python venv 建立并切换到虚拟环境”);
第二步: cd
到你想要存放项目文件的目录;
第三步:命令行执行 django-admin startproject [项目名称]
创建项目。
注:如果你是在虚拟环境中部署的 Django
,那么理论上 django-admin
所在的目录会被注册到虚拟环境的 $PATH
中,也就是你可以直接使用 django-admin
这一命令。如果你是在真实环境中部署的 Django,那么 可能需要手动将 django-admin 所在的目录 export
到 $PATH
中。因此不建议将 Django
部署到真实环境中。
2.2 Django 项目的文件结构
[项目名称]/
|- manage.py
|- [项目名称]/
|- __init__.py
|- settings.py
|- urls.py
|- asgi.py
|- wsgi.py
2.3 settings.py 中的一些内容
设置语言: LANGUAGE_CODE='zh-hans'
(设置后,Django 管理员界面将提供汉化)
设置时区: TIME_ZONE='Asia/Shanghai'
调试模式: DEBUG=True
(Django 默认采用调试模式,在工业环境中,要置 DEBUG=False
)
注册 Django 应用: INSTALLED_APPS
中列出了所有当前启用的 Django 应用,将来我们自己实现应用时,也须要将应用的 注册类的名字放到这个 list
中(详见后文)。
注意:如果您正在配置云服务器上的 Django 项目,而且希望能够通过域名的方式访问您的服务器。您需要修改 settings.py
中的 ALLOWED_HOSTS=[]
,并在中括号中填入所有可用的域名。
2.3′ 配置 SMTP: 以 QQ 邮箱的 POP3/SMTP 为例
第一步:登录 mail.qq.com,开启 POP3/SMTP
功能(可能需要使用密保手机短信验证)。”设置” => “账户” => “POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”
第二步:在 settings.py
中添加以下内容:
EMAIL_HOST = "smtp.qq.com"
EMAIL_HOST_PASSWORD = "[QQ提供的邮箱授权码]"
EMAIL_HOST_USER = "[QQ账号 或 邮箱名]"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
一般来说,只有配置了以上内容才能通过 Django
发送邮件。据说 Django
的 SMTP
功能本质上是对 smtplib
包的封装。
2.4 创建超级管理员
Django 提供了非常方便的后台管理界面,使用 Django 提供的接口,我们可以使用后台管理界面可视化地修改数据库中存储的内容。后台管理界面需要使用账号密码登录,因此需要 先使用命令行创建一个超级管理员。
第零步:确定自己在部署了 Django
的 python
虚拟环境中;
第一步: cd
到 manage.py
所在的目录;
第二步:执行 python manage.py migrate
进行初次数据库迁移;
第三步:执行 python manage.py createsuperuser
,接下来按照提示输入即可。
2.5 Django 服务器的启动与停止
第零步:确定自己在部署了 Django
的 python
虚拟环境中;
如果你是在本地对 Django 项目进行测试:
第一步: cd
到 manage.py
所在的目录;
第二步:执行 python manage.py runserver [端口号]
,如在 Linux 系统下使用了系统敏感的端口号,请在指令前加 sudo
,不写端口号默认端口为 8000
;
停止 Django
服务器: Ctrl+C
结束正在运行的程序即可(由于 Django 会检测代码的变化从而动态重新加载,通常情况不需要停止服务器,只有当新增了文件/目录时才需要重新启动正在运行的服务器)。
如果你是在服务器上启动 Django 项目(以下方法适用于 Ubuntu 18.04
):
第一步:同上。
第二步:执行 nohup python manage.py runserver inner_ip:[端口号] &
这里的 inner_ip
是服务器在内网中的 IP。 nohup
语句用于忽略命令行的输入并将程序的输出重定向到文件 nohup.out
中。
第三步: Ctrl+Z
将程序挂起,再使用 bg %1
将刚刚挂起的程序放于后台继续执行。
上述引导方法在不同的操作系统上不一定可用。
[En]
The above boot methods are not necessarily available on different operating systems.
2.6 登录 Django 管理员界面
第一步:使用 2.5 中给出的方法启动 Django 服务器;
第二步:浏览器访问 http://127.0.0.1:[端口号]/admin/
(本地测试)或 http://ip:[端口号]/admin/
访问管理员界面(ip 为服务器的公网 IP)。
现在管理员界面几乎什么都没有,只是一个用户管理功能,稍后我们将使用管理员界面来操作数据库中的数据。
[En]
Now the administrator interface is almost nothing, only a user management function, later we will use the administrator interface to manipulate the data in the database.
3.1 Django 应用的创建
Django 的应用是一个 python 包,这意味着,你可以方便地将一个 Django 应用移植到其他项目中。尽管如此,在创建一个 Django 应用时,你还是需要一个已有的 Django 项目中的 manage.py
。
第一步: cd
到 manage.py
所在的目录;
第二步:执行 python manage.py startapp [应用名称]
。
第三步:在 [项目名]/settings.py
中的 INSTALLED_APP
列表中注册这个应用。即添加一行内容:
INSTALLED_APP = [
'[应用名A].apps.[应用名B]Config',
...
]
这里的 应用名A
就是你的应用的名字, 应用名B
是采用 大驼峰命名法变换后得到的名字。在创建应用时, [应用名]/apps.py
下回自动生成一个 Config
类,具体的名字详见 apps.py
。
3.2 Django 应用的文件结构
[应用名称]/
|- __init__.py
|- admin.py
|- apps.py
|- migrations/
| |- ...
|- model.py
|- test.py
|- view.py
|- urls.py
|- templates/
| |- [应用名称]/
| |- ...
|- static/
|- [应用名称]/
|- ...
为什么需要在 templates 目录以及 static 目录下创建一个 名字与 应用名称相同的子目录:
由于 Django 提供的文件查找功能是基于 路径后缀匹配的,这样设计方便使用 [应用名称]/[文件名]
获取 模板或静态文件 等资源文件。当然,直接把所有资源文件都直接放在应用目录下也不是不行,但是管理起来起来比较混乱。
3.3 Django 应用的 MVT 架构
MVT
架构与 MVC
架构在思想上有异曲同工之妙, MVT
指 Model/View/Template
结构,以下内容是对 MVT
架构的一个感性的认识(因为我写的不好,所以第一次看很可能不知所云…)。
应用的 Model
是指 “数据模型”,换言之,数据库中的表格。 models.py
中的每个类( models.Model
的子类)对应着数据库中的一个表。
应用的 View
是指 “视图(回调)函数/类”,是从 HttpRequest
到 HttpResponse
的映射,是 python
的函数或者类。Django 框架会根据 urls.py
中指定的 “路由方法”,根据 HttpRequest
的 URL 为其找到指定的 views.py
中的视图函数用来回应。
应用的 Template
是指 “视图模板”,是存放在应用中的 templates/[应用名]/
目录下的 .html
文件。文件中包含了一些预处理脚本,经过 Django 的渲染,能够根据填入的 context
数据生成一个完整的 html
文件。
MVT 和 MVC 的对应关系大概是:Model -> Model, View -> Controller, Template -> View。
3.4 在应用中创建一个数据表格的基本流程
第一步:在 models.py
中新建一个类,并使用类变量指出该表格的所有字段,例如:
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return question_text
如果你的表格类中没有指出哪个字段是”主键”, Django
会自动为你创造一个叫做 id
的 IntegerField
字段作为主键。其值从 1 开始自动递增,不允许出现重复的值,否则会抛出异常。
第二步:在 admin.py
中注册这个数据库到后台管理界面;
from django.contrib import admin
from .models import Question
admin.site.register(Question)
class QuestionAdmin(admin.ModelAdmin):
...
admin.site.register(Question, QuestionAdmin)
第三步:执行 python manage.py makemigrations [应用名]
,生成迁移文件;
第四步:执行 python manage.py migrate
,完成数据迁移。
由于在 models.py
中的修改并不能直接体现在数据库当中,换言之,执行完第一、第二两个步骤后,数据库中的表格结构还没有发生任何改变。需要通过迁移文件指导数据库如何进行修改,因此需要经过第三、第四两个步骤才能将 ORM
的修改迁移到数据库中。
关于字段的类型:Django 为我们提供了几十种内置类型字段,可以参考 Django 中文文档中以下内容:
模型字段参考:字段类型
尤其应该学习一下 ForeignKeyField
(外键字段)和 EmailField
(电子邮箱字段)的使用。
3.5 使用 python 命令行/程序 操作数据表格 (Model)
第一步:在虚拟环境下 cd
到 manage.py
所在的目录;
第二步:命令行执行 python manage.py shell
,虽然结果上看起来与直接命令行启动 python
大同小异,但是这种启动方式配置了更多的项目内的搜索路径,保证 Django
项目中的子应用模块能够正确导入并执行。
第三步:执行查询(方法巨多,下文只介绍最基础的几种方法)。
虽然执行查询的 QuerySet
对象细节颇多,但是如果你的数据库中内容很少,可以使用一种傻瓜式的办法获取某个表格中的全部数据:
from [应用名].models import [表格类]
allRecords = [表格类].objects.all()
oneRecord = [表格类].objects.get(pk=primeKeyValue)
newRecord = [表格类](...)
newRecord.save()
这些用于 python
命令行的查询方法同样可以适用于 views.py
中, views.py
中经常需要进行数据库查询操作。(在 views.py
的试图回调函数设计中,我们经常使用 get_object_or_404
的方法替代 objects.get(...)
方法。)
4.1 [项目名]/urls.py 的文件结构
一个典型的 [项目名]/urls.py
具有类似这样的结构:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('[应用名]/', include('[应用名].urls')),
]
[项目名]/urls.py
的主要功能是将来自客户端的 HttpRequest
分发给下属的应用( django.conrib.admin.site
也可以认为是一个 Django
自带的应用)。 [应用名]/urls.py
也具有与 [项目名]/urls.py
类似的结构。例如当客户端浏览器访问 http://你的IP:端口/service_app/check_prime/135/
时,Django 会将 service_app/check_prime/135/
与 [项目名]/urls.py
中 urlpatterns
指出的 URL 模式一一进行前缀匹配。如果 urlpatterns
中有: path('service_app', include('service_app.urls')),
这一行,则,Django 会从 URL 中截去已经匹配了的前缀,将剩余部分 check_prime/135/
发送给 service_app/urls.py
进行递归匹配,直到找到了对应的视图函数为止。
项目中的所有 urls.py
文件,以 pah('XXX/', include('XXX.urls')),
的形式被组织成了一种树状结构,每个叶子结点就对应了一个视图函数。
4.2 [应用名]/urls.py 的文件结构
与 [项目名]/urls.py
相比, [应用名]/urls.py
更加多元,也更加复杂。
from django.urls import path
from . import views
app_name='[应用名]'
urlpatterns = [
path('', views.IndexView.as_view(), name = 'index'),
path('vote//', views.vote, name = 'vote'),
]
可以看到,无论是视图函数,还是视图类,都需要自己在 views.py
中预先定义好。这里的 name
参数用来给 URL 模式命名, app_name
是当前文件中定义的所有 URL 模式的”名字空间”。在我们将来设计 .html
模板文件时,将可以用到现在通过 name
和 app_name
指定的 URL 模式。
上文中的 <int:question_id></int:question_id>
表示采用正则表达式匹配一个整数,并将这个整数传递给函数 views.vote
作为参数 question_id
。换言之,这要求我们在定义 vote
这个函数的时候,必须要有 question_id
这个形参。
4.3 views.py 的设计方法
序列视图 ListView
用于生成序列型视图类。例如:博客网站的 “最近博文”,在线评测网站的 “题目列表” 页面视图等可以使用序列模型实现。
from django.views import generic
from .models import [表格类]
class IndexView(generic.ListView):
model = [表格类]
template = '[应用名]/index.html'
context_object_name = 'question_list'
class IndexView(generic.ListView):
template = '[应用名]/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return [表格类].objects.XXX
详情视图 DetialView
用于生成详情型视图类。例如:博客网站的 “博文详情”,在线评测网站的 “题目内容” 页面视图等可以使用详情模型实现。 DetailView
要求在 [应用名]/urls.py
的 URL 模式 path
中提供了一个名字叫 pk
的匹配串,并将该匹配串作为主键去寻找一个表格中的对象。如果找到了这个对象,则根据 .html
模板中约定的格式,显示该对象的详细信息;否则反馈一个 Http404
Not Found 页面。
from django.views import generic
from .models import [表格类]
class DetailView(generic.DetailView):
model = [表格类]
template = '[应用名]/detail.html'
无论采用下述的哪种方法给出一个视图函数的返回值,视图函数的第一个参数一定是一个 HttpRequest 对象。
方法零:使用 Http404
返回 404 NotFound 页面。
from django.http import Http404
def vote(request, question_id):
raise Http404("Question does not exist")
方法一:使用 HttpResponse
直接返回 HTML 页面。
from django.http import HttpResponse
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
方法二:使用 render
函数指定渲染的模板,以及 context
对象,并返回渲染后的页面。
def vote(request, question_id):
context = {
'question': question,
'error_message': "you didn't select a choice."
}
return render(request, '[应用名]/detail.html', context)
方法三:使用 HttpResponseRedirect
与 reverse
方法实现页面重定向。
from django.http import HttpResponseRedirect
from django.urls import reverse
def vote(request, question_id):
return HttpResponseRedirect(reverse('[应用名]:result', args=(question_id,)))
使用 request.POST
或者 request.GET
可以得到一个 dict
类型的数据:
from .models import Question, Choice
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
selected_choice = question.choice_set.get(pk=request.POST['choice'])
4.4 设置或删除 Cookie
def login(request):
rep = redirect("/app01/home/")
rep.set_signed_cookie("is_login", "1", salt="ban", max_age=100)
return rep
def logout(request):
rep = redirect("/app01/login/")
rep.delete_cookie("is_login")
return rep
类似于 python
包 jinjia2
渲染器, Django
使用 {{ expr }}
向 HTML 中指明此处渲染时要填入表达式的值。使用 {% script %}
指明渲染时要执行的脚本(一般是 if
或者 for
循环。)
5.1 使用对象的值
DOCTYPE html>
<head>
<title>Question {{ question.id }}title>
head>
<body>
<p>{{ question.question_text }}p>
body>
html>
5.2 使用循环与循环计数器
<ul>
{% if question_list %}
{% for question in question_list %}
<li><strong>{{ forloop.counter }}strong>.{{ question.question_text }}li>
{% endfor %}
{% else %}
<p>No Question.p>
{% endif %}
<\ul>
循环计数器在为个数不确定的对象一一设置 <label></label>
时比较方便,以网站中的单选题为例,每个选项都是一个 <input type="radio">
,但是每个选项的文字内容必须写在这些 <input>
的旁边。由于选项的个数并不确定因此很难手动为这些 <input>
赋予不同的 id
。但却可以使用 id='choice{{ forloop.counter }}'
或类似的方法,循环地给每个 <input>
一个不同的 id
。
5.3 使用外键子对象
如果 class Choice(models.Model)
表格类中包含一个 Question
类的外键,那么在渲染时, Question
类型的对象可以使用 question.choice_set.all
访问 通过外键链接到这个 Question
的所有 Choice
。
在定义外键时,需要给出 related_name
(逆向函数名)的值。 related_name
是逆向关系的函数名,例如上例中通过 Choice
(选项) 可以唯一确定一个 Question
(问题),但是一个 Question
可能可以确定多个 Choice
(换言之, Choice
是 Question
的子对象)。 related_name
的作用是从一个 Question
对象,找到一个与它对应的 Choice
对象列表。如果不指定 related_name
,默认的 related_name
是 [表格类]_set
,例如 choice_set
。但是如果一个表格类有多个同类型的子对象,就必须分别指出两个 related_name
,例如:
class BookInfo(models.Model):
book_name = models.CharField(max_length=32)
donator = models.ForeignKey(UserInfo, related_name="give", on_delete=models.CASCADE)
keeper = models.ForeignKey(UserInfo, related_name="keep", on_delete=models.CASCADE, blank=True)
begin_time = models.DateTimeField(blank=True)
这样对于一个给定的 UserInfo
对象,才能反向确定 give
和 keep
两个 BookInfo
列表(即这个人捐了哪些书,现在借了哪些书)。
5.4 使用静态文件
HTML 文件中的静态文件需要使用一种类似文件路径的方式指出,但模板中的 URL 应该尽可能避免 “硬编码”的出现(换言之,写死了),从而有利于代码的复用与修改。在文件头中使用 {% load static %}
方法,告知脚本解释器,加载 static
命令。此后,使用 {% static '[应用名]/style.css' %}
即可被编译成 Django 搜索到的路径。
5.5 使用站内链接
和 5.4 同理,防止 URL 硬编码。我们使用 {% url '[应用名]:[URL名]' [匹配段填充值序列] %}
给出一个站内链接。例如:
<a href="{% url 'polls:vote' question.id %}">Click Here to Vote on Question {{ question.id }}a>
5.6 实现表单提交
由于 Django 内置了 CSRF
子应用,防止跨站伪 POST 请求,在表单中一定要加入 {% csrf_token %}
,服务器才能正确接收到这条 POST
请求。否则这条 POST
请求会被 Django
自带中间件(MiddleWare) django.middleware.csrf.CsrfViewMiddleware
拦下。也可以在 settings.py
中注释掉该中间件,但是这样会有一定的安全隐患。
<form action="{% url 'book_server:login_check' %}" method="post">
{% csrf_token %}
<input type="text" name="email" placeholder="邮箱@mails.jlu.edu.cn">
<input type="password" name="password" placeholder="密码">
<input type="submit" value="登录">
form>
6.1 使用注册类注册表格类的方法简介
import datetime
from django.utils import timezone
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date timezone.now() and self.pub_date >= timezone.now() - datetime.timedelta(days=30)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
我们还是以 Choice
和 Question
之间的关系为例,介绍使用注册类注册表格类的方法。由于 Choice
类中含有一个 Question
类的 ForeignKey
,因此可以认为 Choice
是 Question
的子对象。所以,逻辑上来说,应该在创建 Question 类的同一个页面上,创建对应的 Choice 对象,注册方法如下。
from django.contrib import admin
from .models import Question, Choice
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 0
class QuestionAdmin(admin.ModelAdmin):
list_display = ('question_text', 'pub_date', 'was_published_recently')
search_fields = ['question_text']
inlines = [ChoiceInline]
admin.site.register(Question, QuestionAdmin)
6.2 SMTP 发送邮件
在发送邮件前,首先要保证您已经根据 2.3′ 节的内容正确地配置了 SMTP 功能,此后,发送邮件就非常地容易了。
def sendEmail(subject, message, fromEmail, toEmailList):
from django.core.mail import send_mail
send_mail(subject, message, fromEmail, toEmailList, fail_silently=False)
sendEmail(
"邮件主题",
"邮件内容",
"from@qq.com",
["to@qq.com"]
)
注:这里的 from@qq.com
必须和 settings.py
中指定的账户一致才能发送。
Original: https://blog.csdn.net/GGN_2015/article/details/122669056
Author: GGN_2015
Title: Django3.2.x 学习实践
相关阅读
Title: 关于 django.db.migrations.exceptions.InconsistentMigrationHistory
出现的原因
出现这个的问题大概有几种:
- 迁移过程失败,导致 django_migrations 中有记录,但实际没有表
- 修改了 django 内部的表结构没有做相应的处理
诸如这几种原因其实都是因为 django_migrations 表中有与要迁移的新表相关的表,所以迁移的时候导致了冲突发生。
我出现这个问题的原因是继承并扩展了 django 的 AbstractUser 类,进行迁移的时候,由于项目开头执行初始化的 migrate 已经创建了 auth_user 相关的表,该表与我的表功能一致,故发生了冲突。
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency user.0001_initial on database 'default'.
解决方案
- 删除相关的表
- 删除 django_migrations 中相关的记录
关于第二点需要注意,如果删除 django_migrations 表,那么原本不受影响的表在下一次迁移的时候也会被重新提交,又造成新的冲突,那时候就只能清除数据库了。
示例如下:
在我下面的表中,除了 blog、django_migrations,都是在初始化时新建出来的表。
+----------------------------+
| Tables_in_yipsweb |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| blog |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
删掉对应表之后状态如下:
+-------------------+
| Tables_in_yipsweb |
+-------------------+
| blog |
| django_migrations |
+-------------------+
接下来删除 django_migrations 中对应的记录。在上一步中删除的表位于 id 19 的条目之上,将 id 小于 19 的所有条目删除。
mysql> select * from django_migrations;
+----+--------------+--------------------------------------------------+----------------------------+
| id | app | name | applied |
+----+--------------+--------------------------------------------------+----------------------------+
| 1 | contenttypes | 0001_initial | 2022-03-14 04:07:28.286762 |
| 2 | auth | 0001_initial | 2022-03-14 04:07:29.684629 |
| 3 | admin | 0001_initial | 2022-03-14 04:07:29.956432 |
| 4 | admin | 0002_logentry_remove_auto_add | 2022-03-14 04:07:29.979374 |
| 5 | admin | 0003_logentry_add_action_flag_choices | 2022-03-14 04:07:29.991648 |
| 6 | contenttypes | 0002_remove_content_type_name | 2022-03-14 04:07:30.234672 |
| 7 | auth | 0002_alter_permission_name_max_length | 2022-03-14 04:07:30.380076 |
| 8 | auth | 0003_alter_user_email_max_length | 2022-03-14 04:07:30.436457 |
| 9 | auth | 0004_alter_user_username_opts | 2022-03-14 04:07:30.460891 |
| 10 | auth | 0005_alter_user_last_login_null | 2022-03-14 04:07:30.620477 |
| 11 | auth | 0006_require_contenttypes_0002 | 2022-03-14 04:07:30.629054 |
| 12 | auth | 0007_alter_validators_add_error_messages | 2022-03-14 04:07:30.642607 |
| 13 | auth | 0008_alter_user_username_max_length | 2022-03-14 04:07:30.761427 |
| 14 | auth | 0009_alter_user_last_name_max_length | 2022-03-14 04:07:30.913584 |
| 15 | auth | 0010_alter_group_name_max_length | 2022-03-14 04:07:30.949697 |
| 16 | auth | 0011_update_proxy_permissions | 2022-03-14 04:07:30.963744 |
| 17 | auth | 0012_alter_user_first_name_max_length | 2022-03-14 04:07:31.101315 |
| 18 | sessions | 0001_initial | 2022-03-14 04:07:31.184898 |
| 19 | blog | 0001_initial | 2022-03-15 08:45:10.789230 |
| 20 | blog | 0002_alter_blogmodel_articleid | 2022-03-15 16:50:59.163678 |
| 21 | blog | 0003_alter_blogmodel_options_blogmodel_viewcount | 2022-03-16 01:37:30.220132 |
+----+--------------+--------------------------------------------------+----------------------------+
删除后重新执行迁移,成功

Original: https://blog.csdn.net/qq_39177678/article/details/123666659
Author: 庸了个白
Title: 关于 django.db.migrations.exceptions.InconsistentMigrationHistory
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/288997/
转载文章受原作者版权保护。转载请注明原作者出处!