Django之深入了解路由层

ORM表关系建立

换位思考,先粘在一张表上面分析, 然后再站在另一张表上分析

一对一

外键字段创建在任意一张表都可以,简易在查询频率较高的一方添加

OneToOneField

models.OneToOneField(to='Author_detail')  # fk + unique

一对多

外键字段创建在多的那一方

ForeignKey

models.ForeignKey(to='Publish')  # to用来指代跟哪张表有关系 默认关联的就是表的主键字段

多对多

外键关系需要创建第三张表来处理。

ManyToManyField

models.ManyToManyField(to='Author')  # django orm会自动帮你创建第三张关系表

Django 请求生命周期

url 路由层

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

url的第一个参数其实就是一个正则表达式,只要该正则表达式能够匹配到内容,就会立刻执行后面的视图函数,不再往下继续匹配。

路由匹配

Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'。

我们定义了urls.py:

from django.conf.urls import url
from app01 import views

urlpatterns = [
        url(r'^blog/$', views.blog),
]

其效果就是:

访问 http://www.example.com/blog 时,默认将网址自动转换为 http://www.example/com/blog/

如果在settings.py中设置了 APPEND_SLASH=False,此时我们再请求 http://www.example.com/blog 时就会提示找不到页面。

Django 路由匹配的规律:

  1. 先是不加斜杠,先匹配依次试试,
  2. 如果匹配不上,会让浏览器重定向,url最后加上斜杠再次匹配一次,
  3. 如果还是匹配不上就报错了。
  4. 并且不会匹配GET请求?后面携带的参数

无名分组

将分组内正则表达式匹配到的内容当做位置参数传递给视图函数

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

访问的url:http://127.0.0.1/test/1111
		
# 当你的路由中有分组的正则表达式  那么在匹配到内容
# 执行视图函数的时候 会将分组内正则表达式匹配到的内容当做位置参数传递给视图函数  
def test(request,xxx):
    '''
    此时url有第二个参数,会传递给视图函数位置参数传进来。
    如果不写接收的位置参数则会报错
    test() takes 1 positional argument but 2 were given
    '''
    print(xxx)	# 1111
    pass

有名分组

将分组内正则表达式匹配到的内容当做关键字参数传递给视图函数

url(r'^test/(?P<year>[0-9]{4})/', views.test)

访问的url:http://127.0.0.1/test/1111
		
# 当你的路由中有分组的正则表达式  那么在匹配到内容
# 执行视图函数的时候 会将分组内正则表达式匹配到的内容当做关键字参数传递给视图函数  
def test(request,year):
    '''
    此时url有第二个参数,会传递给视图函数关键字参数传进来。
    如果不写接收的关键字参数则会报错
    testadd() got an unexpected keyword argument 'year'
    '''
    print(year)	# 1111
    pass

利用有名个无名分组 我们就可以在调用视图函数之前给函数传递额外的参数

注意:无名分组和有名分组不能混合使用

但是同一种分组的情况下 可以使用多次,
无名可以有多个

有名可以有多个

反向解析

给路由匹配设置一个别名,

根据这个别名,动态解析出一个结果,该结果可以直接访问对应的url。

路由匹配条件无分组的情况的反向解析

urls.py:
url(r'^home/',views.home,name='hm'),
url(r'^index/$',views.index),


views.py:
def home(request):
    return HttpResponse(reverse('hm'))


def index(request):
    print(reverse('hm'))	# /home/
    return render(request,'test.html')

test.html:
<body>
<div>TEST</div>
<p><a href="{% url 'hm' %}">点我点我</a></p>
</body>

当我们在浏览器URL输入http://127.0.0.1:8000/index/的时候,
python后端进行reverse反向解析成 别名为'hm'的路径前缀,也就是home路径,
用test.html 页面渲染,html也可以使用反向解析

python后端使用反向解析:
	reverse('hm')
前端html使用反向解析:
        {% url 'hm' %}
        将路径解析为/home/。

无名分组情况的反向解析

urls.py:
url(r'^home/(\d+)/',views.home,name='hm'),
url(r'^index/',views.index),

views.py:
def home(request,xxx):
    # 使用无名分组,视图函数必须写位置参数
    print(xxx)
    return HttpResponse('ok')


