Django orm关系表的创建 、路由层以及视图层
一、Django 表关系的创建
1、表的对应关系
一对多
多对多
一对一
2、如何判断表的对应关系
利用换位思考的方式,分别站在两张表的角度上去思考
这里比如我们以图书管理系统为例来创建几个简单的表
图书表
出版社表
作者表
为了方便演示,这里我们就强行规定了表之间的关系:
图书和出版社是一对多的外键关系,一对多外键关系 将外键字段建在多的哪一张表上
图书与作者之间是多对多的外键关系,多对多的外键关系 外键字段无论建在那张表都可以,但是推荐你建在查询频率高的那张 表上
作者与作者与作者详情是一对一的外键关系,一对一 的外键关系外键字段建在那张表都可以,但是推荐你建在查询频率高的那 张表上
注意:
1、在书写表关系的时候,要先把基表全部写出来之后再考虑外键字段
2、Foreignkey字段以及OneToOneField字段 在创建表的时候orm都会自动给该字段加_id的后缀(无论你自己有没有加)
3、authors字段是一个虚拟的字段 并不会真正的在表中创建出来,只是用来告诉Django orm 需要创建一个第三方表
- 创建表的代码演示:
from django.db import models # Create your models here. class Book(models.Model): # id 是自动创建的 这里就不创建了 title = models.CharField(max_length=64) # price 为小数字段 这句话代表总共8位数小数位占2位 price = models.DecimalField(max_digits=8, decimal_places=2) # 书籍与出版社是一对多的关系 publish = models.ForeignKey(to='Publish') # 这句话的意思代表书籍表与出版社表关联,默认关联字段就是出版社的主键字段 # publish = models.ForeignKey(to=Publish) # 注意: to后面也可以直接书写表的变量名, 但是需要保证该变量名在当前位置的上方先定义 # 书籍与作者是对对多外键关系 authors = models.ManyToManyField(to='Author') # 书籍与作者是多对多关系 ''' authors 字段是一个虚拟字段 不会真正的在表中创建出来,只是 用来告诉Django orm 需要创建书籍和作者的第三方关系表 ''' class Publish(models.Model): name = models.CharField(max_length=64) addr = models.CharField(max_length=64) class Author(models.Model): name = models.CharField(max_length=32) phone = models.BigIntegerField() # 一对一外键关系建立 author_detail = models.OneToOneField(to='AuthorDetail') class AuthorDetail(models.Model): age = models.IntegerField() addr = models.CharField(max_length=255)
注: 这里我们演示使用的就是Django默认自带的sqlite3的数据库,同理在使用之前也应该类似于使用mysql一样,先去下载驱动文件
二、Django 请求生命周期图(*****非常重要***)
1、一张非常重要的图(简易版)
三、路由层
1、路由匹配
- url 方法
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'test',views.test),
url(r'testadd',views.testadd)
]
url方法第一个参数是一个正则表达式,路由匹配按照正则表达式去匹配 一旦正则能够匹配到内容 会立刻执行对应的视图函数,就不会再继续匹配了
注意:用户在浏览器中输入url时不加最后的斜杠 Django会默认自动加上,你可以在配置文件中指定是否开启该功能APPEND_SLASH = True/False
- url 参数中的几种演示
1、url第一个参数什么都不加
2、url第一个参数中加了一个/
四、分组
分组就是给一段正则表达式加括号
1、无名分组
在路由分配的时候给某段正则表达式加了括号,匹配的时候会将括号内正则表达式匹配到的内容当做位置参数传递给对应的视图函数
比如:无名分组
url(r'^test/([0-9]{4})/',views.test)
# 视图函数
def index(request,args):
return HttpResponse('')
- 演示
2、有名分组
有名分组就是给一段正则表达式起一个别名,在匹配的时候会将括号内正则表达式匹配到的内容当做关键字参数传递给对应的视图函数
比如:有名分组
url(r'^test/(?P<year>\d+)/',views.test)
# 视图函数
def index(request,year):
...
- 演示
3、无名分组与有名分组能否混合使用
答:不能!!!
注:虽然两种命名方式不能混合使用,但同一种命名方式可以使用多次
- 演示
五、反向解析
通过一些方法,就能够得到一个结果,该结果可以反向的去访问到对应的url
使用方法:
1、要先给路由与视图函数对应关系 起一个名字,比如:
url(r'^testadd/', views.testadd, name='add')
2、前端解析
{% url '你起的绑定的名字' %}
3、后端解析
from django.shortcuts import render, HttpResponse, redirect
然后只需要给你的视图函数在reverse的参数中绑定你起的名字即可
reverse('add')
- 演示
1、路由与视图函数对应关系 起一个名字
2、前端解析
3、后端解析
六、无名分组反向解析
1、无名分组反向解析
url(r'^testadd/(\d+)/', views.testadd, name='add')
2、前端解析
{% url '你起的绑定的名字' 123 %}
3、后端解析
reverse('add', args=(12,))
- 演示
1、urls
2、前端解析
3、后端解析
七、有名分组反向解析
1、有名分组反向解析
url(r'^testadd/(?P<year>)(\d+)/', views.testadd, name='add')
2、前端解析
{% url '你起的绑定的名字' 123 %} #和无名使用方法一样,推荐使用
{% url '你起的绑定的名字' year=123 %} # 标准的
3、后端解析
reverse('add', args=(12,))
reverse('add', kwargs={'year': 12}) # 标准写法
4、伪代码诠释
"""
url(r'^edit_user/(\d+)/',views.edit_user,names='edit')
{% for user_obj in user_queryset %}
<a href="edit_user/{{ user_obj.id }}/">编辑</a>
<a href="{% url 'edit' user_obj.id %}">编辑</a>
{% endfor %}
def edit_user(request,edit_id):
reverse('edit',args=(edit_id,))
"""
- 演示
八、路由分发
当django项目比较庞大的时候 路由与视图函数对应关系较多
总路由代码太多冗长
考虑到总路由代码不好维护 django支持每个app都可以有自己的urls.py
并且总路由不再做路由与视图函数的对应关系 而仅仅只做一个分发任务的操作
根据请求的不同 识别出当前请求需要访问的功能属于哪个app然后自动
下发到对应app里面的urls.py中 然后由app里面的urls.py做路由与视图函数的匹配
不仅如此每个app除了可以有自己的urls.py之外 还可以有自己的static文件夹 templates模板文件
基于上面的特点 基于django分小组开发 会变得额外的简单
每个人只需要开发自己的app即可 之后只需要创建一个空的django项目
将多个人的app全部拷贝项目下 配置文件注册
总路由分发一次
需要一个分发的模块
1.总路由
from django.conf.urls import url,include
# 路由分发 注意事项应用名后面千万不能加$
# from app01 import urls as app01_urls
# from app02 import urls as app02_urls
# url(r'^app01/',include(app01_urls)),
# url(r'^app02/',include(app02_urls))
# 简写
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls'))
2.子路由
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/',views.index)
]
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^index/',views.index)
]
1、创建新的APP
2、去注册新创建的app
3、路由分发
4、效果演示
九、名称空间(了解)
url(r'^app01/', include('app01.urls', namespace='app01'))
url(r'^app02/', include('app02.urls', namespace='app02'))
后端解析
reverse('app01:index')
reverse('app02:index')
前端解析
{% url 'app01:index' %}
{% url 'app02:index' %}
# 在给路由与视图函数起别名的时候只需要保证永远不出现冲突的情况即可
# 通常情况下我们推荐起别名的时候加上当前应用的应用名前缀
- 演示
方式一:加namespace
方式二:加上APP前缀
十、虚拟环境
我们想做到针对不同的项目 只安装项目所需的功能模块,项目用不到的一概不装 来避免加载资源时的消耗
1、查找当前本机所装的所有模块
2、创建虚拟环境
虚拟环境类似于重新下载一个纯净的python解释器环境
十一、Django版本的区别
路由层
1.X用的是url
2.X、3.X用的是path
url第一个参数是一个正则表达式
而path第一个参数不支持正则表达式 写什么就匹配什么
如果你觉得path不好用 2.x、3.x给你提供了一个跟url一样的功能
re_path 等价于1.x里面的url功能
虽然path不支持正则表达式 但是它给你提供了五种默认的转换器
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
path('login/<int:year>/',login)
除了默认的五种转换器之外 还支持你自定义转换器
class MonthConverter:
regex='\d{2}' # 属性名必须为regex
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
7.1 re_path
Django2.0中的re_path与django1.0的url一样,传入的第一个参数都是正则表达式
from django.urls import re_path # django2.0中的re_path from django.conf.urls import url # 在django2.0中同样可以导入1.0中的url urlpatterns = [ # 用法完全一致 url(r'^app01/', include(('app01.urls','app01'))), re_path(r'^app02/', include(('app02.urls','app02'))), ]
7.2 path
在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题,如下
urls.py文件
from django.urls import re_path from app01 import views urlpatterns = [ # 问题一:数据类型转换 # 正则表达式会将请求路径中的年份匹配成功然后以str类型传递函数year_archive,在函数year_archive中如果想以int类型的格式处理年份,则必须进行数据类型转换 re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), # 问题二:正则表达式冗余 # 下述三个路由中匹配article_id采用了同样的正则表达式,重复编写了三遍,存在冗余问题,并且极不容易管理,因为一旦article_id规则需要改变,则必须同时修改三处代码 re_path(r'^article/(?P<article_id>[a-zA-Z0-9]+)/detail/$', views.detail_view), re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/edit/$', views.edit_view), re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/delete/$', views.delete_view), ]
views.py
from django.shortcuts import render,HttpResponse # Create your views here. def year_archive(request,year): print(year,type(year)) return HttpResponse('year_archive page') def detail_view(request,article_id): print(article_id, type(article_id)) return HttpResponse('detail_view page') def edit_view(request,article_id): print(article_id, type(article_id)) return HttpResponse('edit_view page') def delete_view(request,article_id): print(article_id, type(article_id)) return HttpResponse('delete_view page'
Django2.0中的path如何解决上述两个问题的呢?请看示例
from django.urls import path,re_path from app01 import views urlpatterns = [ # 问题一的解决方案: path('articles/<int:year>/', views.year_archive), # <int:year>相当于一个有名分组,其中int是django提供的转换器,相当于正则表达式,专门用于匹配数字类型,而year则是我们为有名分组命的名,并且int会将匹配成功的结果转换成整型后按照格式(year=整型值)传给函数year_archive # 问题二解决方法:用一个int转换器可以替代多处正则表达式 path('articles/<int:article_id>/detail/', views.detail_view), path('articles/<int:article_id>/edit/', views.edit_view), path('articles/<int:article_id>/delete/', views.delete_view), ]
强调:
#1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠 #2、使用尖括号(<>)从url中捕获值,相当于有名分组 #3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符
django默认支持一下5种转换器(Path converters)
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式 int,匹配正整数,包含0。 slug,匹配字母、数字以及横杠、下划线组成的字符串。 uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。 path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
例如
path('articles/<int:year>/<int:month>/<slug:other>/', views.article_detail) # 针对路径http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配出参数year=2009,month=123,other='hello'传递给函数article_detail
很明显针对月份month,转换器int是无法精准匹配的,如果我们只想匹配两个字符,那么转换器slug也无法满足需求,针对等等这一系列复杂的需要,我们可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
-
regex
类属性,字符串类型 to_python(self, value)
方法,value是由类属性regex
所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。-
to_url(self, value)
方法,和to_python
相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。
自定义转换器示例:
-
在app01下新建文件path_ converters.py,文件名可以随意命名
class MonthConverter: regex='\d{2}' # 属性名必须为regex def to_python(self, value): return int(value) def to_url(self, value): return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
-
在urls.py中,使用
register_converter
将其注册到URL配置中:from django.urls import path,register_converter from app01.path_converts import MonthConverter register_converter(MonthConverter,'mon') from app01 import views urlpatterns = [ path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'), ]
-
views.py中的视图函数article_detail
from django.shortcuts import render,HttpResponse,reverse def article_detail(request,year,month,other): print(year,type(year)) print(month,type(month)) print(other,type(other)) print(reverse('xxx',args=(1988,12,'hello'))) # 反向解析结果/articles/1988/12/hello/ return HttpResponse('xxxx')
十二、伪静态
url以.html结尾 给人的感觉好像是这个文件是写死的 内容不会轻易的改变
伪静态
为了提高你的网站被搜索引擎收藏的力度 提供网站的SEO查询效率
但是 无论你怎么做优化 都抗不过RMB玩家
十三、视图层
视图函数必须要返回一个HttpResponse对象
HttpResponse
render
redirect
JsonResponse
from django.http import JsonResponse
def xxx(request):
user_dict = {'username':'reba好美哦 我好喜欢!','password':'123'}
# json_str = json.dumps(user_dict,ensure_ascii=False)
# return HttpResponse(json_str)
l = [1,2,3,4,5,6,7,8,9,]
# return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})
return JsonResponse(l,safe=False) # 序列化非字典格式数据 需要将safe改为False
前后端数据交互
json数据
form表单上传文件
action
1、必须要做到事情:
method必须是post
enctype必须是formdata
2、暂时需要做到事情:
提交post请求需要将中间件里面的有个csrfmiddleware注释掉
method post
enctype formdata
3、后端如何获取用户上传的文件
file_obj = file_request.FILES.get('前端input框name属性值')
file_obj.name # 获取文件名
for line in file_obj:
print(line)
# django 中推荐以下的写法
for chunk in file_obj.chunks():
print(chunk)
from django.db import models # Create your models here. class Book(models.Model): # id 是自动创建的 这里就不创建了 title = models.CharField(max_length=64) # price 为小数字段 这句话代表总共8位数小数位占2位 price = models.DecimalField(max_digits=8, decimal_places=2) # 书籍与出版社是一对多的关系 publish = models.ForeignKey(to='Publish') # 这句话的意思代表书籍表与出版社表关联,默认关联字段就是出版社的主键字段 # publish = models.ForeignKey(to=Publish) # 注意: to后面也可以直接书写表的变量名, 但是需要保证该变量名在当前位置的上方先定义 # 书籍与作者是对对多外键关系 authors = models.ManyToManyField(to='Author') # 书籍与作者是多对多关系 ''' authors 字段是一个虚拟字段 不会真正的在表中创建出来,只是 用来告诉Django orm 需要创建书籍和作者的第三方关系表 ''' class Publish(models.Model): name = models.CharField(max_length=64) addr = models.CharField(max_length=64) class Author(models.Model): name = models.CharField(max_length=32) phone = models.BigIntegerField() # 一对一外键关系建立 author_detail = models.OneToOneField(to='AuthorDetail') class AuthorDetail(models.Model): age = models.IntegerField() addr = models.CharField(max_length=255)
"""day51 URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url, include # 使用路由分发必须使用一个某块include from django.contrib import admin from app01 import views # from app01 import urls as app01_urls # from app02 import urls as app02_urls urlpatterns = [ url(r'^admin/', admin.site.urls), # # # 无名分组 # # url(r'^test/([0-9]{4})/', views.test), # 此时访url必须要跟0-9之间的4个数字才可以访问,后面又把它变成无名分组 # # # # 有名分组 # # url(r'^test/(?P<year>\d+)/', views.test), # 固定写法?P<year>,P是大写,d+表示匹配一个或者多个 # # # # 无名有名能否混合使用 >>> 不能!!! # # url(r'^test/(\d+)/(?P<year>\d+)/', views.test), # # # 虽然不能混合使用 但是同一种命名方式 可以使用多个 # url(r'^test/(\d+)/(\d+)', views.test), # # # # 普通反向解析 # # url(r'^testadd/', views.testadd, name='add'), # # # # 无名分组反向解析 # # url(r'^testadd/(\d+)/', views.testadd, name='add'), # # # 有名分组反向解析 # url(r'^testadd/(?P<year>)(\d+)/', views.testadd, name='add'), # # 首页匹配固定写法 # url(r'^$', views.home) # # # 尾页(不完善) # # url(r'', views.error) # 路由分发 注意事项应用名后面千万不可加$符号 # url(r'^app01/',include(app01_urls)), # url(r'^app02/',include(app02_urls)) # 路由分发简写,加上名称空间 # url(r'^app01/', include('app01.urls', namespace='app01')), # url(r'^app02/', include('app02.urls', namespace='app02')) # 按照app前缀去写就不用写名称空间了 url(r'^app01/', include('app01.urls')), url(r'^app02/', include('app02.urls')), url(r'^index/', views.xxx) ] ''' 无名分组反向解析,Django会不知道你到底要解析哪一个,所以你得告诉Django你到底要解析哪一个 testadd/1 testadd/123 testadd/123456 '''
from django.shortcuts import render, HttpResponse, redirect, reverse # 后端解析需要导一个reverse # Create your views here. # 无名分组 # def test(request, xxx): # print(xxx) # return HttpResponse('test') # 有名分组 # def test(request, year): # # print(year) # # return HttpResponse('test') # 无名有名分组混合使用 def test(request, xxx, year): print('xxx', xxx) print('yyy', year) return HttpResponse('test') def testadd(request, xxx): return HttpResponse('testadd') def home(request): # 无名分组反向解析 _url = reverse('add', args=(12,)) # 无名分组反向解析需要告诉Django你到底要解析哪一个 # # ********你告诉Django要反向解析的这个数字 ,通常都是数据的主键值********# # 有名分组反向解析 _url = reverse('add', kwargs={"year":1}) print(_url) # return HttpResponse('欢迎欢迎') return render(request, 'home.html') ''' url(r'^edit_user/(\d+)', views.edit_user, names='edit) <a href='edit_user/user_obj.id/'>编辑</a> def edit_user(request, edit_id): reverse('edit', args=(edit_id,)) ''' def error(request): return HttpResponse('404 error') def index(request): print(reverse('app01_index')) # 这个是给每个APP器名时加上前缀的写法 # print(reverse('app01:index')) # 这个是加上namespace的写法 return HttpResponse('from app01 index') import json from django.http import JsonResponse def xxx(request): user_dic = {'username': 'yafeng好帅啊 我好喜欢', 'password': '123'} # json_str = json.dumps(user_dic, ensure_ascii=False) # 代表不需要转码 # return HttpResponse(json_str) l = [1,2,3,4,5,6,7,8,9] # 用JsonResponse来实现 # return JsonResponse(user_dic, json_dumps_params={'ensure_ascii': False}) return JsonResponse(l, safe=False) # 序列化非字典格式的数据 需要该为false
from django.conf.urls import url from app01 import views urlpatterns = [ # 这里加不加$随便你,没有关系 url(r'^index/', views.index, name='app01_index') ]
from django.conf.urls import url from app01 import views urlpatterns = [ # 这里加不加$随便你,没有关系 url(r'^index/', views.index, name='app01_index') ]
from django.shortcuts import render, HttpResponse, redirect, reverse # Create your views here. def index(request): print(reverse('app02_index')) # 这个是给每个APP器名时加上前缀的写法 # print(reverse('app02:index')) # 这个是加上namespace的写法 return HttpResponse('from app02 index')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> </head> <body> <a href="{% url 'add' 111 %}">111</a> <a href="{% url 'add' 121 %}">111</a> <a href="{% url 'add' 123 %}">111</a> <a href="{% url 'add' 1234 %}">111</a> <a href="{% url 'add' year=1134 %}">111</a> <a href="{% url 'app01:index' %}"></a> </body> </html>