Title

Django创建表关系,Django生命周期图之路由层和视图层简介

一.Django框架中rom的表关系创建

1).表与表的关系

2)以图书管理系统为例,创建几张关系表

注意:**在models.py中书写表关系的时候,要先写好基表,再写外键关系

 1 # 先建立好三张基表
 2 from django.db import models
 3  4 # Create your models here.
 5 #书表
 6 class Book(models.Model):
 7     # id 是自动创建的,除非id名自定义
 8     title = models.CharField(max_length=255)
 9     # price为小数字段,表示总共8位数,其中小数占2位
10     price = models.DecimalField(max_digits=8, decimal_places=2)
11 12 13 #出版社表
14 class Publsh(models.Model):
15     name = models.CharField(max_length=64)
16     addr = models.CharField(max_length=64)
17 18 19 # 作者表
20 class Author(models.Model):
21     name = models.CharField(max_length=64)
22     # 比Int类型长一些
23     phone = models.BigIntegerField()
  • 图书表

    图书和出版社是一对多的关系

    外键字段应该建立在多的一方

 1 #书表
 2 class Book(models.Model):
 3     # id 是自动创建的,除非id名自定义
 4     title = models.CharField(max_length=255)
 5     # price为小数字段,表示总共8位数,其中小数占2位
 6     price = models.DecimalField(max_digits=8, decimal_places=2)
 7     # 书籍表和出版社表是一对多关系,外键字段要建立在书籍表中
 8     Publish = models.ForeignKey(to='Publish') # 并且默认关联字段就是出版社表的主键字段,里面有个to_field参数能够修改关联的字段
 9     # 补充:to后面需要加引号,若Publish表在Book表上方,则不需要加引号
10     # Publish = models.ForeignKey(to=Publish)
11 12     # 书籍与作者是多对多关系,外键字段建立在较为频繁的书籍表中,通过ManytoManyField方法就能建立第三张表
13     Authors = models.ManyToManyField(to='Author')
14     # 补充注意:Authors字段只是一个虚拟字段,并不会在Book表中创建出来,只是告诉Django ORM为多对多关系创建一个第三方表
15
  • 出版社表

1 #出版社表
2 class Publish(models.Model):
3     name = models.CharField(max_length=64)
4     addr = models.CharField(max_length=64)
5
  • 作者表

    图书表与作者表是多对多的关系

    理论上外键字段在哪张表都一样,但是推荐外键字段建立在查询较为频繁的表中

作者表与作者详情表是一对一关系

同样,还是推荐将外键字段建立在使用频率较高的表中

原因:就是为了方便后面我们基于ORM查询

# 作者表
class Author(models.Model):
    name = models.CharField(max_length=64)
    # 比Int类型长一些
    phone = models.BigIntegerField()
​
    # 作者表与作者详情表一对一关系
    Author_detail = models.OneToOneField(to='AuthorDetail')

  • 作者详情表
1 # 作者详情表
2 class AuthorDetail(models.Model):
3     age = models.IntegerField()
4     addr = models.CharField(max_length=64)
 

3)开始创建

4)查看

 

下载之后点一下apply,将新出现的数据库链接删除,重新点一下db.sqlite3,就会出现数据库了

 

二.Django请求生命周期流程图

 

 

三.路由层

一般我们刚创建好的Django框架中的urls.py里面存放的是总路由,即总的路由和视图函数或app的对应关系

由上面我们可以得到:

url方法第一个参数是一个正则表达式,路由匹配按照正则匹配, 一旦正则能够匹配到内容 会立刻执行对应的视图函数,不会再继续匹配了

注意:用户输入url尾缀路由若没有加最后的斜杠,django会默认将斜杠加上,看下图(将urls中的路由加上/)

补充:你可以在配置文件settings中指定是否开启该功能APPEND_SLASH = True/False(默认是True处于打开状态,改为False则失效)

 

 

 

补充:网站首页一打开是非常不友好的,可以通过正则(^$)设置一下

1.无名分组和有名分组

基于路由层的路由是由正则表达式写的,我们可以在表达式后面来一个数字,如下,表示test后面必须加上四个一到九的数字才能访问

url(r'^test/[0-9]{4}', views.test),

 

但是,如果给正则表达式加一个括号,就表示分组的意思

url(r'^testadd/([0-9]{4})', views.testadd),

会产生下面的报错信息

解决方法:

在视图函数中给一个形参接收这个数字就可以了

def testadd(request, xxx):
    return HttpResponse('testadd')