def index(request):
    print(reverse('hm',args=(1,)))	# 无名分组需要手动给别名传递一个参数才能匹配上,这个参数会传递到视图函数当做位置参数。
    return render(request,'test.html')

test.html:
<body>
<div>TEST</div>
<p><a href="{% url 'hm' 1 %}">点我点我</a></p>
</body>

当一个无名分组使用别名的时候,在浏览器URL输入http://127.0.0.1:8000/index/,python后端进行reverse反向解析成 别名为'hm'的路径前缀,也就是home路径,需要手动给解析出来的路径加参数,不然匹配不成功
用test.html 页面渲染,并且html使用反向解析,也需要手动传入参数
        
python后端使用反向解析:
	reverse('hm',args=(1,))
前端html使用反向解析:
        {% url 'hm' 1 %}
        将路径解析为/home/1。

有名分组的反向解析

urls.py:
url(r'^home/(?P<year>\d+)/',views.home,name='hm'),
url(r'^index/',views.index),

views.py:
def home(request,year):	
    # 使用有名分组,视图函数必须写关键字参数
    print(year)
    return HttpResponse('ok')

def index(request):
    print(reverse('hm',args=(1,)))			# 这样也可以,但不正规
    print(reverse('hm',kwargs={"year":1}))	# 最正规的写法,因为有名分组传入的是关键字参数,用kwargs参数来表示
    return render(request,'test.html')

test.html:
<body>
<div>TEST</div>
<p><a href="{% url 'hm' 1 %}">点我点我</a></p>		# 这样也可以,但不正规
<p><a href="{% url 'hm' year=1 %}">点我点我</a></p>	# 最正规的写法,因为有名分组传入的是关键字参数
</body>

当一个无名分组使用别名的时候,在浏览器URL输入http://127.0.0.1:8000/index/,python后端进行reverse反向解析成 别名为'hm'的路径前缀,也就是home路径,需要手动给解析出来的路径加参数,不然匹配不成功。
用test.html 页面渲染,并且html使用反向解析,也需要手动传入参数
        
python后端使用反向解析:
	reverse('hm',kwargs={"year":1})
前端html使用反向解析:
        {% url 'hm' year=1 %}
        将路径解析为/home/1。

例子

伪代码以编辑用户信息为例,演示具体用法:

urls.py:

url(r'^edit_user/(\d+)',views.edit_user,name='edit'),

views.py:
def edit_user(request,edit_id):
    # 查出来所有 user_list 
    # edit_id 就是用户想要编辑数据的主键值
    print(edit_id)
    return render(request,'edit_user.html',{"user_list":user_list})

edit_user.hmtl:
{% for user_obj in user_list %}
    <a href="/edit_user/{{ user_obj.id }}">编辑</a>
    <a href="{% url 'edit' user_obj.id %}">编辑</a>
{% endfor %}

路由分发

前言,在django中所有的app都可以有自己的独立的urls.py、templates、static文件夹

那么正是由于这个特点,你的django项目可以由多个人一起开发。小组长最后只需要把所有的开发的app整合到一个空的django项目里面,在settings配置文件里注册就可以了。

路由分发解决的就是项目的总路由匹配关系过多的情况,使用路由分发 会将总路由不再做匹配的活 而仅仅是做任务分发

我们新建一个app02,现在我的目录结构是:

python3 manage.py startapp app02
  • app01
  • app02
  • mysite
  • static
  • templates
  • manage.py

好了之后

我们在app01目录中创建urs.py

app01/urls.py:
from app01 import views
from django.conf.urls import url
urlpatterns = [
    url(r'^reg/',views.reg),
]

app01/views.py:
def reg(request):
    return HttpResponse("app01 reg")


-----------------------------------
app02/urls.py:
from app02 import views
from django.conf.urls import url
urlpatterns = [
    url(r'^reg/',views.reg),
]

app02/views.py:
def reg(request):
    return HttpResponse("app02 reg")


总urls:mysite/urls.py:
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
    url(r'^app01/',include(app01_urls)),
    url(r'^app02/',include(app02_urls)),
]

