Django 路由与视图
路由配置
在urls.py
中,通过path
、re_path
、url
配置路由
其中kwargs
入参可传入额外信息,并在视图中被**kwargs
捕获
from django.urls import include, path, re_path
path(route, view, kwargs=None, name=None, Pattern=None):
固定路由
FBV直接引入即可
CBV需要调用as_view
方法(继承自View类)
此外还支持include
另一个路由文件作为二级路由
from django.urls import include, path
import blog.views
urlpatterns = [
path('', blog.views.index), # FBV
path('restful/', blog.views.RestfulView.as_view()), # CBV
path('blog/', include('blog.urls')), # 作为blog下的二级路由
]
动态路由
使用<type:name>
形式匹配参数,其中type
可以为 int、str、path 等,也可以通过register_converter
注册自定义类型
# urls.py
urlpatterns = [
path('blog/<int:id>/', views.db_get),
path('blog/<int:id>/<str:name>/', views.month_archive)
]
# views.py
def db_get(request, id):
vv = Blog.objects.get(id=id)
return JsonResponse(model_to_dict(vv), json_dumps_params={'ensure_ascii': False})
正则表达式路由
通过re_path
方法,用(?P<name>pattern)
进行正则匹配
也可以用老版本中的url
语法,等同于re_path
from django.urls import re_path
from django.conf.urls import url
from . import views
from django.views.static import serve
from django.conf import settings
urlpatterns = [
re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}),
re_path('articles/(?P<year>[0-9]{4})/(?P<month>0[1-9]|1[0-2])/', views.month_archive)
]
视图函数 function base views
比较简单,需自行区分请求的method并做出响应
@csrf_exempt
def hello_world(request, *args, **kwargs):
if request.method == 'GET':
return HttpResponse('Hello, get request', content_type="text/plain")
elif request.method == 'POST':
return HttpResponse('Hello, post request', content_type="text/plain")
return HttpResponse("Hello, world.", content_type="text/plain")
# urls.py
urlpatterns = [
path('hello/', hello_world),
]
视图类 class base views
推荐,符合 restful 规范, 且支持继承
视图类可继承View
类自行实现逻辑,也可以继承其他类以简化,称为 mixins:
- SingleObjectMixin
实现get_object
的单个对象查找方法 - TemplateView
帮助渲染模板文件 - ListView
列表获取和处理 - FileProxyMixin
文件上传
from django.http import HttpResponse
from django.views import View
class HelloView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('getn')
def post(self, request, *args, **kwargs):
return HttpResponse('postn')
def put(self, request, *args, **kwargs):
return HttpResponse('putn')
def delete(self, request, *args, **kwargs):
return HttpResponse('deleten')
# 请求会先进入 dispatch 方法找到对应函数,然后再执行
# @csrf_exempt
# def dispatch(self, request, *args, **kwargs):
# return super(HelloView, self).dispatch(request, *args, **kwargs)
# urls.py
urlpatterns = [
path('hello/', HelloView.as_view()),
]
method_decorator
来自django.utils.decorators
模块的类装饰器,用于将函数装饰器应用到视图类中的方法
其 name
属性用于指定添加装饰器的方法名,若全都需要则添加给dispatch
# 定义函数装饰器
def wrapper(f):
def innser(*args, **kwargs):
print('before')
ret = f(*args, **kwargs)
print('after')
return ret
return innser
@method_decorator(wrapper, name='get')
class HelloView(View):
...
视图方法入参
接受 request 参数(继承自 HttpRequest 的子类 WSGIRequest ),用于存放浏览器传递过来的所有数据
HttpRequest
来自django.http
模块,常用属性如下:
- method
请求方法,如GET、POST、PUT、DELETE等 - META
请求头字典。浏览器中aaa-bbb
格式,会变成HTTP_AAA_BBB
格式
request.META.get('HTTP_APP_CLIENT', None)
- GET
query参数字典,通过getlist
方法可将重名参数值解析为列表
# ?a=1&a=2
request.GET.get("a") # 2
request.GET.getlist("a") # [1, 2]
- POST
表单参数QueryDict(application/x-www-form-urlencoded),同上具有getlist
方法 - body
请求主体参数(application/json 或 multipart/form-data),通过decode
方法解析为文本,通常配合json.loads
转为字典
import json
request.body # b'{"a":100}'
request.body.decode() # '{"a":100}'
json.loads(request.body.decode()) # {"a":100}
- data
DRF的额外属性,包含 POST 和 body 的内容。
WSGIRequest
- FILES
文件上传
视图方法返回值
HttpResponse
来自django.http
模块,用于直接返回字符串
return HttpResponse("created VV3 with no hair")
JsonResponse
来自django.http
模块,用于直接返回JSON
注意配置json_dumps_params
以支持中文
data = {'code': 0, "content": "返回中文字符串", "err_msg": ""}
return JsonResponse(data, json_dumps_params={'ensure_ascii': False})
render
来自django.shortcuts
模块,从当前app下的templates
目录逐级向上查找并返回HTML 或者模板文件
模板引擎可在settings.py
的TEMPLATES
下配置,默认引擎为Django Template Language (DTL),也可以修改为 Mako 或 Jinja2
需配合settings.py
中TEMPLATES
配置使用
def db_retrieve(request):
arr = []
vvs = Blog.objects.all()
for vv in vvs:
arr.append(model_to_dict(vv))
content = json.dumps(arr)
return render(request, "index.html", {"title":"首页", "content": content })
# template/index.html
<h1>{{ title }}</h1>
<script> console.log("{{ content | safe }}"); </script>
redirect
来自django.shortcuts
模块,用于重定向
return redirect("http://www.baidu.com")
数据校验
Django本身支持通过form
模块实现数据校验,但仅适用于后端渲染,并不方便
from django import forms
class LoginForm(forms.Form):
name = forms.CharField(
label="账号",
min_length=4,
required=True,
error_messages={'required': '账号不能为空', "min_length": "账号名最短4位"},
widget=forms.TextInput(attrs={'class': "input-text",
'placeholder': '请输入登录账号'})
)
静态目录
通常通过STATICFILES_DIRS
指定通用目录,且每个app拥有自己的static目录(不强求,因访问时并不会指定从哪个app下获得)
不同目录下的静态文件需注意不可 同子目录且同名,其本质上最终都在一个空间内
- 当
DEBUG = True
时
通过为INSTALLED_APPS
配置django.contrib.staticfiles
模块,访问STATIC_URL
路由时会自动从STATICFILES_DIRS
及所有 app 的 static 目录下查找对应的静态文件 - 当
DEBUG = False
时,STATIC_URL
配置无效
此时可先执行python manage.py collectstatic
将STATICFILES_DIRS
及所有 app 的 static 目录下文件打包到STATIC_ROOT
目录
再自行通过url正则匹配或外部nginx转发等手段实现静态文件的访问
# settings.py
STATIC_URL = '/static/' # 用于指定静态目录对应的网址URL映射,默认值'/static/'
STATICFILES_DIRS = ( # 各个app通用的静态文件目录
os.path.join(BASE_DIR, 'common_static'),
)
# 执行 python manage.py collectstatic 后所有app下的static目录,和STATICFILES_DIRS下的静态文件都会打包到此目录
STATIC_ROOT = os.path.join(BASE_DIR, "static")
# urls.py
from django.urls import re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT})
]
模板文件中可通过static
标签加载静态资源,如settings.py
中已配置'builtins':['django.templatetags.static']
则可无需通过load
引入static
{% load static %}
<link rel="stylesheet" href="{% static 'style.css' %}">
文件上传
通过request.FILES
获取上传的文件
自行处理
# urls.py
path('file_upload/', views.file_upload)
# views.py
class FileUploadForm(forms.Form):
file = forms.FileField(label="文件上传")
def handle_uploaded_file(f):
save_path = os.path.join(settings.MEDIA_ROOT, f.name)
with open(save_path, 'wb+') as fp:
for chunk in f.chunks():
fp.write(chunk)
@csrf_exempt
def file_upload(request, *args, **kwargs):
error_msg = ""
if request.method == 'POST':
forms = FileUploadForm(request.POST, request.FILES)
if forms.is_valid():
handle_uploaded_file(request.FILES['file'])
return HttpResponse('上传成功')
error_msg = "异常"
else:
forms = FileUploadForm()
return render(request, 'file_upload.html', {'forms': forms, "error_msg": error_msg})
# file_upload.html
<form method="post" action="/blog/file_upload/" enctype="multipart/form-data">
{% csrf_token %}
{{ forms }}<br>
<input type="submit" value="提交">
</form>
通过ORM自动处理
在模型中定义models.FileField
类型字段,该字段在数据库里只是个字符串,但在view中,其不但可以接收普通的字符串,还可以接收文件实例,此时其会自动将该文件存入MEDIA_ROOT / upload_to
路径
- MEDIA_ROOT 在
settings.py
中配置 - upload_to 是 FileField 的属性,用于指定次级路径,也可以传入一个函数用于自定义保存逻辑
- MEDIA_URL (用处不大)配合 django.template.context_processors.media 使用,支持在模板中通过
{{MEDIA_URL}}
引用该路径
# settings.py
# 需配合 django.template.context_processors.media 使用,支持在模板中通过`{{MEDIA_URL}}`引用该路径
MEDIA_URL = '/media/'
# 配合 models.FileField 上传文件
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# models.py
def upload_to(instance, filename):
return "/".join([MEDIA_ROOT, "upload", filename])
class Blog(models.Model):
...
# upload_file = models.FileField(upload_to="upload/") #指向 MEDIA_ROOT/upload
# upload_file = models.FileField(upload_to="upload/%Y/%m/%d")
upload_file = models.FileField(upload_to=upload_to, default="")
# urls.py
path('file_upload2/', views.file_upload2)
# views.py
def file_upload2(request, *args, **kwargs):
if request.method == 'POST':
upload_file = request.FILES['file']
# upload_files = request.FILES.getlist('files') # 如有多个文件
Blog.objects.create(name=upload_file.name, age=18, has_hair=False,
upload_file=upload_file)
return HttpResponse('上传成功')
return render(request, 'file_upload2.html', {})
# file_upload2.html
<form method="post" action="/blog/file_upload2/" enctype="multipart/form-data">
{% csrf_token %}
<label>选择上传文件:</label><input type="file" name="file">
<div><input type="submit" value="提交" style="margin-top:10px"></div>
</form>