什么叫无名分组?

在路由匹配的时候给某段正则表达式加了括号,匹配的时候会将括号内的正则表达式匹配到的内容当作位置参数传递给对应的视图函数,因此视图函数就要多准备一个形参去接收这个位置参数

什么叫有名分组?

给一段正则表达式起一个别名 匹配的时候会将括号内正则表达式匹配到的内容当做关键字参数传递给对应的视图函数

#有名分组
# \d+表示一个或多个数字,?P<ttt>固定写法,表示给正则表达式命名为ttt
    url(r'^testadd/(?P<ttt>\d+)', views.testadd)

 

解决方法:在视图函数中添加关键字形参去接收关键字实参

# ttt就是接收关键字参数的
def testadd(request, ttt):
    return HttpResponse('testadd')

注意:

  • 无名有名不能混合使用

url(r'^test/(\d+)/(?P<year>\d+)/', views.test)
# 会报找不到位置参数的错误
  • 无名有名虽然不能混合使用,但是同一种命名方式 可以使用多个

1 #无名叠加
2 url(r'^test/(\d+)/(\d+)/',views.test)
3 4 #有名叠加
5 url(r'^test/(?P<xxx>\d+)/(?P<year>\d+)/',views.test),

2.反向解析

通过一些方法 能够得到一个结果 该结果可以访问到对应的url

例如:可以通过html页面访问另一个url页面

但是,如果将urls中的test名修改了,后面的html所有关于这个的url链接都将失效

如何解决此问题?

  • 第一步:

    先给路由与视图函数对应关系 起一个名字(相当于赋予一个令牌)

url(r'^test/',views.testadd,name='add')

  • 第二步:若是前端解析,将{% url ‘add’ %}添加html中

<a href="{% url 'add' %}">2222</a>

  • 第三不:若是后端解析,先在view.py导入reverse模块

 1 url(r'^test/',views.testadd,name='add')
 2  3  4 from django.shortcuts import  reverse
 5  6 def test(request):
 7     _url = reverse('add')
 8     print(_url)
 9     
10 # 通过reverse 模块就能拿到test路由名  
11 # /test/
12 # 注意:一定要提前命名name,否则reverse会找不到

3.有名无名反向解析

分析:先在urls.py文件中建立有名无名分组

# 无名分组
url(r'^testadd/([0-9]{4})', views.testadd, name='add'),
    
 #有名分组
url(r'^test/(?P<ttt>\d+)', views.test),

然后views中导入reverse模块,运行home函数

  • 无名后端反向解析

 1 from django.shortcuts import render, HttpResponse, redirect, reverse
 2  3 def test(request, ttt):
 4     return HttpResponse('test')
 5  6  7 def testadd(request, xxx):
 8     return HttpResponse('testadd')
 9 10 11 def home(request):   
12     _url = reverse('add')
13     print(_url)
14     return HttpResponse('欢迎欢迎')

运行后会报错,表示查找add时缺一个参数

报错原因: 访问这条url:url(r'^testadd/([0-9]{4})', views.testadd, name='add')

必须以/testadd/1234/这种形式,否则无法访问,因此结果的不确定性让Django无法做判断,因此需要人为的给予数字

解决方案:在_url = reverse('add')中随意给一个数字,例如

_url = reverse('add', args=(1234,))
# 注意:此处要以元组的形式添加
#补充:这个数字通常就是数据的主键值

再次启动访问home的url就能得到/testadd/1234,通过他就能访问testadd这条url了

  • 无名前端反向解析

def home(request):
    return render(request, 'home.html')
1 <!--同样需要根据正则分组规则添加相应的数字-->
2 3 <a href="{% url 'add' 1234 %}">2222</a>
4 <a href="{% url 'add' 5628 %}">2222</a>
5 <a href="{% url 'add' 3637 %}">2222</a>
6 <a href="{% url 'add' 3839 %}">2222</a>

同上可以获得

# 有名分组
url(r'^test/(?P<ttt>\d+)', views.test)
  • 有名后端反向解析

