1125 视图层
一. orm表关系的建立
小数类型
# 小数max_digits共8位,小数占两位
price = models.DecimalField(max_digits=8,decimal_places=2)
一对多.ForeignKey(to='')
一对多,主键建在多的一方
publish = models.ForeignKey(to='Publish')
to 用来指定跟那张表有关系,默认关联的就是表的主键
多对多ManyToManyField(to='')
多对多的方式,主键随意在哪个表都可以,但推荐在查询频率高的表中
django会自动创建第三张表,并建立表于表之间的联系
一对一OneToOneField(to='')
一对一的表关系,外键字段建在任意样都可,推荐建在查询频率高的一方
一对一外键字段,创建的时候,会同步到数据中,表字段会自动加_id后缀
如果加了_id,orm还会再度添加_id后缀
实例
图书管理系统
一对多 外键字段建在 多的那一方
多对多 多对多的外键关系需要建立第三章来专门处理
图书表
出版社表
作者表
from django.db import models
# Create your models here.
# 先不要考虑外键关系 先创基表
class Book(models.Model):
title = models.CharField(max_length=32)
# 小数总共八位 小数占两位
price = models.DecimalField(max_digits=8,decimal_places=2)
# 书跟出版社是一对多 并且书是多的一方 所以外键字段健在书表中
publish_id = models.ForeignKey(to='Publish') # to用来指代跟哪张表有关系 默认关联的就是表的主键字段
"""
一对多外键字段 创建的时候 同步到数据中 表字段会自动加_id后缀
如果你自己加了_id 我orm头铁 再在后面加一个_id
所以你在写一对多外键字段的时候 不要自作聪明的加_id
"""
# 书跟作者是多对多的关系 外键字段建在任意一方都可以 但是建议你建在查询频率较高的那一方
author = models.ManyToManyField(to='Author') # django orm会自动帮你创建书籍 和作者的第三张关系表
# author这个字段是一个虚拟字段 不能在表中展示出来 仅仅只是起到一个高速orm 建第三章表的关系的作用
class Publish(models.Model):
title = models.CharField(max_length=32)
email = models.EmailField()
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
# 一对一的表关系 外键字段建在任意一方都可以 但是建议你建在查询频率较高的那一方
author_detail = models.OneToOneField(to='Author_detail') # fk + unique
"""
一对一外键字段 创建的时候 同步到数据中 表字段会自动加_id后缀
如果你自己加了_id 我orm头铁 再在后面加一个_id
所以你在写一对一外键字段的时候 不要自作聪明的加_id
"""
class Author_detail(models.Model):
phone = models.BigIntegerField()
addr = models.CharField(max_length=32)
二. django请求周期流程图
三. 路由层
urls
文件,负责路由分发
1. 路由匹配
url第一个参数是正则表达式
- url第一个参数是一个正则表达式 只要该正则表达式能够匹配到内容
- 就会立刻执行后面的视图函数 而不再往下继续匹配了
urlpatterns = [
url(r'^admin/', admin.site.urls), # url第一个参数是一个正则表达式
url(r'^test/$', views.test), # 一旦正则表达式能够匹配到内容 会立刻结束匹配关系 直接执行后面对应的函数
url(r'^testadd/$', views.testadd),
]
-
^表示开头匹配(只能匹配以^后面的字符开头)
-
$表示结尾匹配(只能匹配以$后面的字符结尾)
django匹配路由的规律
不加斜杠 先匹配一次试试 如果匹配不上 会让浏览器重定向 加一个斜杠再来一次 如果还匹配不上 才会报错"""
1.取消django自动加斜杠的功能
settings中添加字段
APPEND_SLASH = False
2.路由匹配只匹配url
部分,不匹配?
后面的get
携带的参数
总结
# 正则路径 视图函数地址 默认关键字参数(了解) 路由别名
# ^表示开头匹配(只能匹配以^后面的字符开头) $表示结尾匹配(只能匹配以$后面的字符结尾)
# r'^index$' 只能匹配index,不能匹配index/
# r'^index/$' 只能匹配index/,能匹配index(先拿index匹配,如果失败,会添加/再次匹配)
url(r'^index/$', views.index)
url(r'^testkw/$', views.testkw, {'num': 88888}) # def testkw(request, num):
# num为关键字参数,此处已固定死,传给testkw 意义不大,因此不常用
# 路由匹配时是否会默认添加/
# 'django.middleware.common.CommonMiddleware' 控制APPEND_SLASH的中间件
# APPEND_SLASH = False # django中此值默认为True
2.无名分组与有名分组
无名分组(/d+)
将分组内正则表达式匹配到的内容当做位置参数传递给视图函数
url(r'^test/([0-9]{4})/', views.test)test()
url(r'^test/(/d+)/', views.test)test()
takes 1 positional argument but 2 were given
- 当你的路由中有分组的正则表达式,那么在匹配的内容
- 执行视图函数的时候,会将分组内正则表达书匹配到的内容当做位置参数传递给视图函数
test(request,分组内正则表达式匹配到的内容)
有名分组(?P<page>\d+)
url(r'^testadd/(?P<year>\d+)/', views.testadd),
testadd() got an unexpected keyword argument 'year'
- 当你的路由中有分组并且给分组起了别名,那么在匹配内容的时候会将分组内的正则表达式匹配到的内容当做关键字参数传递给视图函数
testadd(request,year=分组内的正则表达式匹配到的内容)
利用有名个无名分组 我们就可以在调用视图函数之前给函数传递额外的参数
不能混合使用
不能混合使用!!!
但是同一种分组的情况下 可以使用多次
无名可以有多个
有名可以有多个
但是就是不能混合使用
url(r'^index/(\d+)/(\d+)/', views.index),
url(r'^index/(?P<args>\d+)/(?P<year>\d+)/', views.index)
总结
# 无名分组:/testnoname/10/
url(r'^testnoname/(\d+)/$', views.testnoname)
# django自动将testnoname和(\d+)匹配到值以位置参数的形式传给views.testnoname,因此testnoname函数中可以用任意变量接受(\d+)匹配到值
# def testnoname(request, var) # var=10
# 有名分组:/testname/10/1/
url(r'^testname/(?P<num>\d+)/(?P<page>\d+)/$', views.testname)
# django自动将(?P<num>\d+)/(?P<page>\d+)匹配到值 以关键字参数的形式传给views.testname,因此testname函数中只能用对应的关键字接受(?P<num>\d+)/(?P<page>\d+)匹配到值
# def testname(request, page, num) # page=1 num=10
# 注:有名分组和无名分组不能混用(如果混用,无名参数无法接受)
3.反向解析
定义
根据一个别名 动态解析出一个结果,该结果可以直接访问对应的url
随着功能的增加会出现更多的视图,可能之前配置的正则表达式不够准确,于是就要修改正则表达式,但是正则表达式一旦修改了,之前所有对应的超链接都要修改,让链接根据正则表达式动态生成,就是用反向解析的办法。
使用方法
- 定义url时,需要为include定义namespace属性,为url定义name属性
- 使用时,在模板中使用url标签,在视图中使用reverse函数,根据正则表达式动态生成地址,减轻后期维护成本。
首先,要对主路由增加命名空间,
对app里面的路由加上别名(即是给name赋值),
然后无论是在view视图中的反定向也好,模板中的反向解析也好,都是在用命名空间和别名来操作的,
无论路由中的正则表达式怎么变化,都不会影响到别名,也就是说别名是固定的,改变正则表达式时别名不变,
这样就无需改变代码里用到的路由的地方,否则就要花很大的功夫来修改里面的每一个。
第一种情况
路由中没有正则表达式,直接是写死的
前端反向解析
1.url中添加name属性
url(r'^home/', views.home,name='xxx'), # 给路由与视图函数对应关系起别名
2.模板中设置url
a 标签内 {% url 'xxx' %}
后端反向解析reverse
from django.shortcuts import reverse
url = reverse('xxx')
第二种情况
无名分组的反向解析
在解析的时候 你需要手动指定正则匹配的内容是什么
前端反向解析
添加name属性
url(r'^home/(\d+)/', views.home,name='xxx'),
# 给路由与视图函数对应关系起别名
模板设置
直接设置数字即可,一般设置的是主键值
<p><a href="{% url 'xxx' 12 %}">111</a></p>
<p><a href="{% url 'xxx' 1324 %}">111</a></p>
后端反向解析
url = reverse('xxx',args=(1,))
url1 = reverse('xxx',args=(3213,))
url2 = reverse('xxx',args=(2132131,))
# 手动传入的参数 只需要满足能够被正则表达式匹配到即可
第三种情况
有名分组的反向解析
- 在解析的时候,你需要手动指定正则匹配的内容是什么
- 有名分组的反向解析可以跟无名分组一样
url(r'^home/(?P<year>\d+)/', views.home,name='xxx'),
# 给路由与视图函数对应关系起别名
前端
# 可以直接用无名分组的情况
<p><a href="{% url 'xxx' 12 %}">111</a></p>
# 你也可以规范写法
<p><a href="{% url 'xxx' year=1232 %}">111</a></p> # 了解即可
后端
# 可以直接用无名分组的情况
url = reverse('xxx',args=(1,))
# 你也可以规范写法
url = reverse('xxx',kwargs={'year':213123}) # 了解即可
实例(编辑功能)
url(r'^edit_user/(\d+)/',views.edit_user,name='edit')
def edit_user(request,edit_id):
# edit_id就是用户想要编辑数据主键值
pass
{% for user_obj in user_list %}
<a href='/edit_user/{{user_obj.id}}/'>编辑</a>
<a href='{% url 'edit' user_obj.id %}'>编辑</a>
{% endfor %}
总结
from django.shortcuts import reverse
在视图函数中通过reverse方法反向解析出真实的路径
# 1.不带分组:url(r'可能会变的真实路由', 视图函数, name='路由别名')
url = reverse('路由别名')
# 2.无名分组:url(r'可能会变的真实路由(带无名分组)', 视图函数, name='路由别名')
url = reverse('路由别名', args=(给无名分组赋值))
# 3.有名分组:url(r'可能会变的真实路由(带有名分组)': 视图函数, name='路由别名')
url = reverse('路由别名', kwargs={给有名分组赋值:key就是有名分组名})
4. 路由分发*******
前提
- 在django中所有的app都可以有自己独立的urls.py templates static
- 正是由于上面的特点 你用django开发项目就能够完全做到多人分组开发 互相不干扰
- 每个人只开发自己的app
- 小组长只需要将所有人开发的app整合到一个空的django项目里面
- 然后在settings配置文件注册 再利用路由分发将多个app整合到一起即可完成大项目的拼接
解决
路由分发解决的就是项目的总路由匹配关系过多的情况
- 使用路由分发 会将总路由不再做匹配的活 而仅仅是做任务分发
- 即(请求来了之后 总路由不做对应关系只询问你要访问哪个app的功能 然后将请求转发给对应的app去处理)
使用include()
总路由
1.在总路由中导入子路由的地址,防止重名起别名
第一种导入
from app01 import views as app01_views
from app02 import views as app02_views
第二种方法
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls'))
2.导入自己的视图函数
3.路由分发include()
总路由中不能以$
结尾
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 路由分发
url(r'^app01/',include(app01_urls)),
# 路由分发需要注意的事项: 就是总路由里面不能以$结尾
url(r'^app02/',include(app02_urls)),
]
子路由
**************************app01**************************
from django.conf.urls import url
from app01 import views
urlpatterns = [
url('^reg/',views.reg)
]
**************************app02**************************
from django.conf.urls import url
from app02 import views
urlpatterns = [
url('^reg/',views.reg)
]
总结
# 在主路由中要配置多个app,要对视图函数调用起别名
from app01 import views as app01_views
from app02 import views as app02_views
url(r'^app01/test_page/$', app01_views.test_page),
url(r'^app02/test_page/$', app02_views.test_page),
5.名称空间namespace
定义
当多个app中出现了起别名冲突的情况 你在做路由分发的时候 可以给每一个app创建一个名称空间然后在反向解析的时候 可以选择到底去哪个名称空间中查找别名
url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02'))
路由别名
主路由:
from django.conf.urls import include
urlpatterns = [
url(r'^app01/', include('app01.urls', namespace='app01')),
url(r'^app02/', include('app02.urls', namespace='app02')),
]
app01应用下路由
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^test/', views.test, name='test')
]
app02应用下路由
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^test/', views.test, name='test')
]
前端页面反向解析:{% url 'app01:test' %} | {% url 'app02:test' %}
视图函数reverse方法:url = reverse('app01:test') | url = reverse('app02:test')
反向解析
# 后端
print(reverse('app01:reg'))
print(reverse('app02:reg'))
# 前端
<a href="{% url 'app01:reg' %}"></a>
<a href="{% url 'app02:reg' %}"></a>
使用解决
起别名的时候统一加上应用名前缀
urlpatterns = [
url(r'^reg/',views.reg,name='app02_reg')
]
urlpatterns = [
url('^reg/',views.reg,name='app01_reg')
]
总结
方法1:在对应应用下建立自己的templates,再在templates下建立与应用名同名的文件夹,模板文件放在应用名同名的文件夹下
方法2:在项目根目录下的templates中建立每一个应用同名的文件夹,每个应用的模板文件放在自己应用名文件夹下
使用:render(request, 'app_name/test.html')
6.伪静态
浏览的html网页看起来像是静态网页,其实是动态改变的,而搜索引擎会优先获取静态网页保存,所以都使用html后缀,改变后缀即可.
- 将一个动态网页伪装成一个静态网页 以此来挺好搜索引擎SEO查询频率和搜藏力度
- 所谓的搜索引擎其实就是一个也别巨大的爬虫程序
- 优化的再厉害还是抵不过RMB玩家
动态页面:数据内容会发生变化的页面
静态页面:数据内容不会发生变化的页面
针对SEO(搜索引擎优化),静态页面更容易被搜索引擎网站收录
伪静态就是将动态页面伪装成静态页面,容易被搜索引擎网站收录,从而增加搜索概率,提高流量
路由层:
url('^index/$', views.index),
url('^article/(?P<id>(\d+)).html/$', views.article, name='article') # url中加上.html(访问时也需要有后缀如此处http://127.0.0.1:8000/article/2.html)变成伪静态页面.
视图函数层:
def index(request):
return render(request, 'index.html')
def article(request, id):
return render(request, 'article.html', {'id': id})
模板层:
index.html
<a href="{% url 'article' 1 %}">第一篇文章</a>
<a href="{% url 'article' 2 %}">第二篇文章</a>
<a href="{% url 'article' 3 %}">第三篇文章</a>
article.html
<h1>第{{ id }}篇文章</h1>
7.虚拟环境
解决版本共存
- 用pycharm选择virtualenv创建一个纯净环境
- 将环境copy到需要指定长期使用的文件夹下
- 再次创建项目时,将该环境添加到pycharm环境选择中
- 为该环境添加需要处理的版本共存包
- 给每一个项目 装备该项目所需要的模块 不需要的模块一概不装
- 每创建一个虚拟环境就类似于你重新下载了一个纯净python解释器之后该项目用到上面 你就装什么(虚拟环境一台机器上可以有N多个)
- 不要在你的机器上无限制创建虚拟环境
8. django版本区别
django1.X django2.X
urls.py中路由匹配的方法有区别
django2.X用的是path
urlpatterns = [
path('admin/', admin.site.urls),
]
django1.X用的是url
urlpatterns = [
url(r'^reg.html',views.reg,name='app02_reg')
]
# 区别 django2.X里面path第一个参数不是正则也不支持正则 写什么就匹配什么
# 虽然path不支持正则 感觉也好用 django2.X还有一个re_path的方法 该方法就是你django1.X里面url
# 虽然path支持 但是它提供了五种转换器 能够将匹配到的数据自动转黄成对应的类型
# 除了有默认五种转换器之外 还支持你自定义转换器
2.x新特性
from django.urls import path, re_path
# 向下兼容,不建议使用url, 使用与url相同的re_path
from django.conf.urls import url
urlpatterns = [
path('index/', views.index),
# page/<int:msg>/ page/(?P<msg>(\d+)) # 访问/page/123/
# path('page/<int:msg>/', views.page),
# path('page/<str:msg>/', views.page),
# path('page/<path:msg>/', views.page),
# path('page/<slug:msg>/', views.page),
path('page/<uuid:msg>/', views.page),
]
1. 2.x版本re_path的使用方式同1.x版本url
2. path写的是绝对字符串,请求地址必须与路由地址完全匹配
3. path拥有五个转换器:
-- str:匹配除路径分隔符(/)外的字符串,默认
-- int:匹配自然数
-- slug:匹配字母、数字、横杠及下划线组成的字符串
-- uuid:匹配uuid形式的数据
-- path:匹配任何字符串,包括路径分隔符(/) (不能用?)
- 2.x版本re_path的使用方式同1.x版本url
- path写的是绝对字符串,请求地址必须与路由地址完全匹配
- path拥有五个转换器:
-- str:匹配除路径分隔符(/)外的字符串,默认
-- int:匹配自然数
-- slug:匹配字母、数字、横杠及下划线组成的字符串
-- uuid:匹配uuid形式的数据
-- path:匹配任何字符串,包括路径分隔符(/) (不能用?)
9.form表单上传文件
form表达传文件需要注意的事项
- method必须改成post
- enctype该成formdata格式
- 前期你在使用post朝后端发请求的时候 需要去settings配置文件中注释掉一个中间件csrf
request.FILES
获取上传的文件
前端:upload.html页面
1.往自身路径发送post请求,要将第四个中间件注释
2.multipart/form-data格式允许发送文件
3.multiple属性表示可以多文件操作
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple="multiple">
<input type="submit" value="上传">
</form>
后台:re_path('^upload/$', upload)
def upload(request):
if request.method == "GET":
return render(request, 'upload.html')
if request.method == "POST":
# 如果一个key对应提交了多条数据,get取最后一个数据,getlist取全部数据
last_file = request.FILES.get('files', None)
files = request.FILES.getlist('files', None)
# import django.core.files.uploadedfile.TemporaryUploadedFile
# file是TemporaryUploadedFile类型,本质是对系统file类封装,就是存放提交的文件数据的文件流对象
for file in files:
with open(file.name, 'wb') as f:
for line in file: # 从file中去数据写到指定文件夹下的指定文件中
f.write(line)
return HttpResponse('上传成功')