Django--路由层、视图层、模版层
路由层:
路由匹配
url(正则表达式,视图函数内存地址)
只要正则匹配到了内容,就不再往下匹配,而是直接运行后面的视图函数
匹配首页)
url(r'^&', home)
匹配尾页
url(r'', errors)
路由匹配的规律
第一次是按照用户输入什么就匹配什么
第二次是Django会让浏览器自动在尾部加上斜杠再次匹配,要是还是匹配不上就不报错
上述的规律不是浏览器的特点,而是Django的功能
Django settings文件夹中
APPEND_SALSH = True # 这个是自动加斜杠的
无名分组
路由匹配成功之后,如果正则表达式中有分组,那么在调用视图函数的时候会将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
url(r'^index/(\d+)/', index)
def index(request, args):
return Httpresponse对象
有名分组
路由匹配成功之后,如果正则表达式中有有名分组并且起了别名的话,那么在调用视图函数的时候会将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
url(r'^index/(?P<year>\d+)/', index)
def index(request, index):
return Httpresponse对象
有名分组和无名分组不能混合使用,但是两者可以单独使用多个
反向解析
由来是很多html页面和后端代码中频繁使用了指名道姓的url(/index/)
那么这个时候如果一旦发生了变化,前后端所有的地方都需要修改,造成了可维护性比较差
反向解析的本质就是通过某个方法获取的一个能够访问对应的url的结果
没有涉及到正则表达式符号的情况下
url(r'^index/', index, name='index')
前端:
{% url 'aaa' %}
后端:
from django.shortcuts import HttpResponse,render,redirect,reverse
url = reverse('index')
无名分组和有名分组的情况
无名:
url(r'^index/(\d+)/',index,name='bbb')
前端:
{% url 'bbb' 1 %}
后端:
url = reverse('aaa',args=(1,))
有名:
url(r'^index/(?P<edit_id>\d+)/',index,name='bbb')
前端:
{% url 'bbb' 1 %}
{% url 'bbb' year=1 %}
后端:
url = reverse('aaa',args=(1,))
url = reverse('aaa',kwargs={'year':1})
路由分发
include()
django中的每一个app都可以是独立的urls.py, static静态文件, templates模版文件夹
真是基于上面的特点,Django就可以很好的支持多人分组开发,每个人只需要开发各自的app就可以了,最后只需要创建一个空的Django项目将所有人的app注册进来,总路由中来一个路由分发就可以将多个人的功能整合到一个项目中了
当项目比较巨大的时候,如果不使用路由分发,那么路由中的url代码太多了,不利于维护和更新
from app01 import urls as app01_urls
from app02 import urls as app02_urls
url(r'应用名前缀/',include(app01_urls))
url(r'应用名前缀/',include(app02_urls))
# 推荐使用下面的方式
url(r'应用名前缀/',include('app01.urls'))
url(r'应用名前缀/',include('app02.urls'))
路由分发要注意的点:
总路由中的最后千万不要加$符号
名称空间
你在起别名的时候,虽然能够同一个应用下不重名,但是你没办法实现多个人多个app不重名的情况
一旦重名在进行反向解析的话就不能识别所在的应用了
在总路由中 给每一个应用开设一个名称空间
url(r'应用名前缀/',include('app01.urls',namespace='app01'))
url(r'应用名前缀/',include('app02.urls',namespace='app02'))
子路由:
url(r'^index/',index,name='aaa')
url(r'^index/',index,name='aaa')
反向解析的时候:
reverse('app01:aaa')
reverse('app02:aaa')
{% url 'app01:aaa' %}
{% url 'app02:aaa' %}
其实你可以完全不使用名称空间 只需要保证别名不重复就行,也就是你在起别名的时候给每个别名加上所在应用名的前缀
伪静态
就是在url后面加上一个.html
虚拟环境
不同的项目应该有属于该项目独有的python解释器环境,这样的话就可以避免资源的浪费以及版本兼容的问题
注意⚠️:
虚拟环境虽然好用,但是不要无限制的进行创建 你机器上的虚拟环境可以手动进行删除
Django版本之间的区别:
路由2.X里面的是path第一个参数也不再支持正则表达式,而是写什么就匹配什么,如果这个时候你还是想要使用正则进行匹配的话,这里面提供了一个re_path模块就是1.X里面的url
虽然path不再支持正则,但是他提供了五种转化器(可以将路由匹配到的内容自动转换成对应的数据格式然后再传递给后面的视图函数)
视图层
如果一个一个应用下面的视图函数特别多,这时候你就可以新建一个views文件夹,然后根据不同的功能起不同的名字的py文件存放不同的功能,实现持续的解耦合 和增加可读性
views
user.py
account.py
order.py
...
前后端分离
JsonResponse(data,safe=False,json_dumps_params={'ensure_ascii':False})
如果在son_dumps_params里面你传递的不是字典数据,那么需要手动指定safe=False
form表单上传文件
需要注意的事项:
1、method必须是post
2、enctype必须要制定成formdata
3、目前需要考虑的 需要将settings配置文件中的csrf中间件注释掉
后端不再通过request.POST获取文件数据,而是通过request.FILES
file_obj = request.FILES.get('myfile')
file_obj.name # 文件名
with open(file_obj.name,'wb') as f:
for chunk in file_obj.chunks():
f.write(chunk)
request
request.method
request.GET
request.POST
request.FILES
request.body # 获得是原生的二进制的数据
request.path # 只拿url
request.get_full_path() # url+?后面的参数
render能够返回一个html页面,并且还能够为该页面传递数据
render的内部原理
from django.template import Template,Context
def index(request):
temp = Template('<h1>{{ user }}</h1>')
con = Context({"user":{"name":'jason',"password":'123'}})
res = temp.render(con)
print(res)
return HttpResponse(res)
FBV和CBV
视图函数并不是指函数 同样也可以指类
FBV:基于函数的视图(面向函数式编程)
CBV:基于类的视图 (面向对象式编程)
# 问题:基于CBV的视图函数 """get请求来就会走类里面get方法,post请求来就会走类里面post方法 为什么???""" urls.py中 url(r'^login/',views.MyLogin.as_view()) # 由于函数名加括号的执行优先级最高,所以这一句话写完就会立刻执行as_view()方法 views.py中 from django.views import View class MyLogin(View): def get(self,request): print("from MyLogin get方法") return render(request,'login.html') def post(self,request): return HttpResponse("from MyLogin post方法")
研究方向 1.从url入手 url(r'^login/',views.MyLogin.as_view()) 由于函数名加括号执行优先级最高,所以这一句话一写完会立刻执行as_view()方法 @classonlymethod def as_view(cls, **initkwargs): # cls就是我们自己的写的类 MyLogin def view(request, *args, **kwargs): self = cls(**initkwargs) # 实例化产生MyLogin的对象 self = MyLogin(**ininkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs # 上面的几句话都仅仅是在给对象新增属性 return self.dispatch(request, *args, **kwargs) # dispatch返回什么 浏览器就会收到什么 # 对象在查找属性或者方法的时候 你一定要默念 先从对象自己这里找 然后从产生对象的类里面找 最后类的父类依次往后 return view 通过源码发现url匹配关系可以变形成 url(r'^login/',views.view) # FBV和CBV在路由匹配上是一致的 都是url后面跟函数的内存地址 2.当浏览器中输入login 会立刻触发view函数的运行 def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. # 我们先以GET为例 if request.method.lower() in self.http_method_names: # 判断当前请求方法是否在默认的八个方法内 # 反射获取我们自己写的类产生的对象的属性或者方法 # 以GET为例 handler = getattr(self,'get','取不到报错的信息') # handler = get(request) handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 直接调用我们自己的写类里面的get方法 # 源码中先通过判断请求方式是否符合默认的八个请求方法 然后通过反射获取到自定义类中的对应的方法执行
Django settings中的源码
前提:
1、Django除了暴露给用户一个settings.py配置文件之外 自己内部还有一个全局的配置文件
2、我们在使用配置文件的时候,可以直接导入暴露给用户的settings.py也可以使用Django全局的配置文件 并且一般都是后者使用居多
from django.conf import settings
3、Django的启动入口是manage.py
import os import sys if __name__ == "__main__": # django在启动的时候 就会往全局的大字典中设置一个键值对 值是暴露给用户的配置文件的路径字符串 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day54.settings") class Settings(object): def __init__(self, settings_module): # settings_module = 'day54.settings' # update this dict from global settings (but only for ALL_CAPS settings) for setting in dir(global_settings): # django全局配置文件 # dir获取django全局配置文件中所有的变量名 if setting.isupper(): # 判断文件中的变量名是否是大写 如果是大写才会执行/生效 setattr(self, setting, getattr(global_settings, setting)) # 给settings对象设置键值对 # 给settings对象设置键值对 settings[配置文件中大写的变量名] = 配置文件中大写的变量名所对应的值 # store the settings module in case someone later cares self.SETTINGS_MODULE = settings_module # 'day54.settings' mod = importlib.import_module(self.SETTINGS_MODULE) # mod = 模块settings(暴露给用户的配置文件) for setting in dir(mod): # for循环获取暴露给用户的配置文件中所有的变量名 if setting.isupper(): # 判断变量名是否是大写 setting_value = getattr(mod, setting) # 获取大写的变量名所对应的值 setattr(self, setting, setting_value) # 给settings对象设置键值对 """ d = {} d['username'] = 'jason' d['username'] = 'egon' 用户如果配置了就用用户的 用户如果没有配置就用系统默认的 其实本质就是利用字典的键存在就是替换的原理 实现了用户配置就用用户的用户没配置就用默认的 """ class LazySettings(LazyObject): def _setup(self, name=None): # os.environ你可以把它看成是一个全局的大字典 settings_module = os.environ.get(ENVIRONMENT_VARIABLE) # 从大字典中取值键为DJANGO_SETTINGS_MODULE所对应的值:day54.settings # settings_module = 'day54.settings' self._wrapped = Settings(settings_module) # Settings('day54.settings') settings = LazySettings() # 单例模式
模版层
模版语法
{{}}变量相关
{%%}逻辑相关
都是为模版来传值的
过滤器:|
前后端取消转义
前端:
|safe
后端:
from django.utils.safestring import mark_safe
zzz = mark_safe('<h1>阿萨德搜啊第三款垃圾袋</h1>')
|safe
|filesizeformat
|date
标签{%%}
{#{% for foo in l %}#} {# <p>{{ forloop }}</p>#} {#{% endfor %}#} {##} {##} {#{% if '' %}#} {#<p>xxx条件为true</p>#} {# {% else %}#} {# <p>xxx条件为false</p>#} {#{% endif %}#} {##} {#{% for foo in '' %}#} {# {% if forloop.first %}#} {# <p>这是我的第一次</p>#} {# {% elif forloop.last %}#} {# <p>这是最后一次了啊</p>#} {# {% else %}#} {# <p>来啊来啊!!!</p>#} {# {% endif %}#} {# {% empty %}#} {# <p>当for循环的对象为空的时候 会走empty</p>#} {#{% endfor %}#} {##} {#<p>#} {# django模板语法在取值的时候 统一使用句点符(大白话就是 点号 .)#} {# {% with l.6.3.name as ttt %} 可以给一个比较复杂的取值操作取一个别名 之后在with语句中 就可以使用该别名#} {# {{ ttt }}#} {# {{ l.6.3.name }}#} {# {% endwith %}#} {#</p>#} {% for foo in d.keys %} <p>{{ foo }}</p> {% endfor %} {% for foo in d.values %} <p>{{ foo }}</p> {% endfor %} {% for foo in d.items %} <p>{{ foo }}</p>
{% endfor %}
for循环里面的forloop对象
if判断
自定义标签、过滤器、inclusion_tag
自定义固定的三步走战略:
1、必须在你的应用下新建一个叫templatetags文件夹
2、在该文件夹内新建一个任意名称的py文件
3、在该py文件中固定先写入下面两句代码
from django import template
register = template.Library()
自定义过滤器
@register.filter(name='baby') def index(a,b): # 该过滤器只做一个加法运算 是|add建议版本 """ |length |add |default |filesizeformat |truncatewords |truncatechars |safe |slice :param a: :param b: :return: """ print('下午刚起床 一脸懵逼') return a + b
自定义标签,支持传多个值
@register.simple_tag(name='jason') def xxx(a,b,c,year): return '%s?%s|%s{%s'%(a,b,c,year)
自定义inclusion_tag
接受用户传入的参数 然后作用于一个html页面,在该页面上渲染数据,之后将渲染好的页面放到用户调用inclusion_tag的地方
@register.inclusion_tag('bigplus.html') def bigplus(n): l = [] for i in range(n): l.append('第%s项'%i) return {'l':l}
模版的继承
在多个页面整体样式都差不多的情况下,这时候就可以设置一个模版文件了,在该模版文件中,使用block划分多个区域,之后子版本在使用模版的时候 可以通过block块的名字来选定到底需要修改哪一部分区域
模版一般情况下 都应该至少有三个可以被修改的地方
1、子页面自己的css代码
2、子页面自己的html代码
3、子页面自己的js代码
一般情况下 模版上的block越多,页面的可扩展性就越强
模版的导入
{% include 'beautiful.html' %} 当你写了一个特别好看的form表单/列表标签等 可以将它当成一个模块 哪个地方需要 就直接导入使用即可