1 # 在reverse中添加kwargs={‘名字’:符合正则的符号}
2 def home(request):   
3     _url = reverse('add', kwargs={'year':11})  # 标准写法
4     _url = reverse('add', args=(11,)
5     print(_url)
6     return HttpResponse('欢迎欢迎')
  • 有名前端反向解析

{% url 'add' 1 %}  # 推荐使用
{% url 'add' year= 1 %}  # 标准的

总结:

1 # 无名分组反向解析:
2 3 url(r'^testadd/(\d+)/',views.testadd,name='add')
4         #前端解析
5             {% url 'add' 1 %}
6         #后端解析
7             reverse('add',args=(12,))
#
 1 有名分组反向解析:
 2  3 url(r'^testadd/(\d+)/',views.testadd,name='add')
 4  5 #前端解析
 6 {% url 'add' 1 %}  # 推荐使用
 7 {% url 'add' year = 1} # 标准使用
 8 #后端解析
 9 _url = reveerse('add', args=(11,) # 推荐使用
10 _url = reveerse('add', kwargs={'year':11} # 标准使用          
#伪代码
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,))
​
    """

4.路由分发

1).什么是路由分发?

    当django项目比较庞大的时候 路由与视图函数对应关系较多
总路由代码太多冗长
考虑到总路由代码不好维护 django支持每个app都可以有自己的urls.py
并且总路由不再做路由与视图函数的对应关系 而仅仅只做一个分发任务的操作

根据请求的不同 识别出当前请求需要访问的功能属于哪个app然后自动
下发到对应app里面的urls.py中 然后由app里面的urls.py做路由与视图函数的匹配

不仅如此每个app除了可以有自己的urls.py之外 还可以有自己的static文件夹 templates模板文件
基于上面的特点 基于django分小组开发 会变得额外的简单
每个人只需要开发自己的app即可 之后只需要创建一个空的django项目
将多个人的app全部拷贝项目下 配置文件注册
总路由分发一次

2).如何实现一个简单的路由分发

第一步:在控制台通过命令行创建多个app文件夹,并将app在settings中注册

第二步:建立urls,导入include模块,各种导入,看下图

 

注意事项:

urlpatterns = [
    url(r'^admin/', admin.site.urls),    
    url(r'^app01/', include(app01_urls)),
    url(r'^app02/', include(app02_urls)),
]
# 在总路由^app01/后面千万不能添加$,否则无法找到

补充:简写写法

# 将导入和路由对应关系全部拿掉,用app.urls的方法
    # 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'))

5.名称空间(了解)

问题:如果在不同的app中的路由对应关系中添加了相同的name,那么在反向解析的时候能否准确找到

结果是一定的,不能准确找到,那如何解决这个问题?

可以再主路由对应列表中添加namespace属性,如下所示

url(r'^app01/', include('app01.urls', namespace='app01'))
url(r'^app02/', include('app02.urls', namespace='app02'))
后端解析
reverse('app01:id')
reverse('app02:id')
​
前端解析
{% url 'app01:index' %}
{% url 'app02:index' %}

如何避免使用这种方法?

1 # 在给路由与视图函数起别名(name的名字)的时候只需要保证永远不出现冲突的情况即可
2 # 通常情况下我们推荐期别名的时候加上当前应用的应用名前缀
3 #例如
4 url(r'^index/',views.index,name='app01_index')
5 url(r'^index/',views.index,name='app02_index')

6.虚拟环境

为什么使用虚拟环境?

我们想做到针对不同的项目 只安装项目所需要的功能模块 项目用不到的一概不装 来避免加载资源时的消耗

注意:这次创建需要用以前创立好的虚拟环境来创立

虚拟环境就类似于一个纯净的python解释器环境
每创建一个虚拟幻境就类似于你重新下载一个python解释器
另外虚拟环境不推荐使用太多,可以创建几个玩一玩,否则占用资源

7.DJango版本区别

路由层:

  • 用的是url,第一个参数是正则表达式

  • 2.x和3.x用的path,第一个参数不是正则表达式,写的是什么就匹配什么,精确匹配

1.x版本
urlpatterns = [
    url(r'^admin/', admin.site.urls),
]
2.x和3.x版本
urlpatterns = [
   path('admin/', admin.site.urls),
]

如果觉得path不好用 2.x、3.x给你提供了一个跟url一样的功能 re_path 等价于1.x里面的url功能

虽然path不支持正则表达式 但是它给你提供了五种默认的转换器

  • str:匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式

  • int:匹配正整数,包含0。

  • slug,匹配字母、数字以及横杠、下划线组成的字符串。

  • uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。

  • path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)

除了默认的五种转换器之外 还支持你自定义转换器

自定义转换器示例:

  1. 在app01下新建文件path_ converters.py,文件名可以随意命名

    Copy
    class MonthConverter:
        regex='\d{2}' # 属性名必须为regex
    
        def to_python(self, value):
            return int(value)
    
        def to_url(self, value):
            return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
    
  2. 在urls.py中,使用register_converter 将其注册到URL配置中:

    Copy
    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'),
    
    ]
    
  3. views.py中的视图函数article_detail

    Copy
    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')
    
  4. 测试

    Copy
    # 1、在浏览器输入http://127.0.0.1:8000/articles/2009/12/hello/,path会成功匹配出参数year=2009,month=12,other='hello'传递给函数article_detail
    # 2、在浏览器输入http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配失败,因为我们自定义的转换器mon只匹配两位数字,

8.伪静态

伪静态 url以.html结尾 给人的感觉好像是这个文件是写死的 内容不会轻易的改变 伪静态

为了提高你的网站被搜索引擎收藏的力度 提供网站的SEO查询效率

但是 无论你怎么做优化 都抗不过RMB玩家

四.视图层

1)JsonResponse

视图函数必须要返回一个HttpResponse对象,因为比如这三板斧都是直接或间接的继承HTTresponse

  • HttpResponse

 

 

  • render

  • redirect

JsonResponse

前后端数据交互

举例:

from django.http import JsonResponse
def xxx(request):
    user_dict = {'username':'jason','password':'123'}
    json_str=json.dumps(user_dict)
    return HttpResponse(json_str)

若添加中文进去,就会发生自动转码的事情,如下图

from django.http import JsonResponse
def xxx(request):
    user_dict = {'username':'jason好帅哦 我好喜欢!','password':'123'}
    json_str=json.dumps(user_dict)
    return HttpResponse(json_str)

原因:

所以只要将ensure_ascii修改成False即可

json_str = json.dumps(user_dict,ensure_ascii=False)

也可以使用JsonResponse

return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})

若是一个列表

l = [1,2,3,4,5,6,7,8,9,]
return JsonResponse(l,safe=False)  # 序列化非字典格式数据 需要将safe改为False

forml表单

1.必须做的事
  method必须是post
  enctype必须是formdata
2.暂时需要做的
  提交post请求需要将中间件里面的一个csrfmiddleware注释掉

后端如何获取用户上传的文件
  file_obj = request.FILES.get('前端input框name属性值')
  file_obj.name # 文件名
  for line in file_obj:
  print(line)

# django中推荐以下写法
  for chunk in file_obj.chunks():
  print(chunk)

render原理

def ab_render(request):
  temp = Template("<h1>{{ user_dict }}{{ user_dict.username }}{{ user_dict.password }}</h1>")
  user_dict = Context({'user_dict':{'username':'jason','password':123}})
  res = temp.render(user_dict)
  return HttpResponse(res)

补充:视图函数不一定是函数,也有可能是类

FBV:基于函数的视图
CBV:基于类的视图

CBV基本写法

from django.views import View


class MyLogin(View):
        def get(self,request):
            return render(request,'login.html')
        def post(self,request):
            return HttpResponse('我是类里面的post方法')


url(r'^login/',views.MyLogin.as_view())

朝login提交get请求会自动执行MyLogin里面的get方法
而提交post请求也会自动执行MyLogin里面的post方法
为什么MyLogin针对不同的请求方法能够自动执行对应的方法

研究源码的突破口
url(r'^login/',views.MyLogin.as_view())

猜想

as_view要么是类里面定义的普通函数 @staticmethod
要么是类里面定义的绑定给类的方法 @classmethod
看源码发现是绑定给类的方法@classonlymethod,变成类绑定方法,并且as_view返回的是一个view函数
url(r'^login/',views.MyLogin.as_view())
等价于
url(r'^login/',views.MyLogin.view)
FBV和VBV在路由匹配上是一样的,都是路由与函数内存地址的对应关系
并且这个view函数引用了外部的类,他就是一个闭包函数,这个方法给自己定义一些属性,返回一个self.dispatch,我们都知道对象.属性的查找顺序是先查找对象自己的,再查找类的,然后再查找父类的,这里吗就会按顺序找到父类view 的dispatch方法
dispatch方法中会先判断请求方式是不是默认的八个请求方式:get,post,put。patch,delete,head,options,trace
然后通过反射getattr从我们自己写的类的对象中找请求方式,没有就会报错,该请求方式不允许,有就会调用这个方法并执行,这就是为什么cbv模式下能够自动执行我们定义的方法

 

 



 

posted @ 2020-01-07 01:55  Mr江  阅读(339)  评论(2编辑  收藏  举报