一,项目题目:Book单表的增删改查页面
该项目主要练习使用Django开发一个Book单表的增删改查页面,通过这个项目巩固自己这段时间学习Django知识。
二,项目需求:
开发一个简单的Book增删改查页面 要求: 实现一个书籍的增删改查功能即可 尽量规范化代码 查询操作: 1,查找A出版社出版过的书籍价格大于100 2,查询某月出版的所有python书籍名称 3,查询价格为100,或者150的所有书籍名称及其出版社名称 4,查询价格在100-200之间的所有书籍名称及其价格 5,查询所有A出版社出版的书籍价格,(降序排列,去重)
三,编码规范需求:
编码规范需求:15% 1. 代码规范遵守pep8 (https://python.org/dev/peps/pep-0008/) 2. 函数有相应的注释 3. 程序有文档说明文件(README.md参考:https://github.com/csrftoken/vueDrfDemo) 4. 程序的说明文档必须包含的内容:程序的开发环境(django版本)、程序的实现的功能、程序的启动方式、登录用户信息、程序的运行效果 5. 程序设计的流程图:
四,项目思路
1,创建项目及其APP
1,创建project django-admin startproject Book_single 2,创建APP python manage.py startapp app01 3,settings配置设置模板 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'template')], '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', ], }, }, ] 4,将APP添加到settings.py里面 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01' ] 5,设置时区和语言 Django默认使用美国时间和英语,在项目的settings文件中,如下图所示: LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True 我们将其改为 亚洲/上海 时间和中文 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False
2,配置URL
这里将全局配置的urls.py文件和APP下面的urls.py分开写。(自己在APP下面创建一个urls.py)这样做的好处:
- 1,全局配置文件中的urls.py模型主要管理本项目的全局url配置(虽然目前此项目只有一个APP)
- 2,每个APP的urls.py模块管理自己APP下的url集。
2.1 全局配置文件(Book_single/urls.py)
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('app01/', include('app01.urls')), ]
2.2 APP下url配置文件(Book_single/app01/urls.py)
name的作用就是对自己的URL进行命名,让自己能够处于Django的任意处,尤其是模板内显式的引用它,这是一个非常强大的功能,相当于给URL取了一个全局变量名,不会将url匹配地址写死。
from django.urls import path, re_path from app01 import views app_name = 'app01' urlpatterns = [ path('books/', views.books, name='books'), path('addbooks/', views.addbooks, name='addbooks'), path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), path('query/', views.query, name='query'), path('test/', views.test, name='test') ]
在二级路由(即APP的URls文件中),在urlpatterns后,应该加上app_name='app_name',否则有可能会发生报错。
3,设计数据库
3.1 数据库模式设计
作为一个单表增删改查操作项目,很明显,我们至少需要一个Book表,用来保存下面信息:图书标题,价格,出版日期,出版社等等,代码如下:
Book_single/app01/models.py
from django.db import models # Create your models here. class Book_single(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32, unique=True) price = models.DecimalField(max_digits=8, decimal_places=2) pub_date = models.DateField() publish = models.CharField(max_length=32) def __str__(self): return self.title
各字段含义:
- id必填,自增字段,并且是主键
- title,最长不超过32个字符,并且唯一,也就是不能有相同名字
- price,设置了精度的十进制数字,数字允许的最大位数是8位,小数的最大位数是2.
- 出版日期,设置出版日期
- 出版社,最长不超过32位
- 使用__str__帮助人性化显示对象信息
3.2 更改settings配置,设置数据库
在settings中修改DATABASES:
1,注销掉下面设置 # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } 2, 新增下面设置 import pymysql pymysql.install_as_MySQLdb() DATABASES = { 'default':{ 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '3306', } }
3.3 数据库迁移
python manage.py makemigrations python manage.py migrate
结果展示:
D:DjangoBook_single>python manage.py makemigrations Migrations for 'app01': app01migrations 001_initial.py - Create model Book_single D:DjangoBook_single>python manage.py migrate Operations to perform: Apply all migrations: admin, app01, auth, contenttypes, sessions Running migrations: Applying app01.0001_initial... OK
4,编写视图
由于是单表操作,而且视图函数比较简单,所以我们不需要初步规划视图函数,直接写即可。
Book_single/app01/views.py
from django.shortcuts import render, HttpResponse, redirect # Create your views here. from app01 import models def books(request): book_list = models.Book_single.objects.all() return render(request, 'app01/books.html', locals()) def addbooks(request): # title = 'python1' # price = 100 # publish = '机械出版社' # pub_date = '2019-3-3' # book_obj = models.Book_single.objects.create(title=title, price=price, # publish=publish, pub_date=pub_date) # book_obj.save() # return HttpResponse("OK") if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有选项不能为空'}) models.Book_single.objects.create(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books') else: return render(request, 'app01/addbook.html') def delbook(request, id): # return HttpResponse("OK") print(models.Book_single.objects.filter(id=id)) models.Book_single.objects.filter(id=id).delete() # 两次请求 return redirect('/app01/books/') def changebook(request, id): book_obj = models.Book_single.objects.filter(id=id).first() print(book_obj) if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有选项不能为空'}) models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books/') else: return render(request, 'app01/change.html', {'book_obj': book_obj}) def query(request): # 1, 查询Publish1出版过价格大于100的书籍 book_list = models.Book_single.objects.filter(publish='Publish1', price__gt=100) print(book_list) # 2,查询2019年1月出版的所有以py开头的数据名称 book_list = models.Book_single.objects.filter(title__startswith='py', pub_date__year=2019, pub_date__month=1).values('title') print(book_list) # 3,查询价格为50,100 或者150 的所有书籍的名称及其出版社名称 book_list = models.Book_single.objects.filter(price__in=[50, 100, 150]).values('title', 'publish') print(book_list) # 4,查询价格在100到200之间所有书籍名称及其价格 book_list = models.Book_single.objects.filter(price__range = [100, 200]).values('title', 'price') print(book_list) # 5, 查询所有Publish1出版的书籍的价格(由高到底排序,去重) book_list = models.Book_single.objects.filter(publish='Publish1').values('price').distinct().order_by('-price') print(book_list) return HttpResponse('OK') def test(request): return HttpResponse("OK")
5,创建HTML页面文件
5.1,创建四个HTML文件
先不填充内容,创建即可。在项目的根路径下创建一个template目录,然后在template目录下创建一个app01目录,在里面创建HTML,如下:
5.2,引入Bootstrap
Bootstrap3.3.7的下载地址:请点击我
JQuery 的下载地址:请点击我
注意:{% static '相对路径' %} 这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来。
根目录下新建一个static目录,并将解压后的bootstrap-3.3.7目录,整体拷贝到static目录中,而且由于Bootstrap依赖于JQuery,所以我们需要引入JQuery,并且在static目录下,新建一个CSS和JS目录,作为以后的样式文件和JS文件的存放地,创建展示图如下:
然后打开项目的settings文件,在最下面添加配置,用于指定静态文件的搜索目录:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
5.3 创建base.html模板
既然要将前端页面做的像个样子,那么就要各写各的。一个网站要有自己的统一风格和公共部分,可以将这部分内容集中到一个基础模板base.html中。现在在根目录下的template中新建一个base.html文件作为站点的基础模板。
在Bootstrap文档中,为我们提供了一个非常简单而且又实用的基础模板,代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>Bootstrap 101 Template</title> <!-- Bootstrap --> <link href="css/bootstrap.min.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <h1>你好,世界!</h1> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> </body> </html>
将其整体拷贝到base.html文件中。
然后修改base内容,最后base.html文件的内容如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> {% block title %} <title>base</title> {% endblock title %} <!-- Bootstrap --> <link href="/static/bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 --> <!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> <link rel="stylesheet" href="/static/CSS/base.css"> </head> <body> <div class="container my-con"> <div class="col-md-2"> {% block operation %} <h2>operation</h2> {% endblock operation %} </div> <div class="col-md-10"> {% block con %} <h2>content</h2> {% endblock con %} </div> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) --> <script src="/static/JS/jquery-3.2.1.min.js"></script> <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 --> <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script> </body> </html>
5.4 完成自己编写的HTML内容
addbook.html
{% extends 'base.html' %} {% block title %} <title>addbook</title> {% endblock title %} {% block operation %} <h2>添加书籍</h2> {% endblock operation %} {% block con %} <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="title">书籍名称</label> <input type="text" class="form-control" id="title" name="title"> </div> <div class="form-group"> <label for="price">价格</label> <input type="text" class="form-control" id="price" name="price"> </div> <div class="form-group"> <label for="pub-date">出版日期</label> <input type="text" class="form-control" id="pub-date" name="pub-date"> </div> <div class="form-group"> <label for="publish">出版社</label> <input type="text" class="form-control" id="publish" name="publish"> </div> <button type="submit" class="btn btn-success pull-right">提交</button> </form> <p style="color: red;">{{ ret }}</p> {% endblock con %}
books.html
{% extends 'base.html' %} {% block title %} <title>books</title> {% endblock title %} {% block operation %} <h2>查看书籍</h2> {% endblock operation %} {% block con %} <a href="{% url 'app01:addbook' %}" class='btn btn-primary' role='button'>添加书籍</a> <div class="table-responsive"> <table class="table table-striped table-bordered table-hover"> <thead> <tr class="active"> <td><strong>书籍名单</strong></td> <td><strong>价格</strong></td> <td><strong>出版日期</strong></td> <td><strong>出版社</strong></td> <td><strong>删除操作</strong></td> <td><strong>编辑操作</strong></td> </tr> </thead> <tbody> {% for book in book_list %} <tr> <td>{{ book.title }}</td> <td>{{ book.price }}</td> <td>{{ book.pub_date|date:'Y-m-d' }}</td> <td>{{ book.publish }}</td> <td><a href="/app01/{{ book.pk }}/delete" class="btn btn-danger" role="button" >删除</a></td> <td><a href="/app01/{{ book.pk }}/change" class="btn btn-info" role="button" >编辑</a></td> </tr> {% endfor %} </tbody> </table> </div> {% endblock con %}
change.html
{% extends 'base.html' %} {% block title %} <title>change</title> {% endblock title %} {% block operation %} <h2>编辑书籍</h2> {% endblock operation %} {% block con %} <form action="" method="post"> {% csrf_token %} <div class="'form-group"> <label for="title">书籍名称</label> <input type="text" class="form-control" id="title" name="title" value="{{ book_obj.title }}"> </div> <div class="'form-group"> <label for="price">价格</label> <input type="text" class="form-control" id="price" name="price" value="{{ book_obj.price }}"> </div> <div class="'form-group"> <label for="pub-date">出版日期</label> <input type="text" class="form-control" id="pub-date" name="pub-date" value="{{ book_obj.pub_date|date:'Y-m-d' }}"> </div> <div class="'form-group"> <label for="publish">出版社</label> <input type="text" class="form-control" id="publish" name="publish" value="{{ book_obj.publish }}"> </div> <button type="submit" class="btn btn-success pull-right">提交</button> </form> <p style="color: red;">{{ ret }}</p> {% endblock con %}
5.5 部分模板语法的解析
从上面的HTML代码,我们可以发现出现了许多模板语言。最多的就是使用大括号和百分比的组合来表示。
下面学习一下标签的使用。标签看起来像下面的格式。但是标签比变量更加复杂:一些在输出中创建文本,一些通过循环或者逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模板中。一些标签需要开始和结束标签等等。
5.5.1 {% csrf_token %}
csrf_token 标签,用于生成csrf_token的标签,用于防治跨站攻击验证。注意如果你的view的index使用的是render_to_response方法,则不会生效。
其实这只会生成一个input 标签,和其他表单标签一起提交给后台。
5.5.2 extend(继承)模板标签
在实际应用中,我们将用到Django模板系统来创建整个HTML页面。这就带来了一个常见的Web开发问题:在整个网站中,如何减少共有页面区域(比如站点导航)所引起的重复和冗余代码?
解决该问题的传统做法是使用服务器端的includes,我们可以在HTML页面中使用该指令将一个网页嵌入到另一个中。事实上,DJango通过 {% include %} 支持了这种方法。
但是Django解决此类问题的首先方法是使用更加优雅的策略——模板继承。
本质上来说,模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点共用部分和定义块进行重载。
让我们通过修改 current_datetime.html 文件,为 current_datetime 创建一个更加完整的模板来体会一下这种做法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
这看起来很好,但是如果我们要为hours_ahead视图创建另一个模板会发生什么呢?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
很显然,我们刚才重复了大量的HTML代码。详细一下,如果有一个更典型的网站,它由导航条,样式表,可能还会有一些JavaScript 代码,事情必将以向每个模板填充各种冗余的HTML而告终。
解决这个问题的服务器端include方案是找出两个模板中的共同部分,将其保存为不同的模板片段,然后在每个模板中进行include。也许你可以把模板头部的一些代码保存为header.html 文件:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
你可能也会将底部保存到文件 footer.html:
<hr> <p>Thanks for visiting my site.</p> </body> </html>
对基于include 的策略,头部和底部的包含很简单。麻烦的是中间部分。在此范例中,每个页面都有一个My helpful timestamp site</h1> 标题,但是这个标题不能放在 header.html 中,因为每个页面的 <title> 是不同的。 如果我们将 <h1> 包含在头部,我们就不得不包含 <title> ,但这样又不允许在每个页面对它进行定制。 何去何从呢?
Django的模板继承系统解决了这些问题,你可以将其视为服务器端include的逆向思维版本。你可以对那些不同的代码进行定义,而不是共同代码段。
第一步是定义基础模板,该框架之后将由子模板所继承。以下是我们目前所讲述范例的基础模板:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
这个叫做base.html 的模板定义了一个简单的HTML框架文档,我们将本站点的所有页面中使用。子模板的作用就是重载,添加护着保留那些块的内容。(这个文件可以保存在template目录下,命令为base.html)
我们将使用模板标签{% block %} 。 所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。 每个{% block %}标签所要做的是告诉模板引擎,该模板下的这一块内容将有可能被子模板覆盖。
现在我们已经有了一个基本模板,我们可以修改 current_datetime.html 模板来 使用它:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
再为hours_ahead 视图创建一个模板,看起来是这样的:
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
看起来很漂亮是不是?每个模板只包含对自己而言独一无二的代码。无需多余的部分。如果想要对站点级进行设计修改,仅仅需要修改base.html,所有其他模板会立即反映出所做的修改。
以下是其工作方式:
在加载 current_datetime.html 模板时,模板引擎发现了 {% extends %} 标签, 注意到该模板是一个子模板。 模板引擎立即装载其父模板,即本例中的 base.html 。此时,模板引擎注意到 base.html 中的三个 {% block %} 标签,并用子模板的内容替换这些 block 。因此,引擎将会使用我们在 { block title %} 中定义的标题,对 {% block content %} 也是如此。 所以,网页标题一块将由{% block title %}替换,同样地,网页的内容一块将由 {% block content %}替换。
注意由于子模板并没有定义 footer 块,模板系统将使用在父模板中定义的值。 父模板 {% block %} 标签中的内容总是被当作一条退路。继承并不会影响到模板的上下文。 换句话说,任何处在继承树上的模板都可以访问到你传到模板中的每一个模板变量。你可以根据需要使用任意多的继承次数。 使用继承的一种常见方式是下面的三层法:
<1> 创建 base.html 模板,在其中定义站点的主要外观感受。 这些都是不常 修改甚至从不修改的部分。 <2> 为网站的每个区域创建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。这些模板对base.html 进行拓展,并包含区域特定的风格与设计。 <3> 为每种类型的页面创建独立的模板,例如论坛页面或者图片库。 这些模板拓展 相应的区域模板。
这个方法可最大限度的重用代码,并使得向公共区域(如区域级的导航)添加内容称为一件轻松的工作。
5.5.3 模板继承的一些诀窍
<1>如果在模板中使用 {% extends %} ,必须保证其为模板中的第一个模板标记。 否则,模板继承将不起作用。 <2>一般来说,基础模板中的 {% block %} 标签越多越好。 记住,子模板不必定义 父模板中所有的代码块,因此你可以用合理的缺省值对一些代码块进行填充,然后只对 子模板所需的代码块进行(重)定义。 俗话说,钩子越多越好。 <3>如果发觉自己在多个模板之间拷贝代码,你应该考虑将该代码段放置到父模板的 某个 {% block %} 中。如果你需要访问父模板中的块的内容,使用 {{ block.super }} 这个标签吧,这一个魔法变量将会表现出父模板中的内容。 如果只想在上级代码块基 础上添加内容,而不是全部重载,该变量就显得非常有用了。 <4>不允许在同一个模板中定义多个同名的 {% block %} 。 存在这样的限制是因 为block 标签的工作方式是双向的。 也就是说,block 标签不仅挖了一个要填的坑,也定义了在父模板中这个坑所填充 的内容。如果模板中出现了两个相同名称的 {% block %} 标签,父模板将无从得知 要使用哪个块的内容。
五,注意事项及其补充内容
5.1,django.db.utils.ProgrammingError: (1146, u"Table'' doesn't exist")解决办法
5.1.1 现象
在数据库中删除了一张表,重新执行python manage.py migrate 时报错,提示不存在这张表
5.1.2 原因
主要是因为Django一般在第一次迁移的时候新建表,后面都不会新建表,而是只检查字段等等的变化,所以我们既然已经删除了这张表,django检查这张表的字段变化的时候自然报错了。
5.1.3 解决方法
解决方法仍然是执行数据库迁移命令
python manage.py makemigrations python manage.py migrate
只不过在执行上面代码之前,把第一次执行迁移创建的那个记录删除掉,否则它检查到已经执行过第一次了,那么它后面就不会创建表了。
在该APP模块下,有一个migtations文件夹,除了前两个文件外,其他的文件都删除,其实每一次如果有变化的话,这边就会生成一个文件,下面的这个001_initial.py看名字就知道是第一次迁移的时候生成的,也就是因为有它的存在,所以以后每次再执行就不再创建表了。
其次,在数据库里面也有相应的记录,也要删除。我们仔细看看数据库里面存的是什么,在Django_migrations里面,这表里面存的都是每次迁移的记录,当然记录的是什么模块以及对应的文件名字,比如我们这里的模块是dtheme,这里的文件名叫做001_initial,和我们文件夹里面是一一对应的,同样,删除这条记录。
然后在执行数据库迁移命令即可。
注意:如果这个APP模块下面还有其他的model的话,那么其他的model创建的表也要删除掉,相当于我们这样的解决方案是针对整个APP模块的,要执行就会全部重新生成,不然会提示部分表已经存在错误。
5.2 django.db.utils.InternalError: (1366, "Incorrect string value: '\xE6\x9C\xBA\xE6\xA2\xB0...' for column 'publish' at row 1")
这个问题,在MySQL中修改配置文件。
具体请参考博客:请点击我
5.2 补充1:正则匹配
上面代码我们用到了正则匹配,下面学习一下正则匹配的用法。
(如果想全面学习正则表达式的使用:请点击我)
这里介绍与Django有关的知识点。我们最常用的正则匹配就是下面两个:
^ 表示会匹配行或者字符串的起始位置,有时还会匹配整个文档的起始位置 $ 表示匹配行或者字符串的结尾
Django在检查URL模式之前,移除每一个申请的URL开头的斜杠(/)。这意味着我们写的URL模式不用包括斜杠(/)。
举个例子:
from django.urls import path, re_path from app01 import views urlpatterns = [ path('login/<int:id>/', views.login), # 上下两个是同样的效果 re_path(r'login/(d+)/', views.login), ]
5.3 补充2:re_path和path
在新版Django2.X中,URL的路由表示用path 和re_path代替,模块的导入由Django1.X版本的url变为Django2.X中的path,re_path。
看下面两幅图:
而且该url() 函数传递了四个参数,两个必需:regex和view,以及两个可选:kwargs 和 name。也就是说正则表达式和视图是两个必填参数。
函数path() 具有四个参数,两个必需参数 :route 和view,两个可选参数:kwargs 和 name。即路由和视图是必填参数。
所以两者的主要区别就在于url() 是要写正则表达式(regex)的路由,而path()时写非正则路由(route),接下来主要看一下path() 函数的四个参数含义。
1,path() 参数: route route 是一个匹配URL的准则(类似于正则表达式),当Django响应一个 请求时,它会从urlpaterns的第一项开始,按顺序依次匹配列表中的项,直到 找到匹配的项。 这些准则不会匹配GET 和 POST 参数或域名。例如:URLconf在处理请求 https://www.example.com.myapp/时候,它会尝试匹配myapp/。处理请 求https://www.example.com/myapp/?page=3时,也只会尝试匹配myapp/ 2, path()参数: view 当django找到一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。 3, path()参数:kwargs 任意个关键字参数可以作为一个字典传递给目标视图函数 4, path()参数: name 为你的URL取名能使你在Django的任意地方唯一的引用它,尤其是在模板中。 这个有用的特性运行你只改一个文件就能全局的修改某个URL模式。
5.4 path() 的用法
上面介绍的path中,第一个参数route使用的是非正则表达式可以表示的普通路由路径。
注意:
- 要从URL捕获值,请使用尖括号
- 捕获的值可以选择包括转换器类型。例如:用<int:name> 捕获整数参数。如果未包含转换器 / ,则匹配除字符之外的任何字符串。
- 没有必要添加前导斜杠,因为每个URL都有。例如,articles 不是 /articles。
默认情况下,以下路径转换器可用:
- str- 匹配除路径分隔符之外的任何非空字符串‘/’。如果转换器未包含在表达式中,则这是默认值。
- int- 匹配零或者任何正整数,返回一个int
- slug- 匹配由ASCII字母或者数字组成的任何slug字符串,以及连字符和下划线字符。例如 : building-your-1st-django-site。
- uuid- 匹配格式化的UUID,要防止多个URL映射到同一页面,必须包含短划线并且字母必须为小写。例如:0787878d3-4545-456a-d45ad6d4s5d4a65。返回一个UUID实例。
- path- 匹配任何非空字符串,包括路径分隔符 ‘/’。这使得你可以匹配完整的URL路径,而不仅仅是URL路径的一部分str。
比如要匹配一个视图中的函数路由,该函数有两个形参:
def peopleList(request,book_id)
第一个request是默认的,那么路由自动匹配该函数的第二个形参,匹配格式:<int:book_id>,并返回一个正整数或者零值。
urlpatterns = [ path('booklist/', views.booklist, name='booklist'), path('<int:book_id>/', views.peopleList, name='peopleList'), ]
5.5 re_path() 的用法
而如果遇上路径和转换器语法都不足以定义的URL模式,那么就需要使用正则表达式,这时候就需要使用re_path() ,而非path()。
from django.urls import re_path
在Python正则表达式中 ,命名正则表达式组的语法是(?P<name>pattern),组name的名称,并且pattern是要匹配的模式。
还是以上面的为例,采用正则表达式写下来,如下:
re_path(r'^(d+)/$',views.peopleList,name='peopleList'), # 类似于下面 path('<int:book_id>/', views.peopleList, name='peopleList'),
这样也可以匹配到views视图中的peopleList函数的形参。
所以这两种使用方式在使用上根据实际情况自行使用。
5.6 re_path和path的区别
1,re_path和path的作用都是一样的。只不过re_path是在写url的时候可以用正则表达式,功能更加强大。
2,写正则表达式都推荐使用原生字符串,也就是r开头的字符串
3,在正则表达式中定义变量,需要使用圆括号括起来。这个参数是由名字的,那么需要使用(?P<参数的名字>)。然后在后面添加正则表达式的规则。
4,如果不是特别需求,直接使用path就够了,省的将代码搞的很麻烦(因为正则表达式是非常晦涩的,特别是一些比较复杂的正则表达式,可能今天写的明天就忘了),除非是URL中确实是需要使用正则表达式来解决才使用re_path。
举个例子
urlpatterns = [ re_path(r"^list/(?P<year>d{4})/$", views.article_list), re_path(r"^list/(?P<month>d{2})/$", views.article_list_month) ]
第一个表达式以list开头,中间需要有4个数字,一个都不能多也不能少,再以'/'结尾。形如list/2019/这样的字符串才能被识别,同理,第二句是需要形如 list/02/这样的字符串才能被识别。
六,结果展示
6.1 项目树形图展示:
6.2 增加页面展示
增加成功,直接跳转到查看界面
6.3 查看页面展示
6.4 编辑页面展示
6.5 创建的数据库如下:
七,代码展示及部分代码解析
7.1 完整代码
传送门:请点击我(小编的GitHub)
7.2 部分注意写错的代码
感觉上面代码加上GitHub的代码,就不需要展示了。。。。。。
7.2.1 注意代码1
算了,这里还是放上APP的urls.py
from django.urls import path, re_path from app01 import views app_name = 'app01' urlpatterns = [ path('books/', views.books, name='books'), path('addbooks/', views.addbooks, name='addbooks'), path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), path('query/', views.query, name='query'), path('test/', views.test, name='test') ]
和views.py里面的删除和修改函数:
def delbook(request, id): # return HttpResponse("OK") print(models.Book_single.objects.filter(id=id)) models.Book_single.objects.filter(id=id).delete() # 两次请求 return redirect('/app01/books/') def changebook(request, id): book_obj = models.Book_single.objects.filter(id=id).first() print(book_obj) if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有选项不能为空'}) models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books/') else: return render(request, 'app01/change.html', {'book_obj': book_obj})
为什么重点放这里呢,当然主要是因为自己犯错了,这里需要注意的。
因为这里有一个匹配视图中函数路由的函数,里面又两个形参:
path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'),
第一个request是默认的,所以路由会自动匹配该函数的第二个形参,匹配格式为<int:id> , 并返回一个正整数或者零值。
(这里也可以用re_path进行匹配,这里附上两个类似的代码)
urlpatterns1 = [ path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), # 等同于下面 re_path(r'^(d+)/delete', views.delbook, name='delbook'), re_path(r'^(d+)/change', views.changebook, name='changebook'), ]
7.2.2 注意代码2
在settings.py中,添加配置,用于指定静态文件的搜索目录,注意代码如下:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
注意 将STATICFILES 不能写错!!!
7.3 部分代码解析
7.3.1 获取所有数据显示在页面上
book_list = models.Book_single.objects.all()
通过上面获取数据,拿到数据后,渲染给前端;前端通过for循环的方式,取出数据。
目的就是通过Book_single(这个数据表)里面的字段拿到对应的数据。
7.3.2 删除功能的制作
models.Book_single.objects.filter(id=id).delete()
通过上面的代码,找到数据库中对应的id字段,删除这个ID字段对应的这行数据。
7.3.3 编辑功能的制作
book_obj = models.Book_single.objects.filter(id=id).first()
通过上面代码,拿到需要编译的ID,然后在数据库中找到对应的ID字段,然后对内容进行修改。
修改完之后,对数据库对应字段的内容进行更新。
models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish)
7.3.4 查询功能的代码
查询部分代码比较简单,这里回顾一下。
如果不懂:请点击这里
def query(request): # 1, 查询Publish1出版过价格大于100的书籍 book_list1 = models.Book_single.objects.filter(publish='Publish1', price__gt=100) # 2, 查询2019年1月出版的所有以pyth开头的书籍名称 book_list2 = models.Book_single.objetcs.filter(title__startwith='pyth', pub_date__year=2019, pub_date__month=1).values('title') # 3, 查询价格为50, 100或者 150 的所有书籍的名称及其出版社名称 book_list3 = models.Book_single.objects.filter(price__in[50, 100, 150]).values ('title, 'publish') #4, 查询价格在100到200之间所有书籍名称及其价格 book_list4 = models.Book_single.objects.filter(price__range= [100, 200]).values ('title', 'price') #5,查询所有Publish1出版的书籍的价格(由高到低排序,去重) book_list5 = models.Book_single.objects.filter(publish="Publisher1").values ('price').distinct().order_by('-price')