后端开发简介 后端开发框架:
Java: Spring
Python: Django, Flask, tornado
MVC 框架(核心思想:解耦。):
Web MVC 框架模块功能:
M: Model,模型,和数据库进行交互。 V: View,视图,产生 html 页面。 C: Controller,控制器,接收请求,进行处理,与 M 和 V 进行交互,返回应答。
Django 一般前后端不分离,虽然也可以分离。
Django 遵循 MVC 思想,但是有自己的一个名词,叫做 MVT 。Django 遵循快速开发 和 DRY(Do not repeat yourself) 原则,不要自己去重复一些工作。
MVT 各部分功能:
M: Model,模型,和 MVC 中 M 功能相同,和数据库进行交互。 V: View,视图,和 MVC 中 C 功能相同,接收请求,进行处理,与 M 和 T 进行交互,返回应答。 T: Template,模板,和 MVC 中 V 功能相同,产生 html 页面。
配置虚拟环境 虚拟环境是真实 python 环境的复制版本。
在虚拟环境中使用的 python 是复制的 python,安装 python 包也是安装在复制的 python 中。
安装虚拟环境工具:
1 sudo apt install python3-venv
创建虚拟环境:
激活虚拟环境:
1 source myenv/bin/activate
想退出虚拟环境,可以运行以下命令:
退出了虚拟环境,如何再次进入?首先,导航到虚拟环境所在的目录。
在该目录下,运行以下命令来激活虚拟环境:
现在,尝试在虚拟环境里面下载东西。注意,即使配置了翻墙工具,虚拟环境中的 pip 请求可能无法正确通过代理 。解决方案(port 需要视情况改动):
1 pip install jieba --proxy="http://127.0.0.1:7897"
安装完成后,我们可以:
1 pip freeze > requirements.txt
查看内容:
1 2 (myenv) zhiyue@168:~/myenv$ cat requirements.txt jieba==0.42.1
这个 txt 文件的作用是,我们可以使用它方便地创建需要的环境 :
1 pip install -r requirements.txt
在虚拟环境中安装 Django :
1 pip install django==4.2 --proxy="http://127.0.0.1:7897"
项目创建 创建 Django 项目 注意:创建应用必须先进入虚拟环境。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (myenv) zhiyue@168:~/myenv$ django-admin startproject day1010 (myenv) zhiyue@168:~/myenv$ ls bin day1010 include lib lib64 pyvenv.cfg (myenv) zhiyue@168:~/myenv$ cd day1010/ (myenv) zhiyue@168:~/myenv/day1010$ tree . ├── day1010 │ ├── asgi.py │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py 2 directories, 6 files
说明:
__init__.py
: 说明 day1010 是一个 python 包。
settings.py
: 项目的配置文件。
urls.py
: 进行 url 路由的配置。
wsgi.py
: (对接某种类似协议的东西)web 服务器和 Django 交互的入口。
manage.py
: 项目的管理文件。
创建 Django 应用
一个项目由很多个应用组成的,每一个应用完成一个功能模块。
注意,创建应用时需要先进入项目目录:
1 python3 manage.py startapp booktest
效果如下:
对于 booktest 文件夹下的文件:
__init__.py
: 说明目录是一个 Python 模块。
models.py
: 写和数据库项目的内容,设计模型类。
views.py
: 接收请求,进行处理,与 M 和 T 进行交互,返回应答。定义处理函数,视图函数 。
tests.py
: 写测试代码的文件 。
admin.py
: 网站后台管理相关的文件。
migrations
: (作用后面讲解)
应用注册 建立应用和项目之间的联系,需要对应用进行注册。
在 day1010/settings.py 中 INSTALLED_APPS 下添加应用的名称就可以完成安装:
1 2 3 4 5 6 7 8 9 INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'booktest' , ]
启动项目 在开发阶段,为了能够快速预览到开发的效果,django 提供了一个纯 python 编写的轻量级 web 服务器,仅在开发阶段使用。
运行服务器的命令:
1 python3 manage.py runserver
模型类 ORM
ORM 框架帮我们把类和数据表进行了映射,可以让我们通过类和类对象操作它所对应的表格(数据库)中的数据 。ORM 框架还可以根据我们设计的类自动生成数据库中的表格 ,省去了我们自己建表的过程。
使用 django 进行数据库开发的步骤如下:
在 models.py
中定义模型类
迁移去数据库中看表是否生成
通过类和对象完成数据增删改查操作
模型类设计 在 models.py 中定义模型类如下:
1 2 3 4 5 6 7 from django.db import modelsclass BookInfo (models.Model): btitle = models.CharField(max_length= 20 ) bpub_data = models.DateField()
模型类生成表 生成迁移文件,根据模型类生成创建表的迁移文件:
1 python3 manage.py makemigrations
Django 框架根据我们设计的模型类生成了迁移文件,在迁移文件中可以看到 fields 列表中每一个元素跟 BookInfo 类属性名以及属性的类型是一致的。同时我们发现多了一个 id 项,这一项是 Django 框架帮我们自动生成的,在创建表的时候 id 就会作为对应表的主键列,并且主键列自动增长。
执行迁移,根据第一步生成的迁移文件在数据库中创建表:
1 python3 manage.py migrate
执行迁移命令后,Django 框架会读取迁移文件自动帮我们在数据库中生成对应的表格。
Django 默认采用 sqlite3 数据库,db.sqlite3 就是 Django 框架帮我们自动生成的数据库文件。 sqlite3 是一个很小的数据库,通常用在手机中,它跟 mysql 一样,我们也可以通过 sql 语句来操作它。
在 vscode 安装 SQLite Viewer 插件,可以查看此文件。
上面生成的表的名字叫做 booktest_bookinfo,booktest 是应用的名字,bookinfo 是模型类的名字。
通过模型类操作数据表 进入项目 shell 的命令:
在 shell 终端中演示的例子:
1 2 3 4 5 6 >>> from booktest.models import BookInfo >>> b = BookInfo() >>> b.btitle = '转生成为雷电将军然后天下无敌' >>> from datetime import date >>> b.bpub_data = date (2022,11,11) >>> b.save()
注意我之前把 date 拼成了 data ,神智不清了一会。现在将错就错。
b.save()
之后才会将数据保存进数据库。
查询:
1 2 3 4 5 6 7 8 >>> from booktest.models import BookInfo >>> b = BookInfo.objects.get(id =1) >>> b <BookInfo: BookInfo object (1)> >>> b.btitle '转生成为雷电将军然后天下无敌' >>> b.bpub_data datetime.date(2022, 11, 11)
修改:
1 2 3 >>> from datetime import date >>> b.bpub_data = date (1999,9,9) >>> b.save()
刷新,可以看到数据已经被修改。
删除:
1 2 >>> b.delete() (1, {'booktest.BookInfo' : 1})
刷新,可以看到数据已经被删除。
现在,在 models.py
中设计一个新的类:
1 2 3 4 5 6 7 class HeroInfo (models.Model): hname = models.CharField(max_length=20 ) hgender = models.BooleanField(default=False ) hcomment = models.CharField(max_length=100 ) hbook = models.ForeignKey('BookInfo' ,on_delete=models.CASCADE,)
执行:
1 2 python3 manage.py makemigrations python3 manage.py migrate
可以看到生成了一张新表。
重新在 BookInfo 中插入一条数据,可以看到 id =2 ,过程略。
在 heroinfo 中插入数据,并尝试关联两张表:
1 2 3 4 5 6 7 >>> from booktest.models import HeroInfo >>> h = HeroInfo() >>> h.hname = '雷电将军' >>> h.hcomment = '梦想一心' >>> b = BookInfo.objects.get(id = 2) >>> h.hbook = b >>> h.save()
查询操作:
1 2 3 4 5 6 >>> HeroInfo.objects.all() <QuerySet [<HeroInfo: HeroInfo object (1)>, <HeroInfo: HeroInfo object (2)>]> >>> HeroInfo.objects.all()[0] <HeroInfo: HeroInfo object (1)> >>> HeroInfo.objects.all()[0].hname '雷电将军'
关联操作 省流:由一查多,由多查一。
目前的数据库:
查询:
1 2 >>> h.hbook.btitle '三哼经'
1 2 3 4 5 6 7 8 9 10 >>> b = BookInfo.objects.get(btitle = '转生成为雷电将军然后天下无敌' ... ) >>> b <BookInfo: BookInfo object (2)> >>> b.bpub_data datetime.date(2022, 11, 11) >>> b.heroinfo_set.all() <QuerySet [<HeroInfo: HeroInfo object (1)>, <HeroInfo: HeroInfo object (2)>]> >>> b.heroinfo_set.all()[0].hname '雷电将军'
后台管理 假设我们要设计一个新闻网站,我们需要编写展示给用户的页面,网页上展示的新闻信息是从哪里来的呢?是从数据库中查找到新闻的信息,然后把它展示在页面上 。但是我们的网站上的新闻每天都要更新,这就意味着对数据库的增、删、改、查操作,那么我们需要每天写 sql 语句操作数据库吗? 这样会非常繁琐,所以我们可以设计一个页面,通过对这个页面的操作来实现对新闻数据库的增删改查。那么问题来了,老板说我们需要在建立一个新网站,是不是还要设计一个页面来实现对新网站数据库的增删改查操作?但是这样的页面有很大的重复性,那有没有一种方法能够很快生成管理数据库表的页面呢?有,那就是 Django 的后台管理 。Django 能够根据定义的模型类自动地生成管理页面。使用 Django 的管理模块,需要按照如下步骤操作:
管理界面本地化
创建管理员
注册模型类
自定义管理页面
本地化 ,打开 day1010/settings.py ,找到语言编码、时区的设置项,将内容改为如下:
1 2 3 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
创建管理员:
1 python3 manage.py createsuperuser
根据提示操作即可。
启动 server:
1 python3 manage.py runserver
在浏览器中进入:
1 http://127.0.0.1:8000/admin/
注册模型类: 登录后台管理后,默认没有我们创建的应用中定义的模型类,需要在自己应用中的 admin.py 文件中注册,才可以在后台管理中看到,并进行增删改查操作。
在 booktest/admin.py 中,编写代码:
1 2 3 4 5 6 7 8 from django.contrib import adminfrom booktest.models import BookInfo,HeroInfoadmin.site.register(BookInfo) admin.site.register(HeroInfo)
到浏览器中刷新页面,可以看到模型类 BookInfo 和 HeroInfo 的管理了。
出现如下问题:
为什么没有直接显示书名呢?因为是 str(object) 的返回值。我们可以重写 str 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.db import modelsclass BookInfo (models.Model): btitle = models.CharField(max_length= 20 ) bpub_data = models.DateField() def __str__ (self ) -> str : return self.btitle class HeroInfo (models.Model): hname = models.CharField(max_length=20 ) hgender = models.BooleanField(default=False ) hcomment = models.CharField(max_length=100 ) hbook = models.ForeignKey('BookInfo' ,on_delete=models.CASCADE,) def __str__ (self ) -> str : return self.hname
在列表页只显示出了 BookInfo object,对象的其它属性并没有列出来,查看非常不方便。Django 提供了自定义管理页面的功能 ,比如列表页要显示哪些值。
修改 booktest/admin.py 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from django.contrib import adminfrom booktest.models import BookInfo,HeroInfoclass BookInfoAdmin (admin.ModelAdmin): list_display = ['id' , 'btitle' , 'bpub_data' ] class HeroInfoAdmin (admin.ModelAdmin): list_display = ['id' , 'hname' , 'hgender' , 'hcomment' ] admin.site.register(BookInfo, BookInfoAdmin) admin.site.register(HeroInfo, HeroInfoAdmin)
刷新,发现功能实现。
视图 什么是视图: 一个 url 首先到达路由(这里的“路由”和计算机网络中的“路由”概念不同),路由会分配到对应的视图函数,不同的网址路由会分配到不同的视图函数。
定义视图函数 视图就是一个 Python 函数,被定义在 views.py 中。
打开 booktest/views.py 文件,定义视图 index 如下:
1 2 3 4 5 6 7 8 from django.shortcuts import renderfrom django.http import HttpResponsedef index (request ): return HttpResponse('hello python' )
url 配置语法 一个简单的示例:
1 2 3 4 5 6 7 8 9 from django.shortcuts import renderfrom django.http import HttpResponsedef index (request ): return HttpResponse('hello python' )
1 2 3 4 5 6 7 8 9 from django.contrib import adminfrom django.urls import pathfrom booktest.views import indexurlpatterns = [ path('admin/' , admin.site.urls), path('' , index) ]
回到主页,浏览器显示 hello python
。
模板 模板不仅仅是一个 html 文件。
模板文件的使用 创建模板文件夹,名字为 templates,与 booktest 在同一个路径级别。
在 settings.py
中,添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TEMPLATES = [ { 'BACKEND' : 'django.template.backends.django.DjangoTemplates' , 'DIRS' : [BASE_DIR / 'templates' ], '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' , ], }, }, ]
在 templates 下新建一个 index.html,并写入如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 这是一个模板文件</h1 > </body > </html >
对 views.py
和 urls.py
作如下改动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.shortcuts import renderfrom django.http import HttpResponsefrom django.template import loader,RequestContextdef index (request ): return myrender(request, 'index.html' ) def index2 (request ): return HttpResponse('hello python' ) def myrender (request, template_path, context_dict={} ): temp = loader.get_template(template_path) res_html = temp.render(context_dict) return HttpResponse(res_html)
1 2 3 4 5 6 7 8 9 10 from django.contrib import adminfrom django.urls import pathfrom booktest import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), ]
刷新页面,可以看到效果。
现在,对 index.html
作如下修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 这是一个模板文件</h1 > 使用模板变量:<br > {{ content }}<br > </body > </html >
其实我们是不需要 myrender 的,上面写 myrender 的目的是为了理解 render 帮我们做了什么。我们可以直接改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from django.shortcuts import renderfrom django.http import HttpResponsedef index (request ): return render(request, 'index.html' , {'content' :'hello world' }) def index2 (request ): return HttpResponse('hello python' )
效果:
模板文件进阶用法 下面实现了一个 for 循环,直接看例子,简洁明了。
1 2 3 4 5 6 7 8 urlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), ]
1 2 3 4 5 def show_books (request ): books = BookInfo.objects.all () return render(request, 'showbooks.html' , {'books' : books})
在 templates 文件夹下新增 showbooks.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示图书信息</title > </head > <body > <ul > {% for book in books %} <li > {{ book.btitle }}</li > {% endfor %} </ul > </body > </html >
效果:
现在,我们尝试实现更高级的功能:把这两个文本做成超链接,点开之后可以查看详情。
为了做成超链接,首先要修改 showbooks.html
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示图书信息</title > </head > <body > <ul > {% for book in books %} <li > <a href ="/books/{{ book.id }}" > {{ book.btitle }} </a > </li > {% endfor %} </ul > </body > </html >
然后,在 urls.py
中增加路由信息:
1 2 3 4 5 6 7 8 urlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' ,views.detail) ]
我们将 detail 函数设计为:
1 2 3 4 5 6 def detail (request, bid ): book = BookInfo.objects.get(id = bid) heros = book.heroinfo_set.all () return render(request, 'detail.html' , {'book' :book, 'heros' :heros})
最后,detail.html
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 图书详情</title > </head > <body > <h1 > {{ book.btitle }}</h1 > 英雄信息如下: <br /> <ul > {% for hero in heros %} <li > {{hero.hname}} --- {{hero.hcomment}} </li > {% empty %} <li > 没有英雄信息 </li > {% endfor %} </ul > </body > </html >
效果如下:
{% empty %}
有什么用?我们删除”应龙“的数据,然后点击”三哼经“的链接,就会显示”没有英雄信息“。
数据库配置 现在我们将 sqlite 切换为 mysql.
在 settings.py 中修改:
1 2 3 4 5 6 7 8 9 10 11 12 DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'test2' , 'USER' : 'root' , 'PASSWORD' : 'jtsws' , 'HOST' : 'localhost' , 'PORT' : 3306 , } }
接下来安装(我不确定这两个是否有用,有可能你只需要执行接下来的一个命令):
1 2 sudo apt install libssl-dev sudo apt install libmysqlclient-dev
执行:
1 sudo apt-get install pkg-config python3-dev default-libmysqlclient-dev build-essential
然后:
相关讨论参见:https://stackoverflow.com/questions/76585758/mysqlclient-cannot-install-via-pip-cannot-find-pkg-config-name-in-ubuntu
生成迁移文件、执行迁移:
1 2 python3 manage.py makemigrations python3 manage.py migrate
我们可以看到,test2(原本为空)中多出了很多 table :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mysql> show tables; +----------------------------+ | Tables_in_test2 | +----------------------------+ | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | booktest_bookinfo | | booktest_heroinfo | | django_admin_log | | django_content_type | | django_migrations | | django_session | +----------------------------+ 12 rows in set (0.00 sec) mysql> select * from booktest_bookinfo -> ; Empty set (0.00 sec)
为了演示方便,我们接下来仍然使用 sqlite.
更多细节的演示 btw, 不使用 SQLite Viewer 了,使用更强大的 vscode 插件 SQLite3 Editor(by yy0931).
实现效果:
点击“新增”会新增一本指定的书《C 语言开发宝典》(同时数据库中也删除);点击书名背后的删除会删除该书(同时数据库中也删除)。
models.py
更新为:
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 from django.db import modelsclass BookInfo (models.Model): btitle = models.CharField(max_length= 20 ) bpub_data = models.DateField() bread = models.IntegerField(default=0 ) bprice = models.DecimalField(max_digits=10 , decimal_places=2 , default=0 , null=True ) bcomment = models.IntegerField(default=0 ) isDelete = models.BooleanField(default=False ) def __str__ (self ) -> str : return self.btitle class HeroInfo (models.Model): hname = models.CharField(max_length=20 ) hgender = models.BooleanField(default=False ) hcomment = models.CharField(max_length=100 ) isDelete = models.BooleanField(default=False ) hbook = models.ForeignKey('BookInfo' ,on_delete=models.CASCADE,) def __str__ (self ) -> str : return self.hname
修改 models 文件后需要重新 migrate.
修改 showbooks.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示图书信息</title > </head > <body > <a href ="/create" > 新增</a > <ul > {% for book in books %} <li > <a href ="/books/{{ book.id }}" > {{ book.btitle }} </a > ---<a href ="/delete{{ book.id }}" > 删除</a > </li > {% endfor %} </ul > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.contrib import adminfrom django.urls import pathfrom booktest import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' , views.detail), path('create/' , views.create), path('delete<int:bid>' , views.delete), ]
最后,views.py
中也要添加相应的逻辑:
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 from django.shortcuts import renderfrom django.http import HttpResponse, HttpResponseRedirectfrom booktest.models import BookInfofrom datetime import datedef index (request ): return render(request, 'index.html' , {'content' :'hello world' }) def index2 (request ): return HttpResponse('hello python' ) def show_books (request ): books = BookInfo.objects.all () return render(request, 'showbooks.html' , {'books' : books}) def detail (request, bid ): book = BookInfo.objects.get(id = bid) heros = book.heroinfo_set.all () return render(request, 'detail.html' , {'book' :book, 'heros' :heros}) def create (request ): '''新增一本图书''' b = BookInfo() b.btitle = 'C 语言开发宝典' b.bpub_data = date(2019 ,10 ,1 ) b.save() return HttpResponseRedirect('/books' ) def delete (request, bid ): '''删除点击的图书''' book = BookInfo.objects.get(id = bid) book.delete() return HttpResponseRedirect('/books' )
字段属性和选项 模型类属性命名限制
不能是 python 的保留关键字。
不允许使用连续的下划线,这是由 django 的查询方式决定的。比如 Books__Info
是不可以的。
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:属性名=models.字段类型(选项)
字段类型 官方文档:https://docs.djangoproject.com/en/4.2/ref/models/fields/#field-types
使用时需要引入 django.db.models 包,几个常用的如下:
AutoField
自动增长的 IntegerField,通常不用指定,不指定时 Django 会自动创建属性名为 id 的自动增长属性。
BooleanField
布尔字段,值为 True 或 False。
NullBooleanField
支持 Null、True、False 三种值。
CharField(max_length=最大长度)
字符串。参数 max_length 表示最大字符个数。
TextField
大文本字段,一般超过 4000 个字符时使用。
IntegerField
整数。
DecimalField(max_digits=None,decimal_places=None)
十进制浮点数。参数 max_digits 表示总位。参数 decimal_places 表示小数位数。(精度较高,建议用这个)
FloatField
浮点数。参数同上(精度不够)。
DateField
、TimeField
、DateTimeField
FileField
上传文件字段。
ImageField
继承于 FileField,对上传的内容进行校验,确保是有效的图片。
选项 通过选项实现对字段的约束。
官网可查:https://docs.djangoproject.com/en/4.2/ref/models/fields/
对比: null 是数据库范畴的概念,blank 是后台管理页面表单验证范畴的。
查询 查询函数 通过 模型类.objects
属性可以调用如下函数,实现对模型类对应的数据表的查询。
get
函数:
返回表中满足条件的一条且只能有一条 数据
返回值是一个模型类对象
参数中写查询条件
如果查到多条数据,则抛异常 MultipleObjectsReturned
查询不到数据,则抛异常 DoesNotExist
all
函数:
返回模型类对应表格中的所有数据
返回值是 QuerySet 类型
查询集,可以拿出来进行遍历
filter
函数:
返回满足条件的数据
返回值是QuerySet类型
参数写查询条件
exclude
函数:
order_by
函数:
对查询结果进行排序
返回值是 QuerySet
参数中写根据哪些字段进行排序
下面展示一些例子:
1 2 3 4 >>> from booktest.models import * >>> c = BookInfo.objects.filter(bcomment=34) >>> c <QuerySet [<BookInfo: 射雕英雄传>]>
模糊查询:
1 2 3 4 5 6 7 >>> b = BookInfo.objects.filter(btitle__contains='传' ) >>> b <QuerySet [<BookInfo: 射雕英雄传>]> >>> b = BookInfo.objects.filter(btitle__endswith='部' ) >>> b <QuerySet [<BookInfo: 天龙八部>]>
空查询:
1 2 3 4 5 >>> b= BookInfo.objects.filter(bprice__isnull=False) >>> b <QuerySet [<BookInfo: C 语言开发宝典>]>
范围查询:
1 2 3 >>> b = BookInfo.objects.filter(id__in = [1,3,5]) >>> b <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 笑傲江湖>, <BookInfo: C 语言开发宝典>]>
比较查询:
1 2 3 >>> b = BookInfo.objects.filter(id__gt=3) >>> b <QuerySet [<BookInfo: 雪山飞狐>, <BookInfo: C 语言开发宝典>]>
F 对象 作用:用于对象属性之间的比较。
1 2 3 4 >>> from django.db.models import F >>> from booktest.models import * >>> BookInfo.objects.filter(bread__gt=F('bcomment' )) <QuerySet [<BookInfo: 雪山飞狐>]>
1 2 >>> BookInfo.objects.filter(bread__gt=F('bcomment' )*2) <QuerySet [<BookInfo: 雪山飞狐>]>
没有问题:
Q 对象 作用:用于查询时条件之间的逻辑关系。not、and、or,可以对 Q 对象进行 &|~
操作(和 C 语言对应的运算符)。
我们改变一下演示的方式。
1 2 3 4 5 6 7 8 from django.db.models import F,Qdef index2 (request ): print (BookInfo.objects.filter (Q(id__gt=2 ) & Q(bread__gt=19 ))) return HttpResponse('hello python' )
访问 /index2 , 我们可以在终端中的一堆打印信息中看到:
1 <QuerySet [<BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>]>
聚合函数 作用:对查询结果进行聚合操作。
我们在 views.py
中演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from django.db.models import Sum,Count,Max,Min,Avgdef use_aggregate (request ): print (BookInfo.objects.all ().aggregate(Count('id' ))) print (BookInfo.objects.aggregate(Sum('bread' ))) print (BookInfo.objects.all ().count()) print (BookInfo.objects.count()) print (BookInfo.objects.filter (id__gt=3 ).count()) return HttpResponse('ok' )
同时 urls.py
中添加路由:
1 2 3 4 5 6 7 8 9 10 11 urlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' , views.detail), path('create/' , views.create), path('delete<int:bid>' , views.delete), path('aggregate/' ,views.use_aggregate), ]
页面 http://127.0.0.1:8000/aggregate/
返回 ok;终端中看到:
1 2 3 4 5 {'id__count': 5} {'bread__sum': 126} 5 5 2
多对多关系 在 models.py
中添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class NewsType (models.Model): type_name = models.CharField(max_length=20 ) type_news = models.ManyToManyField('NewsInfo' ) class NewsInfo (models.Model): title = models.CharField(max_length=128 ) pub_date = models.DateTimeField(auto_now_add=True ) content = models.TextField()
多对多会生成三张表。
迁移之后,查看数据库:
建表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 >>> from booktest.models import * >>> n = NewsType() >>> n.type_name = 'IT' >>> n.save() >>> n = NewsType() >>> n.type_name = '考研' >>> n.save() >>> i = NewsInfo() >>> i.title = '408上热搜' >>> i.content = '计算机卷疯了' >>> i.save() >>> i = NewsInfo() >>> i.title = '金毛师王兴趣转移' >>> i.content = '将诺贝尔奖挂在嘴边' >>> i.save() >>> i = NewsInfo() >>> i.title = '神秘男子在武当山接引雷劫' >>> i.content = '雷击木附近出现神秘舍利子' >>> i.save()
在第三张表中添加多对多关系:
1 2 3 >>> n = NewsType.objects.get(id = 1) >>> n.type_news.add(2) >>> n.type_news.remove(2)
(现在第三张表为空)
第二种方法:
1 2 3 4 5 6 >>> i = NewsInfo.objects.filter(id__lt=3) >>> i <QuerySet [<NewsInfo: NewsInfo object (1)>, <NewsInfo: NewsInfo object (2)>]> >>> n.type_news.set(i)
关联查询
自关联
我们设计一个类:
1 2 3 4 5 6 7 8 9 class Areas (models.Model): '''地区模型类''' atitle = models.CharField(max_length=20 ) aParent = models.ForeignKey('self' , null=True ,blank=True ,on_delete=models.CASCADE,)
迁移之后,我们尝试在表中导入数据(一个 sql 文件 )。
安装 sqlite3 命令行工具:
1 sudo apt install sqlite3
进入虚拟环境,进入包含 db.sqlite3(即 django 自带的那个数据库) 的目录。
执行:
执行上面的命令后,会进入 SQLite 命令行中。
在 SQLite 命令行中,运行以下命令:
1 .read /home/zhiyue/Downloads/area.sql
可能会出现 “database is locked” 错误,因为 SQLite 数据库文件被锁定。这个时候可以多试几次上面的命令,会补全的。
一睹芳容:
上面这张图也可以帮助理解什么叫自关联。
编写模板页面 area.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!DOCTYPE html > <html lang ="en" > </html > <head > <meta charset ="UTF-8" > <title > 自关联案例</title > </head > <body > <h1 > 当前地区</h1 > {{ area.atitle }}<br /> <h1 > 父级地区</h1 > {{ parent.atitle }}<br /> <h1 > 下级地址</h1 > <ul > {% for child in children %} <li > {{ child.atitle }}</li > {% endfor %} </ul > </body > </html >
编写 views 函数:
1 2 3 4 5 6 7 8 9 10 def areas (request ): '''获取广州市的上级地区和下级地区''' area = Areas.objects.get(atitle='广州市' ) parent = area.aParent children = area.areas_set.all () return render(request, 'area.html' , {'area' :area,'parent' :parent, 'children' :children})
最后,配置 urls.
效果:
管理器 BookInfo.objects.all()->objects
是一个什么东西呢?
objects 是 Django 帮我自动生成的管理器对象,通过这个管理器可以实现对数据的查询。
objects 是 models.Manger
类的一个对象,是 models.Model
的一个属性。
自定义管理器之后 Django 不再帮我们生成默认的 objects 管理器。
下面我们尝试自定义模型管理器类 。
需求 : 我们要实现软删除。并非在数据库中删除数据,而是将 isDelete 字段设为 1 ,这样的字段不会在 all 查询中被查询到。
在 models.py
中新增管理器类,然后在 BookInfo 类中重写 objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class BookInfoManager (models.Manager): '''图书模型管理器类''' def all (self ): books = super ().all () books = books.filter (isDelete=False ) return books class BookInfo (models.Model): objects = BookInfoManager()
我们将《C语言开发宝典》的 isDelete 设为 1,然后执行:
1 2 3 >>> from booktest.models import * >>> BookInfo.objects.all() <QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>]>
需求 : 我们希望改进新增数据的方法,之前在命令行里一个一个敲非常麻烦。
这里我直接更新一版 models.py
,新增的功能在里面了:
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 from django.db import modelsclass BookInfoManager (models.Manager): '''图书模型管理器类''' def all (self ): books = super ().all () books = books.filter (isDelete=False ) return books def create_book (self, btitle, bpub_data ): '''添加一本图书''' model_class = self.model book = model_class() book.btitle = btitle book.bpub_data = bpub_data book.save() return book class BookInfo (models.Model): btitle = models.CharField(max_length= 20 ) bpub_data = models.DateField() bread = models.IntegerField(default=0 ) bprice = models.DecimalField(max_digits=10 , decimal_places=2 , default=0 , null=True ) bcomment = models.IntegerField(default=0 ) isDelete = models.BooleanField(default=False ) objects = BookInfoManager() def __str__ (self ) -> str : return self.btitle class HeroInfo (models.Model): hname = models.CharField(max_length=20 ) hgender = models.BooleanField(default=False ) hcomment = models.CharField(max_length=100 ) isDelete = models.BooleanField(default=False ) hbook = models.ForeignKey('BookInfo' ,on_delete=models.CASCADE,) def __str__ (self ) -> str : return self.hname class NewsType (models.Model): type_name = models.CharField(max_length=20 ) type_news = models.ManyToManyField('NewsInfo' ) def __str__ (self ): return self.type_name class NewsInfo (models.Model): title = models.CharField(max_length=128 ) pub_date = models.DateTimeField(auto_now_add=True ) content = models.TextField() class Areas (models.Model): '''地区模型类''' atitle = models.CharField(max_length=20 ) aParent = models.ForeignKey('self' , null=True ,blank=True ,on_delete=models.CASCADE,)
新增功能之后,可以:
1 2 3 4 >>> from booktest.models import * >>> from datetime import date >>> BookInfo.objects.create_book('日月前事', date(2019, 1, 1)) <BookInfo: 日月前事>
在数据库中,可以看到这本书已经添加了进去。
元选项 Django 默认生成的表名: 应用名小写_模型类名小写
。
元选项可以更改表名。
例如,我们在 BookInfo 模型类中 增加如下代码:
1 2 3 4 class BookInfo (models.Model): class Meta : db_table = 'bookinfo'
404 页面 我们设计一个 404 页面。需要在 settings.py
中把 DEBUG 改为 FALSE. 然后:
404 页面设计如下:
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 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 404 - 找不到页面</title > <style > body { background-image : url ('http://img.netbian.com/file/2017/0326/64aab4ae3e632dbcbf9223995c654317.jpg' ); background-size : cover; color : white; font-family : 'Arial' , sans-serif; text-align : center; padding : 200px ; } h1 { font-size : 3em ; } p { font-size : 1.5em ; } a { color : #ffcc00 ; text-decoration : none; font-weight : bold; } </style > </head > <body > <h1 > 404 页面未找到</h1 > <p > 找不到页面 {{ request_path }}</p > <p > <a href ="/" > 返回主页</a > </p > </body > </html >
我们仍回到 DEBUG 模式。
捕获 url 参数 官方文档:https://docs.djangoproject.com/en/4.2/topics/http/urls/
例子:
1 2 3 4 5 6 7 8 9 10 from django.urls import pathfrom . import viewsurlpatterns = [ path("articles/2003/" , views.special_case_2003), path("articles/<int:year>/" , views.year_archive), path("articles/<int:year>/<int:month>/" , views.month_archive), path("articles/<int:year>/<int:month>/<slug:slug>/" , views.article_detail), ]
官方文档里写的很清楚了,不再搬运。
设计登录页面 实战 设计一个 login.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > login page</title > </head > <body > <form method ="post" action ="/login_check/" > 用户名:<input type ="text" name ="username" value ="{{ username }}" > <br /> 密码:<input type ="password" name ="password" > <br /> <input type ="checkbox" name ="remember" > 记住用户名<br /> <input type ="submit" value ="登录" > </form > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 urlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' , views.detail), path('create/' , views.create), path('delete<int:bid>' , views.delete), path('aggregate/' ,views.use_aggregate), path('areas/' , views.areas), path('login/' , views.login), path('login_check/' , views.login_check), ]
1 2 3 4 5 6 def login (request ): return render(request, 'login.html' ) def login_check (request ): pass
填写数据后点击登录按钮:
避免 CSRF 报错的方法是注释掉 settings 中的校验 。
1 2 3 4 5 6 7 8 9 10 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , ]
HttpReqeust 对象 服务器接收到 http 协议的请求后,会根据报文创建 HttpRequest 对象,这个对象不需要我们创建,直接使用服务器构造好的对象就可以。视图的第一个参数必须是 HttpRequest 对象 ,在 django.http
模块中定义了 HttpRequest 对象的API。
属性 :
path : 一个字符串,表示请求的页面的完整路径,不包含域名和参数部分
method : 一个字符串,表示请求使用的 HTTP 方法,常用值包括:GET
、POST
、PUT
、DETELE
在浏览器中发出地址请求 采用 get 方式,如超链接
在浏览器中点击表单的提交按钮发起请求,如果表单的 method 设置为 post, 则为 post 请求
encoding: 一个字符串,表示提交的数据的编码方式
GET : QueryDict 类型对象,类似于字典,包含 get 请求方式的所有参数
POST : QueryDict 类型对象,类似于字典,包含 post 请求方式的所有参数
FILES: 一个类似于字典的对象,包含所有的上传文件
COOKIES: 一个标准的 Python 字典,包含所有的 cookie,键和值都为字符串
session: 一个既可读又可写的类似于字典的对象,表示当前的会话,只有当 Django 启用会话的支持时才可用,详细内容见”状态保持”
关于 GET 和 POST 的更多细节:
用调试模式演示一下。打上断点,在浏览器输入:
1 http://127.0.0.1:8000/login/?a=10&b=20&c=python
(实际上 ?
前面的 /
可能要去掉?它好像是之后生成的,存疑)
我们可以看到 GET 拿到了这些数据:
HttpResponse 对象 视图在接收请求并处理后,必须返回 HttpResponse 对象或子对象。
属性 :
content:表示返回的内容
charset:表示 response 采用的编码字符集,默认为 utf-8
status_code:返回的 HTTP 响应状态码
content-type:指定返回数据的的 MIME 类型,默认为’text/html’
实战(续) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def login (request ): return render(request, 'login.html' ) def login_check (request ): '''登录校验视图''' username=request.POST.get('username' ) password=request.POST.get('password' ) print (username+':' +password) return HttpResponse('ok' )
在前端点击提交按钮后,后端的终端显示:
1 2 3 asdf:asdf # 这是我设置的用户名和密码
演示一个简单的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def login_check (request ): '''登录校验视图''' username=request.POST.get('username' ) password=request.POST.get('password' ) if username == 'akashi' and password == '123' : return HttpResponseRedirect('/index' ) else : return HttpResponseRedirect('/login' )
Ajax 基本概念 异步的 javascript。在不全部加载某一个页面的情况下,对页面进行局部的刷新 ,ajax 请求都在后台。
图片,css 文件,js 文件都是静态文件。
大致流程:
发起 ajax 请求:jquery(某个老旧的前端框架) 发起
执行相应的视图函数,返回 json 内容
执行相应的回调函数。通过判断 json 内容,进行相应处理。
简单示例 在 templates 文件夹下新建 test_ajax.html
:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > ajax 页面</title > <script src ="/static/js/jquery-1.12.4.min.js" > </script > <script > $(function ( ) { $('#btnAjax' ).click (function ( ) { $.ajax ({ 'url' : '/ajax_handle' , 'dataType' : 'json' , 'async' : false , }).success (function (data ) { if (data.res == 1 ){ $('#message' ).show ().html ('提示信息' ) } }) }) }) </script > <style > #message { display : none; color : red; } </style > </head > <body > <input type ="button" id ="btnAjax" value ="ajax 请求" > <div id ="message" > </div > </body > </html >
建立 templates 的同级文件夹 static, 下面再设 js 文件夹。将 jquery-1.12.4.min.js 放入其中。
在 views.py
中新增:
1 2 3 def test_ajax (request ): '''显示 ajax 页面''' return render(request, 'test_ajax.html' )
同时在 urls.py
中配置好路由。
在 settings 中添加:
1 STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static' )]
这时运行,会提示 ajax_handle 404 Not Found,合理。
在 views.py
中新增:
1 2 3 4 5 6 from django.http import HttpResponse, HttpResponseRedirect, JsonResponsedef ajax_handle (request ): '''ajax 请求处理''' return JsonResponse({'res' :1 })
然后新增路由:
1 path('ajax_handle/' , views.ajax_handle)
运行,在点击按钮后,页面不加载的情况下,按钮下方多出了红色的字“提示信息”。
关于:
1 'async': false, // 同步的 ajax 请求
这句代码,打开这个开关,在 html 文件中打开调试代码的注释,再运行。可以看到弹出窗口的消息提示顺序从“123”变成了“132”。
Ajax 登录案例 尝试用 ajax 做一个登录:若用户输入错误,则不刷新页面,提示错误。
增加路由:
1 path('login_ajax' , views.login_ajax),
设计视图:
1 2 def login_ajax (request ): return render(request, 'login_ajax.html' )
设计 html:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > ajax 登录</title > <script src ="/static/js/jquery-1.12.4.min.js" > </script > <script > $(function ( ) { $('#btnLogin' ).click (function ( ) { username = $('#username' ).val () password = $('#password' ).val () $.ajax ({ 'url' :'/login_ajax_check/' , 'type' : 'post' , 'data' : {'username' :username,'password' :password}, 'dataType' : 'json' }).success (function (data ) { if (data.res == 0 ){ $('#errmsg' ).show ().html (' 用 户 名 或 密码错误' ) } else { location.href = '/index' } }) }) }) </script > <style > #errmsg { display : none; color : red; } </style > </head > <body > <div > 用户名:<input type ="text" id ="username" > <br /> 密码:<input type ="password" id ="password" > <br /> <input type ="button" id ="btnLogin" value ="登录" > <div id ="errmsg" > </div > </div > </body > </html >
设计登录校验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def login_ajax_check (request ): '''ajax 登录校验''' username = request.POST.get('username' ) password = request.POST.get('password' ) if username == 'admin' and password == '123' : return JsonResponse({'res' :1 }) else : return JsonResponse({'res' :0 })
不用忘了配置路由:
1 path('login_ajax_check/' , views.login_ajax_check),
运行,成功。
状态保持 http 协议是无状态 的。下一次去访问一个页面时并不知道上一次对这个页面做了什么。
Cookie 基本知识
cookie 的特点:
以键值对 方式进行存储。
通过浏览器访问一个网站时,会将浏览器存储的跟网站相关的所有 cookie 信息 发送给该网站的服务器。request.COOKIES
cookie 是基于域名安全 的。
cookie 是有过期时间 的,如果不指定,默认关闭浏览器之后 cookie 就会过期。
典型应用:记住用户名,网站的广告推送。
说明:这些广告推送的商品是基于你曾经在淘宝上点击的商品类别等条件筛选出来的,看上去这是在凤凰网上访问淘宝网的 Cookie,但是事实不是这样的,一般是采用 iframe 标签嵌套一个淘宝的广告页面到凤凰网的页面上,所以淘宝的 Cookie 并没有被凤凰网读取到,而是依然交给淘宝网读取的,可以通过”开发者工具”查看元素,如下图:
Cookie 基本使用 配置路由:
1 path('set_cookie/' , views.set_cookie),
在 views.py
中:
1 2 3 4 5 6 7 8 9 10 11 12 from datetime import datetime,timedeltadef set_cookie (request ): '''设置 cookie 信息''' response = HttpResponse('设置 cookie' ) response.set_cookie('num' , 2 ) return response
运行,访问 /set_cookie
页面之后,再去该网站的其他页面,都可以在 F12 中看到多了我们自己添加的 cookie.
获取 cookie:
1 2 3 4 5 6 7 def get_cookie (request ): '''获取 cookie 的信息''' num = request.COOKIES['num' ] print (num) return HttpResponse(num)
同时配置路由:
1 path('get_cookie/' , views.get_cookie),
记住用户名案例 如果在前面的 login.html
我们勾选了记住用户名,那么如何实现下次 login 的时候,用户名在里边呢?
在 views.py
中对原来的函数作如下修改:
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 def login (request ): if 'username' in request.COOKIES: username = request.COOKIES['username' ] else : username = '' return render(request, 'login.html' , {'username' :username}) def login_check (request ): '''登录校验视图''' username=request.POST.get('username' ) password=request.POST.get('password' ) remember = request.POST.get('remember' ) if username == 'akashi' and password == '123' : resp = HttpResponseRedirect('/index' ) if remember == 'on' : resp.set_cookie('username' , username, max_age=7 *24 *3600 ) return resp else : return HttpResponseRedirect('/login' )
运行,可以发现实现效果。
Session 基本介绍
session 存储在服务器端。在服务器端进行状态保持的方案就是 Session .
session 的特点 :
session 是以键值对 进行存储的。
session 依赖于 cookie。唯一的标识码 sessionid 保存在 cookie 中。
session 也是有过期时间,如果不指定,默认两周就会过期。
session 与 cookie 的差异,cookie 无论保存什么值进去,取出来都是字符串,session 保存进去什么类型,取出来就是什么类型。
Django 项目默认启用 Session,可以在 MIDDLEWARE 配置里找到。
设置 SESSION_ENGINE 项,指定 Session 数据存储的方式,可以存储在数据库、django 的缓存、Redis 等。
默认存储方式 ,存储在数据库中,如下设置可以写,也可以不写:
1 SESSION_ENGINE='django.contrib.sessions.backends.db'
存储在缓存中 :
1 SESSION_ENGINE='django.contrib.sessions.backends.cache'
混合存储 ,优先从本机内存中存取,如果没有则从数据库中存取:
1 SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
如果存储在数据库中,需要在项 INSTALLED_APPS 中安装 Session 应用。
依赖于 Cookie 在使用 Session 后,会在 Cookie 中存储一个 sessionid 的数据,每次请求时浏览器都会将这个数据发给服务器,服务器在接收到 sessionid 后,会根据这个值找出这个请求者的 Session。
存储 Session 时,键与 Cookie 中的 sessionid 相同,值是开发人员设置的键值对信息,进行了 base64 编码,过期时间由开发人员设置。
实战 1 2 3 4 5 6 7 8 9 10 11 12 13 def set_session (request ): '''设置 session''' request.session['username' ] = 'yomiya' request.session['age' ] = 17 return HttpResponse('设置 session' ) def get_session (request ): '''获取 session''' username = request.session['username' ] age = request.session['age' ] return HttpResponse(username+':' +str (age))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 urlpatterns = [ path('admin/' , admin.site.urls), path('index/' , views.index), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' , views.detail), path('create/' , views.create), path('delete<int:bid>' , views.delete), path('aggregate/' ,views.use_aggregate), path('areas/' , views.areas), path('login/' , views.login), path('login_check/' , views.login_check), path('test_ajax/' , views.test_ajax), path('ajax_handle/' , views.ajax_handle), path('login_ajax/' , views.login_ajax), path('login_ajax_check/' , views.login_ajax_check), path('set_cookie/' , views.set_cookie), path('get_cookie/' , views.get_cookie), path('set_session/' , views.set_session), path('get_session/' , views.get_session), ]
访问 /set_session , 在 F12 中可以看到:
1 sessionid=xsjm51tmhguylsknjf53nvvzsf83hgui;
与数据库中的一致,同时数据库中 session_data 字段:
1 .eJxVjDsOgzAQRO_iOrLW2HhJyvQ5A1rWayAfW-JToCh3j5EoknJm3ry3amldhnadZWrHoC7KqNNv1xE_JO1DuFPqs-aclmns9I7oY531LQd5Xg_2TzDQPJQ3NghWjDccHfuavQC4M9mGEDxLtAiurkMHleFKIDj0ZCMRVIwSyRbprkv0kmLb8mvcqHTUl2jw8wWmp0Dd:1t1I46:CMmhgMpr67_q-bZvtptryM6HddKavAQEW83ZsPD4TQI
访问 /get_session, 浏览器显示:
清除 session:
记住用户登录状态案例 需求:已登录的用户在访问 /login 时,直接访问首页,不需要再输入用户名和密码。
修改 views.py
中的 login、login_check 函数:
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 def login (request ): if request.session.has_key('islogin' ): return HttpResponseRedirect('/index' ) if 'username' in request.COOKIES: username = request.COOKIES['username' ] else : username = '' return render(request, 'login.html' , {'username' :username}) def login_check (request ): '''登录校验视图''' username=request.POST.get('username' ) password=request.POST.get('password' ) remember = request.POST.get('remember' ) if username == 'akashi' and password == '123' : resp = HttpResponseRedirect('/index' ) request.session['islogin' ] = True if remember == 'on' : resp.set_cookie('username' , username, max_age=7 *24 *3600 ) return resp else : return HttpResponseRedirect('/login' )
cookie 和 session 的应用场景 cookie: 记住用户名。安全性要求不高 。
session: 涉及到安全性要求比较高 的数据。如用户名、余额、等级、验证码等。
深度延伸,如果用户禁用 cookie,如何使用 session :https://www.cnblogs.com/ceceliahappycoding/p/10544075.html
模板进阶 模板变量 1 2 3 4 5 6 7 8 9 10 def test_var (request ): '''模板变量''' my_dict = {'title' :'字典键值' } my_list = [1 ,2 ,3 ] book = BookInfo.objects.get(id =1 ) context = {'my_dict' :my_dict, 'my_list' :my_list, 'book' :book} return render(request, 'test_var.html' , context)
templates 文件夹下 test_var.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 学习模板变量</title > </head > <body > 使用字典属性:{{ my_dict.title }}<br /> 使用列表元素:{{ my_list.1 }}<br /> 使用对象属性:{{ book.btitle }} </body > </html >
然后配置路由。转到浏览器对于路径可以看到效果。
模板标签 可以通过 {{ forloop.counter }}
得到 for 循环遍历到了第几次。
1 2 3 4 {% if 条件 %} {% elif 条件 %} {% else %} {% endif %}
注意:进行比较操作时,比较操作符两边必须有空格。
过滤器 格式:模板变量|过滤器:参数
在 templates 文件夹下新建 test_filters.html
:
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 <!DOCTYPE html > <html lang ="en" > {% load filters %} <head > <meta charset ="UTF-8" > <title > 模板过滤器</title > <style > .red { background-color : red; } .yellow { background-color : yellow; } .green { background-color : green; } </style > </head > <body > <ul > {% for book in books %} {% if book.id|mod %} <li class ="red" > {{ book.id }}--{{ book.btitle|length }}--{{ book.bpub_data|date:'Y 年-m 月-d 日' }}</li > {% else %} <li class ="green" > {{ book.btitle }}--{{ book.bpub_data }}</li > {% endif %} {% endfor %} </ul > default 过滤器:<br /> {{ content|default:'没有数据' }} </body > </html >
新建 templatetags/filters.py
文件,其中 templatetags 文件夹与 models.py
文件同级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from django.template import Libraryregister = Library() @register.filter def mod (num ): '''判断 num 是否为偶数''' return num%2 == 0 @register.filter def mod_val (num, val ): '''判断 num 是否能被 val 整除''' return num%val == 0
1 2 3 4 def test_filters (request ): books = BookInfo.objects.all () return render(request, 'test_filters.html' , {'books' :books})
配置路由。
注意配置自定义过滤器不会自动加载,必须重启 Django 服务 。
效果:
模板继承 模板继承是为了重用 html 页面内容 。比如很多网站的头部导航条和底部版权版权信息不变的。
在 templates 文件夹下创建 base.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > {% block title %}父模板文件{% endblock title %}</title > </head > <body > <h1 > 导航条</h1 > {% block b1 %} <h1 > 这是父模板 b1 块中的内容</h1 > {% endblock b1 %} {% block b2 %} <h1 > 这是父模板 b2 块中的内容</h1 > {% endblock b2 %} <h1 > 版权信息</h1 > </body > </html >
1 2 3 def test_template_inhert (request ): return render(request, "base.html" )
配置路由。
在 templates 文件夹下创建 child.html
:
1 2 3 4 5 6 7 8 9 10 11 {% extends 'base.html' %} {% block title %}子模板文件{% endblock title %} {% block b1 %} {{ block.super }} <h1>这是子模板 b1 块中的内容</h1> {% endblock b1 %} {% block b2 %} {{ block.super }} <h1>这是子模板 b2 块中的内容</h1> {% endblock b2 %}
1 2 3 4 def test_template_inhert (request ): return render(request, "child.html" )
运行即可对比前后效果。
html 转义 增加 html_escape.html
页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > html 转义</title > </head > <body > html 转义:<br /> {{ content }}<br /> 使用 safe 过滤器关闭转义:<br /> {{ content|safe }}<br /> 使用 autoescape 关闭转义:<br /> {% autoescape off %} {{ content }} {{ content }} {% endautoescape %}<br /> 模板硬编码中的字符串默认不会经过转义:<br /> {{ test|default:'<h1 > hello</h1 > ' }}<br /> 手动进行转义:<br /> {{ test|default:'< h1> hello< /h1> ' }} </body > </html >
增加视图函数 html_escape:
1 2 3 4 5 def html_escape (request ): '''html 转义''' return render(request, 'html_escape.html' ,{'content' :'<h1>hello</h1>' })
效果:
csrf 攻击 CSRF 全拼为 Cross Site Request Forgery,译为跨站请求伪造。CSRF 指攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF 能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。
接下来我们演示这一攻击。
设计一个修改密码的页面 change_pwd.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 修改密码页面</title > </head > <body > <form method ="post" action ="/change_pwd_action/" > 新密码:<input type ="password" name ="pwd" > <input type ="submit" value ="确认修改" > </form > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 def change_pwd (request ): return render(request, 'change_pwd.html' ) def change_pwd_action (request ): '''模拟修改密码处理''' pwd = request.POST.get('pwd' ) return HttpResponse('修改密码为:%s' %pwd)
配置路由。
这个时候,我们修改密码,可以成功。
但是这个时候,假如有一个猥琐黑客,直接访问了 /change_pwd_action
,就可以通过一些手段修改我们的密码,怎么办?
答案是凉拌。
不过还好,Django 框架为我们提供了预防这种攻击的方法。
在 settings 中打开 CsrfViewMiddleware :
1 2 3 4 5 6 7 8 9 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.middleware.csrf.CsrfViewMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , ]
这个时候,再回到 /change_pwd
修改密码,就会被阻止。
我们把之前 change_pwd.html
的注释打开:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 修改密码页面</title > </head > <body > <form method ="post" action ="/change_pwd_action/" > {% csrf_token %} 新密码:<input type ="password" name ="pwd" > <input type ="submit" value ="确认修改" > </form > </body > </html >
这时候就可以成功修改了!
防御的大致原理是,只有在本页面发出的请求可以访问到 /change_pwd_action
。
验证码 在用户注册、登录页面,为了防止暴力请求 ,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。
安装 Pillow :
1 pip install Pillow --proxy="http://127.0.0.1:7897"
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 from PIL import Image, ImageDraw, ImageFontdef verify_code (request ): import random bgcolor = (random.randrange(20 , 100 ), random.randrange(20 , 100 ), 255 ) width = 100 height = 25 im = Image.new('RGB' , (width, height), bgcolor) draw = ImageDraw.Draw(im) for i in range (0 , 100 ): xy = (random.randrange(0 , width), random.randrange(0 , height)) fill = (random.randrange(0 , 255 ), 255 , random.randrange(0 , 255 )) draw.point(xy, fill=fill) str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0' rand_str = '' for i in range (0 , 4 ): rand_str += str1[random.randrange(0 , len (str1))] font = ImageFont.truetype('FreeMono.ttf' , 23 ) fontcolor = (255 , random.randrange(0 , 255 ), random.randrange(0 ,255 )) draw.text((5 , 2 ), rand_str[0 ], font=font, fill=fontcolor) draw.text((25 , 2 ), rand_str[1 ], font=font, fill=fontcolor) draw.text((50 , 2 ), rand_str[2 ], font=font, fill=fontcolor) draw.text((75 , 2 ), rand_str[3 ], font=font, fill=fontcolor) del draw request.session['verifycode' ] = rand_str import io buf = io.BytesIO() im.save(buf, 'png' ) return HttpResponse(buf.getvalue(), 'image/png' )
配置路由。
修改 login.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > login page</title > </head > <body > <form method ="post" action ="/login_check/" > {% csrf_token %} 用户名:<input type ="text" name ="username" value ="{{ username }}" > <br /> 密码:<input type ="password" name ="password" > <br /> <img src ="/verify_code" > <input type ="text" name ="vcode" > <br /> <input type ="checkbox" name ="remember" > 记住用户名<br /> <input type ="submit" value ="登录" > </form > </body > </html >
修改一些逻辑:
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 def login (request ): if 'username' in request.COOKIES: username = request.COOKIES['username' ] else : username = '' return render(request, 'login.html' , {'username' :username}) def login_check (request ): '''登录校验视图''' vcode1 = request.POST.get('vcode' ) vcode2 = request.session.get('verifycode' ) if vcode1 != vcode2: return HttpResponseRedirect('/login' ) username=request.POST.get('username' ) password=request.POST.get('password' ) remember = request.POST.get('remember' ) if username == 'akashi' and password == '123' : resp = HttpResponseRedirect('/index' ) request.session['islogin' ] = True if remember == 'on' : resp.set_cookie('username' , username, max_age=7 *24 *3600 ) return resp else : return HttpResponseRedirect('/login' )
现在可以实现效果:验证码不通过则无法登录。
反向解析 当某一个 url 配置的地址发生变化时,页面上使用反向解析生成地址的位置不需要发生变化。
新建 url_reverse.html
页面,里边加入首页超链接:
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > index 链接:<br /> <a href ="/index" > 首页</a > <br /> </body > </html >
1 2 3 4 def url_reverse (request ): return render(request, 'url_reverse.html' )
配置路由。
如果我们修改 urls.py
:
1 2 path('index/' , views.index), path('index1/' , views.index),
则原来能用的链接都失效了。
将 url_reverse.html
修改为:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <br > index 链接:<br /> <a href ="/index" > 首页</a > <br /> url 反向解析生成 index 链接:<br /> <a href ="{% url 'index' %}" > 首页</a > </br > </body > </html >
同时在 urls.py
中新增(修改):
1 2 3 4 5 path('index1/' , views.index), path('index1/' , views.index, name='index' ), path('show_args/<int:a>/<int:b>' ,views.show_args,name='show_args' ), path('show_kwargs/<int:c>/<int:d>' ,views.show_kwargs,name='show_kwargs' ),
1 2 3 4 5 6 7 def show_args (request, a, b ): return HttpResponse(str (a) + ':' + str (b)) def show_kwargs (request, c, d ): return HttpResponse(str (c) + ":" + str (d))
效果,第一个链接不可以访问,第二个可以:
下面我们做一些更复杂的操作。
在 booktest 文件夹下新增 urls.py
. 注意现在我们有两个 urls.py
:
day1010/day1010/urls.py
(旧)
day1010/booktest/urls.py
(新)
在 day1010/day1010/urls.py
(旧)中,修改:
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 from django.contrib import adminfrom django.urls import path, includefrom booktest import viewsurlpatterns = [ path('admin/' , admin.site.urls), path('index1/' , views.index, name='index' ), path('index2/' , views.index2), path('books/' , views.show_books), path('books/<int:bid>' , views.detail), path('create/' , views.create), path('delete<int:bid>' , views.delete), path('aggregate/' ,views.use_aggregate), path('areas/' , views.areas), path('login/' , views.login), path('login_check/' , views.login_check), path('test_ajax/' , views.test_ajax), path('ajax_handle/' , views.ajax_handle), path('login_ajax/' , views.login_ajax), path('login_ajax_check/' , views.login_ajax_check), path('set_cookie/' , views.set_cookie), path('get_cookie/' , views.get_cookie), path('set_session/' , views.set_session), path('get_session/' , views.get_session), path('test_var/' , views.test_var), path('test_filters/' , views.test_filters), path('test_template_inhert/' , views.test_template_inhert), path('html_escape/' , views.html_escape), path('change_pwd/' , views.change_pwd), path('change_pwd_action/' , views.change_pwd_action), path('verify_code/' , views.verify_code), path('url_reverse/' , views.url_reverse), path('' ,include(('booktest.urls' ,'booktest' ),namespace = 'booktest' )), ]
在 day1010/booktest/urls.py
(新)中,修改:
1 2 3 4 5 6 7 from django.urls import path, includefrom booktest import viewsurlpatterns = [ path('show_args/<int:a>/<int:b>' ,views.show_args,name='show_args' ), path('show_kwargs/<int:c>/<int:d>' ,views.show_kwargs,name='show_kwargs' ), ]
在 url_reverse.html
中打开注释:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <br > index 链接:<br /> <a href ="/index" > 首页</a > <br /> url 反向解析生成 index 链接:<br /> <a href ="{% url 'index' %}" > 首页</a > </br > /show_args/1/2:<br /> <a href ="/show_args/1/2" > /show_args/1/2</a > <br /> 动态产生/show_args/1/2:<br /> <a href ="{% url 'booktest:show_args' 1 2 %}" > /show_args/1/2</a > <br /> /show_kwargs/3/4:<br /> <a href ="/show_kwargs/3/4" > /show_kwargs/3/4</a > <br /> 动态产生/show_kwargs/3/4:<br /> <a href ="{% url 'booktest:show_kwargs' c=3 d=4 %}" > /show_kwargs/3/4</a > </body > </html >
现在这些链接都可以访问:
现在,对 day1010/booktest/urls.py
(新)作修改:
1 2 3 4 5 6 7 8 from django.urls import path, includefrom booktest import viewsurlpatterns = [ path('show_args1/<int:a>/<int:b>' ,views.show_args,name='show_args' ), path('show_kwargs1/<int:c>/<int:d>' ,views.show_kwargs,name='show_kwargs' ), ]
再次运行,结果:
静态文件 在 static 文件夹下新增 images 文件夹,里面放入 amber.png
templates 文件夹下新增 static_test.html
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > {% load static %} <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 静态文件</title > </head > <body > <img src ="/static/images/amber.png" > <br /> <img src ="/abc/images/amber.png" > <br /> 动态获取 STATIC_URL,拼接静态文件路径:<br /> <img src ="{% static 'images/amber.png' %}" > </body > </html >
1 2 3 4 def static_test (request ): return render(request, "static_test.html" )
配置路由(老方法)。
结果:
可以看到,中间那张无法加载。
如果在 settings 中,修改:
1 2 STATIC_URL = 'static/' STATIC_URL = 'abc/'
那么结果变成:第一张无法加载,其他可以加载。
体现了动态获取 STATIC_URL 的好处 。
中间件 中间件函数 是 django 框架给我们预留的函数接口,让我们可以干预请求和应答的过程。
需求:现在我们需要禁掉一些有恶意行为的 IP 访问网站。
一种方法,我们可以使用装饰器模式,在每一个视图函数前加装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 EXCLUDE_IPS = [' 192.168.0.108' ] def blocked_ips (view_func ): def wrapper (request, *view_args, **view_kwargs ): user_ip = request.META['REMOTE_ADDR' ] if user_ip in EXCLUDE_IPS: return HttpResponse('<h1>Forbidden</h1>' ) else : return view_func(request, *view_args, **view_kwargs) return wrapper @blocked_ips def index (request ): pass
但是这样有一个问题,如果要禁止某个 IP 访问所有页面,我们需要对所有的视图函数加装饰器。这样非常麻烦(提问:是否通过 vim 操作并不麻烦)。
第二个思路是使用中间件。
在 ./booktest/ 下新建 myMiddleware.py
文件:
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 from django.http import HttpResponseclass BlockedIPSMiddleware (object ): def __init__ ( self, get_response ): self.get_response = get_response def __call__ (self, request ): return self.get_response(request) '''中间件类''' EXCLUDE_IPS = ['127.0.0.1' ] def process_view (self, request, view_func, *view_args, **view_kwargs ): '''视图函数调用之前会调用''' user_ip = request.META['REMOTE_ADDR' ] if user_ip in BlockedIPSMiddleware.EXCLUDE_IPS: return HttpResponse('<h1>Forbidden</h1>' ) class TestMiddleware (object ): '''中间件类''' def __init__ ( self, get_response ): print ('---init---' ) self.get_response = get_response def __call__ (self, request ): '''产生 request 对象之后,url 匹配之前调用''' print ('----process_request----' ) response=self.get_response(request) print ('------response------' ) return response def process_view (self, request, view_func, *view_args, **view_kwargs ): '''url 匹配之后,视图函数调用之前调用''' print ('----process_view----' ) class ExceptionTest1Middleware (object ): def __init__ (self, get_response ): self.get_response = get_response def __call__ (self, request ): return self.get_response(request) def process_exception (self, request, exception ): '''视图函数发生异常时调用''' print ('----process_exception1----' ) print (exception) class ExceptionTest2Middleware (object ): def __init__ (self, get_response ): self.get_response = get_response def __call__ (self, request ): return self.get_response(request) def process_exception (self, request, exception ): '''视图函数发生异常时调用''' print ('----process_exception2----' )
在 settings 中注册:
1 2 3 4 5 6 7 8 9 10 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.middleware.csrf.CsrfViewMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , 'booktest.myMiddleware.BlockedIPSMiddleware' , ]
重启服务,发现无法访问(因为本机地址被禁了)。
后台管理(续) 一些零碎的知识点。
admin.py
新增:
1 2 3 4 5 6 7 8 9 10 11 class AreaInfoAdmin (admin.ModelAdmin): '''地区模型管理类''' list_per_page = 10 list_display = ['id' , 'atitle' , 'parent' ] actions_on_bottom = True actions_on_top = False list_filter = ['atitle' ] search_fields = ['atitle' ] admin.site.register(Areas, AreaInfoAdmin)
在 models.py
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Areas (models.Model): '''地区模型类''' atitle = models.CharField(max_length=20 ) aParent = models.ForeignKey('self' , null=True ,blank=True ,on_delete=models.CASCADE,) def __str__ (self ): return self.atitle def parent (self ): if self.aParent is None : return '' return self.aParent.atitle parent.short_description = '父级地区名称'
效果:
目前的这个管理页面的新增功能有点弱,作修改。
admin.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class AreaStackedInline (admin.StackedInline): model = Areas extra = 2 class AreaInfoAdmin (admin.ModelAdmin): '''地区模型管理类''' list_per_page = 10 list_display = ['id' , 'atitle' , 'parent' ] actions_on_bottom = True actions_on_top = False list_filter = ['atitle' ] search_fields = ['atitle' ] fields = ['aParent' , 'atitle' ] inlines = [AreaStackedInline]
感觉没什么用。
上传图片 新建文件夹 ./static/media
.
在 settings 中新增:
1 MEDIA_ROOT=os.path.join(BASE_DIR,'static/media' )
在 ./booktest/models.py
中新增:
1 2 3 class PicTest (models.Model): '''上传图片''' goods_pic = models.ImageField(upload_to='booktest' )
迁移。
在 ./booktest/admin.py
中新增:
1 2 3 from booktest.models import BookInfo,HeroInfo,Areas,PicTestadmin.site.register(PicTest)
在后台管理页面可以直接上传图片了。由于我们的配置,图片会被存放在 ./static/media/booktest/
下面。
在数据库中,可以看到存储的是一个路径:
新增:
1 2 3 4 5 6 7 from booktest.models import PicTestdef pic_show (request ): pic=PicTest.objects.get(id =1 ) context={'pic' :pic} return render(request,'pic_show.html' ,context)
在 ./templates/
下新增 pic_show.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 显示图片</title > </head > <body > <img src ="/static/media/{{ pic.goods_pic }}" /> </body > </html >
在 ./day1010/urls.py
中新增:
1 path('pic_show/' , views.pic_show),
在 ./templates/
下新增 upload_pic.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 上传图片</title > </head > <body > <form method ="post" enctype ="multipart/form-data" action ="/upload_handle/" > {% csrf_token %} <input type ="file" name ="pic" > <br /> <input type ="submit" value ="上传" > </form > </body > </html >
在 ./booktest/views.py
中新增:
1 2 3 4 def show_upload (request ): '''显示上传图片页面''' return render(request, 'upload_pic.html' )
配置路由。
接下来编写 upload_handle 的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from day1010 import settingsdef upload_handle (request ): '''上传图片处理''' pic = request.FILES['pic' ] save_path = '%s/booktest/%s' %(settings.MEDIA_ROOT,pic.name) with open (save_path, 'wb' ) as f: for content in pic.chunks(): f.write(content) PicTest.objects.create(goods_pic='booktest/%s' %pic.name) return HttpResponse('ok' )
配置路由。
此时,/show_upload/
页面可以正常工作。
分页 在 ./templates/
下新建 show_area.html
:
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示地区</title > </head > <body > {% for area in areas %} <li > {{ area.atitle }} </li > {% endfor %} </body > </html >
在 ./booktest/views.py
中新增:
1 2 3 def show_area (request ): areas = Areas.objects.filter (aParent__isnull=True ) return render(request, 'show_area.html' ,{'areas' : areas})
配置路由。
运行,在浏览器中查看。这个显示太长了,我们希望进行分页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from django.core.paginator import Paginatordef show_area (request, pindex=1 ): areas = Areas.objects.filter (aParent__isnull=True ) paginator = Paginator(areas, 10 ) pindex = int (pindex) page = paginator.page(pindex) return render(request, 'show_area.html' ,{'page' : page})
修改 show_area.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示地区</title > </head > <body > {% for area in page %} <li > {{ area.atitle }} </li > {% endfor %} {% for pindex in page.paginator.page_range %} {% if pindex == page.number %} {{ pindex }} {% else %} <a href ="/show_area/{{ pindex }}" > {{ pindex }}</a > {% endif %} {% endfor %} </body > </html >
配置路由:
1 2 path('show_area/' , views.show_area), path('show_area/<int:pindex>' , views.show_area),
运行,可以进行正常的分页、翻页操作。
增加“上一页” “下一页” 功能,修改 show_area.html
:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 显示地区</title > </head > <body > {% for area in page %} <li > {{ area.atitle }} </li > {% endfor %} {% if page.has_previous %} <a href ="/show_area/{{ page.previous_page_number }}" > < 上一页</a > {% endif %} {% for pindex in page.paginator.page_range %} {% if pindex == page.number %} {{ pindex }} {% else %} <a href ="/show_area/{{ pindex }}" > {{ pindex }}</a > {% endif %} {% endfor %} {% if page.has_next %} <a href ="/show_area/{{ page.next_page_number }}" > 下一页> </a > {% endif %} </body > </html >
效果:
省市县选择案例 这部分内容其实偏前端。
新建 ./templates/areas.html
:
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 99 100 101 102 103 104 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 收件地址选择</title > <script src ="/static/js/jquery-1.12.4.min.js" > </script > <script > $(function ( ) { $.get ('/prov' , function (data ) { res = data.data prov = $('#prov' ) $.each (res, function (index, item ) { id = item[0 ] atitle = item[1 ] option_str = '<option value="' +id + '">' + atitle+ '</option>' prov.append (option_str) }) }) $('#prov' ).change (function ( ) { prov_id=$(this ).val () $.get ('/city/' +prov_id, function (data ) { res = data.data city = $('#city' ) city.empty ().append ('<option>---请选择市---</option>' ) dis = $('#dis' ) dis.empty ().append ('<option>---请选择县---</option>' ) $.each (res, function (index, item ) { id = item[0 ] atitle = item[1 ] option_str = '<option value="' +id + '">' + atitle+ '</option>' city.append (option_str) }) }) }) $('#city' ).change (function ( ) { city_id=$(this ).val () $.get ('/dis/' +city_id, function (data ) { res = data.data dis = $('#dis' ) dis.empty ().append ('<option>---请选择县---</option>' ) $.each (res, function (index, item ) { id = item[0 ] atitle = item[1 ] option_str = '<option value="' +id + '">' + atitle+'</option>' dis.append (option_str) }) }) }) }) </script > </head > <body > <select id ="prov" > <option > ---请选择省---</option > </select > <select id ="city" > <option > ---请选择市---</option > </select > <select id ="dis" > <option > ---请选择县---</option > </select > </body > </html >
视图函数:
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 def areas (request ): '''省市县选中案例''' return render(request, 'areas.html' ) def prov (request ): '''获取所有省级地区的信息''' areas = Areas.objects.filter (aParent__isnull=True ) areas_list = [] for area in areas: areas_list.append((area.id , area.atitle)) return JsonResponse({'data' :areas_list}) def city (request, pid=0 ): '''获取 pid 的下级地区的信息''' areas = Areas.objects.filter (aParent__id=pid) areas_list = [] for area in areas: areas_list.append((area.id , area.atitle)) return JsonResponse({'data' : areas_list})
配置路由:
1 2 3 4 path('areas/' , views.areas), path('prov/' ,views.prov), path('city/<int:pid>' ,views.city), path('dis/<int:pid>' ,views.city),
实现效果:
上一级区域选择之后,到下一级菜单中会自动显示该区域的下级单位。
实际上此类功能现在一般交由第三方处理。
富文本编辑器 安装:
1 pip install django-tinymce --proxy="http://127.0.0.1:7897"
在 settings 中新增:
1 2 3 4 5 6 7 8 9 10 INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , 'booktest' , 'tinymce' , ]
继续在 settings 中增加配置:
1 2 3 4 5 TINYMCE_DEFAULT_CONFIG = { 'theme' : 'silver' , 'width' : 600 , 'height' : 400 , }
配置 ./day1010/urls.py
:
1 path('tinymce/' ,include('tinymce.urls' )),
在 ./booktest/models.py
新增:
1 2 3 from tinymce.models import HTMLFieldclass GoodsInfo (models.Model): gcontent=HTMLField()
迁移。
在 ./booktest/admin.py
中新增:
1 2 3 4 5 6 from booktest.models import BookInfo,HeroInfo,Areas,PicTest,GoodsInfoclass GoodsInfoAdmin (admin.ModelAdmin): list_display = ['id' ] admin.site.register(GoodsInfo,GoodsInfoAdmin)
可以在后台管理页面看到效果:
如何在前台看到呢?
新建 ./templates/show.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <html > <head > <title > 展示富文本编辑器内容</title > </head > <body > id:{{g.id}} <hr > {%autoescape off%} {{g.gcontent}} {%endautoescape%} <hr > </body > </html >
1 2 3 4 5 6 def show (request ): goods=GoodsInfo.objects.get(pk=1 ) context={'g' :goods} return render(request,'show.html' ,context)
配置路由。运行成功。
如何在前台使用这个富文本编辑器呢? 关于这个,小编也很好奇呢
本文章的示例代码参见:https://github.com/dropsong/py_webServer/tree/master/day1010