或者更省事的写法,不用把两个app的urls.py导过来:
urlpatterns = [
    url(r'^app01/',include('app01.urls')),
    url(r'^app02/',include('app02.urls')),
]

最终效果,在浏览器url中输入
http://127.0.0.1:8000/app01/reg		# app01 reg
http://127.0.0.1:8000/app02/reg		# app02 reg

名称空间

当多个app中出现了起别名冲突的情况 你在做路由分发的时候 可以给每一个app创建一个名称空间,然后在反向解析的时候 可以选择到底去哪个名称空间中查找别名

url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02'))
		
# 后端
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='app01_reg')]
urlpatterns = [url(r'^reg/',views.reg,name='app02_reg')]

伪静态

将一个动态网页伪装成一个静态的网页,来提高搜索引擎SEO的查询频率,提高网站的曝光度!

怎么做呢,比如博客园是一个动态网站,但是看起来像一个静态网站,是因为每篇文章都有后缀名.html

那么我们也可以写,在路由匹配规则中写上

# https://www.cnblogs.com/qinyujie/p/11394671.html

url(r'^article/(\d+).html',views.article)

虚拟环境

虚拟环境就相当于重新下载了一个纯净的python解释器,之后项目用这个虚拟环境,你需要什么就安装什么,与系统环境上存在的软件不冲突。

Django版本区别

urls.py路由匹配的方法有区别。

django 1.X

导入的模块是

from django.conf.urls import url

urlpatterns 中 url 对应的是正则表达式,

如下:

from django.conf.urls import url
urlpatterns = [
    url = ('test',view.test)
]

django 2.X

导入的模块是from django.urls import re_path,path

path中第一个参数不支持正则表达式,写了什么就只能匹配什么,匹配不到就报错。
django 2.X 觉得你习惯了之前的正则表达式来匹配,特别人性化 ,

提供一个re_path方法,这个方法就是1.X版本中的url()

如下:

from django.urls import path,re_path
urlpatterns = [
    path = ('test',view.test),
    re_path = (r'^test/(\d+)',view.test)
]

django 2.X的版本中还提供了五种转换器,

  • str:匹配除路径分隔符/外的字符串
  • int:匹配自然数
  • slug:匹配字母,数字,横杠及下划线组成的字符串
  • uuid:匹配uuid形式的数据
  • path:匹配任何字符串,包括路径分隔符/
urlpatterns = [
    path = ('test/<str:\d+>',view.test)
    path = ('test/<int:\d+>',view.test)
    path = ('test/<slug:\d+>',view.test)
    path = ('test/<uuid:\d+>',view.test)
    path = ('test/<path:\d+>',view.test)
]

除了内置的五种转换器外,还可以自定义自己的转换器。

  • 在应用文件夹下创建converter.py文件
  • 在converter.py文件中创建自定义类
  • 在类中定义regex 正则,to_python方法,to_url方法

class CVT185Phone:
    regex = '185\d{8}'
    def to_python(self, value):
        return int(value)
    def to_url(self, value):
        return '%11d' % value
    

from django.urls import register_converter
from app.converter import CVT185phone
register_converter(CVT185phone,'phone185')

path('page/<phone185:msg>/',views.page,name="pages")

小例子

通过路由分发,使用app01上传文件的功能上传一个文件;

mysite/urls.py
urlpatterns = [
    url(r'^app01/',include('app01.urls')),
    url(r'^app02/',include('app02.urls')),
]

app01/urls.py
from app01 import views
from django.conf.urls import url
urlpatterns = [
    url(r'^upload/',views.upload,name='app01_upload'),
]

app01/views.py
from django.shortcuts import reverse,render,HttpResponse,redirect
def upload(request):
    if request.method == "POST":
        print(request.FILES)
        file_obj = request.FILES.get("myfile")
        with open(file_obj.name,"wb") as f:
            for i in file_obj:
                f.write(i)
    return render(request,'upload.html')


templates/upload.html
<body>
<form action="" method="post" enctype="multipart/form-data">
    请选择文件
    <input type="file" name="myfile">
    <input type="submit" class="btn btn-primary">提交
</form>
posted @ 2019-11-25 18:44  GeminiMp  阅读(174)  评论(0编辑  收藏  举报