views.py: (在钩子中代码解耦,将form放在cnblog/blog/Myforms.py中)javascript
from django import forms from django.forms import widgets class UserForm(forms.Form): user = forms.CharField(max_length=32, label="用户名", widget=widgets.TextInput(attrs={"class": "form-control"},) ) pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}, ) ) re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"},) ) email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class": "form-control"},) ) def register(request): form = UserForm() # 实例化form对象 return render(request, "register.html", {"form": form}) # 注意要传入的是一个字典
注意:css
(1)在视图层引入widgets模块,配置修改forms类参数。在这里添加了class="form-control"的bootstrap样式。添加了label标签。html
(2)这里register视图,实例化的form对象是未绑定数据的form表单。将这个form对象传入render中,注意传入的格式必须是字典的形式。
java
register.html:python
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action=""> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} <div class="form-group"> <label for="user">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> <label for="avator">头像</label> <input type="file"> </div> <input type="button" class="btn btn-default login-btn" value="提交"><span class="error"></span> </form> </div> </div> </div> <script src="/static/js/jquery-3.3.1.js"></script> </body> </html>
显示效果以下所示:jquery
register.html:ajax
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册页面</title> <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css"> <style> #avator_img { margin-left: 20px; } #avator { display: none; } </style> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action=""> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} </div> {% endfor %} <div class="form-group"> <label for="avatar"> 头像 <img id="avatar_img" width="60" height="60" src="/static/blog/img/default.png" alt=""> </label> <input type="file" id="avatar"> </div> <input type="button" class="btn btn-default reg-btn" value="提交"><span class="error"></span> </form> </div> </div> </div> <script src="/static/js/jquery-3.3.1.js"></script> </body> </html>
显示效果:数据库
注意:django
(1)添加头像上传和默认头像时,须要优化显示效果,点击默认头像,显示文件上传框图。这里应用的方法是巧妙使用label标签,将img标签嵌套在label标签中,设置<label for="">中的值与input标签的id相同。实现点击label标签就会默认点击到了input标签。设置完后,隐藏input标签的显示。json
(2)<label for="{{ field.auto_id }}"></label>:若是在循环user,filed.auto_id,返回的就是user_id。
首先须要了解jquery事件的change()方法:当元素的值发生改变时,会发生 change 事件;当用于 select 元素时,change 事件会在选择某个选项时发生。当用于 text field 或 text area 时,该事件会在元素失去焦点时发生。
<script> $("#avatar").change(function () { // change方法,当输入域发生变化时改变其颜色(在这里是上传图片时触发) alert(123); }) </script>
上传图片:
获取input标签用户选中的文件对象:
// 获取用户选中的文件对象 var file_obj = $(this)[0].files[0];
<script> // 头像预览 $("#avatar").change(function () { // 获取用户选中的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // reader.result; // 读url的结果保存在result中 </script>
注意:readerAsDataURL()阅读文件的路径。读完url的结果保存在reader.result中。
<script> // 头像预览 $("#avatar").change(function () { // change方法,当输入域发生变化时改变其颜色(在这里是上传图片时触发) // 获取用户选中的文件对象 var file_obj = $(this)[0].files[0]; // 获取文件对象路径 var reader = new FileReader(); reader.readAsDataURL(file_obj); // reader.result; // 读url的结果保存在result中 reader.onload=function () { // // 修改img的src属性,src=文件对象的路径 $("#avatar_img").attr("src", reader.result) // 等事件完成以后再执行 }; }) </script>
显示效果:
注意:
(1)window.onload()等DOM执行完后,再来执行内部的代码;
(2)reader.readAsDataURL();这个读取的时间比较长,同时,这个只是其中一个线程,若是直接执行
$("#avatar_img").attr("src", reader.result)
这个语句会与readAsDataURL方法并发执行,因为前面方法尚未完成,reader.result还只是一个空值。
(3)所以须要作一个load事件,等readAsDataURL读取完后再执行
$("#avatar_img").attr("src", reader.result)
// 基于Ajax提交数据 $(".reg-btn").click(function () { var formdata = new FormData(); formdata.append("user", $("#id_user").val()); formdata.append("pwd", $("#id_pwd").val()); formdata.append("re_pwd", $("#id_re_pwd").val()); formdata.append("email", $("#id_email").val()); formdata.append("avatar", $("#avatar")[0].files[0]); // 拿到用户传递的文件对象 formdata.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, // 将formdata对象放在data里面发送 success:function (data) { console.log(data) } })
注意:
1)给注册按钮绑定一个click事件;
2)因为上传的数据中包含一个图片文件,类型必须换为multipart/form-data格式,再也不使用form-urlencoded 方式提交数据;
3)构建的formdata对象:
var formdata = new FormData();
4)给对象添加键值:
formdata.append("user", $("#id_user").val());
5)添加文件对象:
formdata.append("avatar", $("#avatar")[0].files[0]); // 拿到用户传递的文件对象
$("#avatar")[0]拿到的是DOM对象,$("#avatar")[0].files[0]获得用户选中的文件对象(jquery取上传文件固定语法 :$("#avatar")[0].files[0]);
6)基于Ajax文件上传,传formdata数据必定要写下面这两个参数:
contentType:false, // 在ajax这一层不作任何编码(不设置内容类型) processData:false, // 在ajax这一层不作预处理(不处理数据)
7)要避免forbidden报错,须要添加csrf_token,查看模板中写入的{% csrf_token %}在页面中显示:
而后,添加csrf_token键值:
formdata.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
def register(request): if request.is_ajax(): # 也可使用 if request.method == "POST": # 若是是一个Ajax请求 print(request.POST) # 全部提交的数据 form = UserForm(request.POST) # 建立UserForm对象,传入当前提交的字典 response = {"user": None, "msg": None} if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(全部都经过才返回true) # 类定义的字段全符合要求,返回true response["user"] = form.cleaned_data.get("user") else: # 包含错误信息返回false print(form.cleaned_data) # 字段值符合要求的放在cleaned_data print(form.errors) # 字段不符合要求的对应的键做为键,错误信息做为值 response["msg"] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html", {"form": form}) # 注入要传入的是一个字典
在视图函数register中处理Ajax提交的请求须要注意的是:
1)formdata提交给register处理,便是post请求,又是ajax请求,所以能够用两种方式来获取;
2)form.is_valid是帮忙校验返回布尔值的,true或false(全部都经过才返回true);
展现效果以下所示:
// 基于Ajax提交数据 $(".reg-btn").click(function () { var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操做 formdata.append(data.name, data.value) }); formdata.append("avatar", $("#avatar")[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { console.log(data) } }) })
控制台输出以下:
(1)jQuery ajax - serializeArray() 方法
serializeArray() 方法经过序列化表单值来建立对象数组(名称和值)。能够选择一个或多个表单元素(好比 input 及/或 textarea),或者 form 元素自己。
在register.html中给form元素添加id="form"。在登陆按钮的点击事件中添加:
var ret = $("#form").serializeArray(); console.log(ret);
浏览器控制台输出以下:
打印出一个列表,列表中保存一个个object类型,每一个object类型都有name和value.
(2)jqeury的遍历方法:.each()
each() 方法为每一个匹配元素规定运行的函数。返回 false 可用于及早中止循环。
因为serializeArray()返回的是一个数组,所以这里each处理的request_data是一个数组。each方法会对数组中子元素的逐个进行fn函数调用,直至调用某个子元素返回的结果为false
$.each(request_data, function (index, data) {}在这里index表示数组当前下标,data则表示数组当前元素。
<form id="form"> {% csrf_token %} {% for field in form %} {# 一个个获取字段对象 #} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} <span class="error pull-right"></span> </div> {% endfor %} ...... <form>
// 基于Ajax提交数据 $(".reg-btn").click(function () { var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操做 formdata.append(data.name, data.value) }); formdata.append("avatar", $("#avatar")[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); $("#id_"+field).next().html(error_list[0]); }) } } }) })
1)在回调函数中处理注册信息,success:function(data){}中的data就是视图函数register中返回的response对象:
from django.http import JsonResponse def register(request): if request.is_ajax(): ... return JsonResponse(response)
字典放进去直接序列化,ajax拿到的就是对象,两边都不须要进行json的序列化与反序列化。
$.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); } } })
在视图函数中,当校验成功时,会给字典response的键user赋值,不然仍为None。Ajax中依此判断注册是否成功。
2)当注册失败时,处理视图函数中返回的response的键msg的值(错误信息):
$.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list) }) } } })
浏览器控制台输出以下:
3)因为错误字段的名字和input标签的id相同:能够用"id"和field拼接为id_user、id_re_pwd等input标签的id值。
又因为error_list是一个数组,且数组中保存的是一个字符串,所以只须要取数组的第一条记录便可:
$("#id_"+field).next().html(error_list[0]);
<style> #avatar_img { margin-left: 20px; } #avatar { display: none; } .error { color: red; } </style>
显示效果以下:
$.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg);
// 清空错误信息 $("span.error").html("");
$.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); $("#id_"+field).next().html(error_list[0]); }) } } })
这样在第一次全部注册信息都错误,第二次注册填写的用户名正确时,就只会显示当前错误的提示了。
// 基于Ajax提交数据 $(".reg-btn").click(function () { var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data, function (index, data) { // 依次循环,每次的操做 formdata.append(data.name, data.value) }); formdata.append("avatar", $("#avatar")[0].files[0]); // 拿到用户传递的文件对象 $.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); // 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展现这次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); $("#id_"+field).next().html(error_list[0]); $("#id_"+field).parent().addClass("has-error"); }) } } }) })
显示效果:
注意:
1)对input标签的父标签添加bootstrap的类has-error。
Bootstrap 对表单控件的校验状态,如 error、warning 和 success 状态,都定义了样式。使用时,添加 .has-warning
、.has-error
或 .has-success
类到这些控件的父元素便可。
2)清空错误信息时也须要同时清掉class = "form-group"的组件的表框变红样式
// 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展现这次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); $("#id_"+field).next().html(error_list[0]); $("#id_"+field).parent().addClass("has-error"); })
建立/cnblog/blog/Myforms.py文件存放forms组件和钩子,并在views.py中引入forms
from blog.Myforms import UserForm
在字段构造参数中设置"error_messages"修改字段错误提示消息
from django import forms from django.forms import widgets from blog.models import UserInfo class UserForm(forms.Form): user = forms.CharField(max_length=32, error_messages={"required": "该字段不能为空"}, label="用户名", widget=widgets.TextInput(attrs={"class": "form-control"},) ) pwd = forms.CharField(max_length=32, label="密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}, ) ) re_pwd = forms.CharField(max_length=32, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"},) ) email = forms.EmailField(max_length=32, label="邮箱", widget=widgets.EmailInput(attrs={"class": "form-control"},) )
其中"requried"负责字段不能为空消息提示;"invalid"负责格式错误消息提示等.
from django import forms from django.forms import widgets from blog.models import UserInfo from django.core.exceptions import ValidationError class UserForm(forms.Form): ... # 局部钩子 def clean_user(self): val = self.cleaned_data.get("user") user = UserInfo.objects.filter(username=val).first() if not user: return val else: raise ValidationError("该用户已注册!")
显示效果:
注意:
1)引入ValidationError模块
from django.core.exceptions import ValidationError
2)val = self.cleaned_data.get("user") 在用户输入的用户名不为空时,符合字段要求,所以添加在cleaned_data中,经过此方法能够拿到用户输入的用户名。
3)再根据这个用户名去数据库表blog_userinfo中查找相关用户:
user = UserInfo.objects.filter(username=val).first()
若是这个用户名不存在则经过校验,若是存在则经过ValidationError设置错误提示:
if not user: return val else: raise ValidationError("该用户已注册!")
4)须要注意的是当检验经过时,须要返回的是用户输入的用户名而不是数据库中查找的记录,若是返回的值有问题,在生成用户记录时,每每会报ValueError: The given username must be set。这样的错误。
from django import forms from django.forms import widgets from blog.models import UserInfo from django.core.exceptions import ValidationError class UserForm(forms.Form): ...... # 局部钩子 def clean_user(self): user = self.cleaned_data.get("user") user = UserInfo.objects.filter(username=user).first() if not user: return user else: raise ValidationError("该用户已注册!") # 全局钩子 def clean(self): pwd = self.cleaned_data.get("pwd") re_pwd = self.cleaned_data.get("re_pwd") if pwd and re_pwd: # 若是两个都有值 if pwd == re_pwd: # 验证成功 return self.cleaned_data else: # 验证失败 raise ValidationError("两次密码不一致!") else: # 若是任有一个没有值则不作处理 return self.cleaned_data
注意:
1)在register.html中ajax回调函数中,经过__all__取到全局错误信息:
$.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功 } else { // 注册失败 console.log(data.msg); // 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展现这次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); if (field == "__all__"){ // 全局错误信息 $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); } $("#id_"+field).next().html(error_list[0]); $("#id_"+field).parent().addClass("has-error"); }) } } })
查看页面信息:
2)如上所示在找到field=="__all__"的状况下,在id=“id_re_pwd”的input标签的下一个兄弟标签即span标签中填写错误信息,并在input的标签的父标签div中添加类has_error,修改input标签的显示样式。
if (field == "__all__"){ // 全局错误信息 $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); }
3)当两个密码都有输入时,验证两个密码是否一致;当两个密码任有一个没输入则不作处理
if pwd and re_pwd: # 若是两个都有值 if pwd == re_pwd: # 验证成功 return self.cleaned_data else: # 验证失败 raise ValidationError("两次密码不一致!") else: # 若是任有一个没有值则不作处理 return self.cleaned_data
显示效果:
$.ajax({ url: "", type: "post", contentType: false, processData: false, data: formdata, success:function (data) { // console.log(data) if (data.user){ // 注册成功,跳转登陆页面 location.href = "/login/" } else { // 注册失败 console.log(data.msg); // 清空错误信息 $("span.error").html(""); $(".form-group").removeClass("has-error"); // 展现这次提交的错误信息 $.each(data.msg, function (field, error_list) { // 键:每一个字段字符串; 值:错误信息列表 console.log(field, error_list); if (field == "__all__"){ // 全局错误信息 $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error"); } $("#id_"+field).next().html(error_list[0]); $("#id_"+field).parent().addClass("has-error"); }) } } })
from blog.Myforms import UserForm from blog.models import UserInfo def register(request): if request.is_ajax(): # 也可使用 if request.method == "POST": # 若是是一个Ajax请求 print(request.POST) # 全部提交的数据 form = UserForm(request.POST) # 建立UserForm对象,传入当前提交的字典 response = {"user": None, "msg": None} if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(全部都经过才返回true) # 类定义的字段全符合要求,返回true response["user"] = form.cleaned_data.get("user") # 生成一条用户记录 user = form.cleaned_data.get("user") pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email") avatar_obj = request.FILES.get("avatar") user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) # 用户认证组件使用create_user辅助函数建立用户 else: # 包含错误信息返回false print(form.cleaned_data) # 字段值符合要求的放在cleaned_data print(form.errors) # 字段不符合要求的对应的键做为键,错误信息做为值 response["msg"] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html", {"form": form}) # 注入要传入的是一个字典
注意:
(1)须要在视图中引入模型
from blog.models import UserInfo
(2)form.cleaned_data:字段值符合要求的放在cleaned_data中。字典数据类型。因为校验经过,因此用户名、密码、邮箱信息均可以用以下方法拿到:
# 生成一条用户记录 user = form.cleaned_data.get("user") pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email")
(3)django操做models.FileField数据类型:
class UserInfo(AbstractUser): ...... avatar = models.FileField(upload_to='avatars/', default="/avatars/default.png") # 该字段存放每一个用户的头像文件 def __str__(self): return self.username
FileField与ImageField用法相似,FileField这个字段必定要接受一个文件对象;ImageField必定要接受一个图片对象。
接受文件对象,django会作的事情:默认下载文件到项目的根目录;设置upload_to字段后,会下载到项目根目录的avatars目录下(没有该目录则建立一个)。
user_obj的avatar存的是文件的相对路径
(4)用户认证组件使用create_user辅助函数建立用户
user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj)
(1)注册完成后页面跳转到登陆页面
(2)数据库的blog_userinfo表产生一条新的用户数据,且avatar保存的确实是文件的相对路径
(3)设置upload_to字段后,会下载到项目根目录的avatars目录下,没有提早建立目录的状况下会自动建立:
Django有两种静态文件:
/static/:服务器本身用到的文件,如:js,css,img
/media/:用户上传的文件
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
一旦配置了MEDIA_ROOT,Django会将文件对象下载到MEDIA_ROOT中的avatar目录中。若是没有avatar文件夹,Django也会自动建立,user_obj的avatar存的是文件对象。
在使用FileFIeld或者ImageField时,上传实例化的对象,全部上传的文件都会放在media/"定义的文件夹名称"/,与服务器的文件分开,耦合性很是好!
首先将default.png拷到/media/avatars/目录下,
而后修改register视图函数在生成用户记录时作以下断定:
# 生成一条用户记录 user = form.cleaned_data.get("user") pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email") avatar_obj = request.FILES.get("avatar") if avatar_obj: # 若是上传了头像,即有值 # 用户认证组件使用create_user辅助函数建立用户 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # 若是没有传头像,就用默认的default.png user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email)
from blog.Myforms import UserForm from blog.models import UserInfo def register(request): if request.is_ajax(): # 也可使用 if request.method == "POST": # 若是是一个Ajax请求 print(request.POST) # 全部提交的数据 form = UserForm(request.POST) # 建立UserForm对象,传入当前提交的字典 response = {"user": None, "msg": None} if form.is_valid(): # form.is_valid是帮忙校验返回布尔值的,true或false(全部都经过才返回true) # 类定义的字段全符合要求,返回true response["user"] = form.cleaned_data.get("user") # 生成一条用户记录 user = form.cleaned_data.get("user") pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email") avatar_obj = request.FILES.get("avatar") extra = {} # 空字典 if avatar_obj: extra["avatar"] = avatar_obj # 传值进空字典 UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) # **extra表明额外的参数 """ if avatar_obj: # 若是上传了头像,即有值 # 用户认证组件使用create_user辅助函数建立用户 user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, avatar=avatar_obj) else: # 若是没有传头像,就用默认的default.png user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email) """ else: # 包含错误信息返回false print(form.cleaned_data) # 字段值符合要求的放在cleaned_data print(form.errors) # 字段不符合要求的对应的键做为键,错误信息做为值 response["msg"] = form.errors return JsonResponse(response) form = UserForm() # 实例化form对象 return render(request, "register.html", {"form": form}) # 注入要传入的是一个字典
注意:
1)create_user方法的源代码以下:
def create_user(self, username, email=None, password=None, **extra_fields): extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(username, email, password, **extra_fields)
说明额外的参数经过键值对的方式传入便可。所以建立空字典extra。
2)因为user_obj并无用到,所以删除这个赋值。
客户端浏览器能够访问到static下的数据,但如何能直接访问到media中的数据。
MEDIA_URL = "/media/"
from django.contrib import admin from django.urls import path,re_path from django.views.static import serve from blog import views from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('get_validCode_img/', views.get_validCode_img), path('register/', views.register), # media配置 # 匹配以media开头的任意路径 re_path(r"media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}) ]
导入的包排列顺序是:python标准库、第三方插件的包、本身定义的包。
变量解释能够放在函数下注释来描述:
option+command+L,Python编辑器自动规范代码。