Django知识-视图
视图设置
一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应。代码写在哪里也无所谓,只要它在你的应用目录下面。但是为了方便视图一般被定义在“应用/views.py”文件中。
视图的第一个参数必须为HttpRequest实例,还可能包含下参数如:
- 通过正则表达式组获得参数
视图必须返回一个HttpResponse对象或子对象作为响应。
一.错误视图
以下错误以常见的404报错为例
方法1:
如果想要看到错误视图,而不是错误调试信息的需要设置setting.py配置文件的调试开关设置为False:ALLOWED_HOSTS允许所有主机访问。
DEBUG = False
ALLOWED_HOSTS = ["*",]
然后在templates目录下,创建404.HTML视图
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>404报错视图</title>
</head>
<body>
<h1>错误视图</h1>
<p>404报错视图</p></body>
</html>
再运行Django框架,即可在项目运行后得到如下所示:
若想要修改其他措施视图依旧如此,如创建报错500,403等
方法2:
在总目录下使用固定的变量handler404或handler500等,不需要从专门的errviews视图中显式导入函数,Django会延迟导入,自己寻找
主目录如下:
from django.contrib import admin
from django.urls import path,includeurlpatterns = [path('admin/', admin.site.urls),path('',include('myday.urls')),
]
handler404 = 'myday.errviews.E404'
errviews错误视图如下写入
from django.shortcuts import render,HttpResponsedef E404(request,exception=None):# print(exception)return HttpResponse("报错404")
结果如下:
def get_info(request, id):#post = Post.post_object.get(title=title)post = get_object_or_404(Post,id=id) # 不区分大小写return JsonResponse({'title': post.title,'content': post.content,# 其他需要的字段})
2.错误视图2
当动态路由配置错误时,会报500错误,既服务器错误,我们可以使用get_object_or_404,在用户访问错误的路由时,报错404
这样当用户访问不存在的 ID 时,Django 会自动返回 404 页面,而不是抛出 DoesNotExist 异常导致 500 错误。
def get_info(request, id):#post = Post.post_object.get(title=title)post = get_object_or_404(Post,id=id) # 不区分大小写return JsonResponse({'title': post.title,'content': post.content,# 其他需要的字段})
二.HttpRequest 对象
1.META
包含所有的HTTP 头部,使用字典形式返回
def meta_info(request):m = request.METAstr = ""for key,values in m.items():str += f"{key}:{values}\n"return HttpResponse(str)
2.get
获取路由中的信息,查询参数
def view_get(request):A = request.GET.getlist('a') # 多个参数时,使用列表B = request.GET.get('b')C = request.GET.get('c')return JsonResponse({ # json返回列表形式'a': A,'b': B,'c': C})
实例:
1.编辑视图函数
def get(request):return render(request, 'get/get.html')def get1(request):a_value = request.GET.getlist('a') # 多个参数时,使用列表b_value = request.GET.get('b')c_value = request.GET.get('c')return JsonResponse({ # json返回列表形式'a_value': a_value,'b_value': b_value,'c_value': c_value})
2.配置路由
app_name用于后面在HTML文件中指定文件的路由
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("get/",get),path("get1/",get1,name='get1'),
]
3.HTML文件
在模板中{% url 'myday:name'%}中的name,需要在urls中对路由进行name编写
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>get请求</title>
</head>
<body>
<a href="{% url 'myday:get1' %}?a=1&a=2&b=2&c=3">get1,一键多值等</a>
</body>
</html>
3.post
在进行form表单提交的时候将 method 方式改成post方式,提交表单时会发起POST请求。需要使用HttpRequest对象的POST属性接收参数,POST属性返回QueryDict类型的对象
在表单进行提交时,控件name属性的值作为键,value属性的值为值,构成键值对提交,如果控件没有name属性那么将不进行提交。
1.先将settings中的csrf注释
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware',# 'django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',
]
2.编写视图函数
def post(request):return render(request, 'get-post/post.html')def post1(request):p_dict = request.POST # 获取post请求参数uname = p_dict.get('uname')upwd = p_dict.get('upwd')ugender = p_dict.get('ugender')uhobby = p_dict.getlist('uhobby') # 爱好是一键多值,使用getlist方法获取所有爱好context = {'uname':uname,'upwd':upwd,'ugender':ugender,'uhobby':uhobby}return render(request,'get-post/post1.html',context=context)
3.HTML文件
提交表单HTML
<html lang="en">
<head><meta charset="UTF-8"><title>表单</title>
</head>
<body>
<form method="post" action="{% url 'myday:post1' %}">姓名:<input type="text" name="uname"/><br>密码:<input type="password" name="upwd"/><br>性别:<input type="radio" name="ugender" value="1"/>男<input type="radio" name="ugender" value="0"/>女<br>爱好:<input type="checkbox" name="uhobby" value="抽烟"/>抽烟<input type="checkbox" name="uhobby" value="喝酒"/>喝酒<input type="checkbox" name="uhobby" value="烫头"/>烫头<br><input type="submit" value="提交"/>
</form></body>
</html>
接收查看效果
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>姓名:
<p>{{ uname }}</p>
<br>
密码:{{ upwd }}
<br>
性别:{{ ugender }}
<br>爱好:{{ uhobby }}</body>
</html>
4.编辑路由
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("post/",post,name='post'),path("post1/",post1,name='post1'),
]
三.HttpResponse对象
1.HttpResponse 属性
- content:表示返回的内容
- charset:表示response采用的编码字符集,默认为utf-8
- status_code:返回的HTTP响应状态码
- content-type:指定返回数据的的MIME类型,默认为'text/html'
2.设置cookie
- max_age 以秒为单位,如果Cookie 只应该持续客户端浏览器的会话时长则应该为None(默认值)。
- expires 应该是一个 UTC "Wdy, DD-Mon-YY HH:MM:SS GMT" 格式的字符串,或者一个datetime.datetime 对象。如果expires 是一个datetime 对象,则max_age 会通过计算得到。
- max_age与expires二选一
- 如果不指定过期时间,则关闭浏览器过期
- 如果你想设置一个跨域的Cookie,请使用domain 参数。例如,domain=".lawrence.com" 将设置一个www.lawrence.com、blogs.lawrence.com 和calendars.lawrence.com 都可读的Cookie。否则,Cookie 将只能被设置它的域读取。
- 如果你想阻止客服端的JavaScript 访问Cookie,可以设置httponly=True。
def set_cookie(request):response = HttpResponse("设置cookie")response.set_cookie('username', 'admin')response.set_cookie('password','123456',max_age=60*60*12) # 设置超时时间 60*60*12秒return response
def delete_cookie(request):response = HttpResponse('删除cookie')response.delete_cookie('password') # 删除键为password 的cookiereturn response
def get_cookie(request):cookie = request.COOKIES # 获取cookieusername = cookie.get('username')password = cookie.get('password')response = HttpResponse('%s %s'%(username,password)) # 将获取到的cookie返回return response
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("set_cookie/",set_cookie),path("delete_cookie/",delete_cookie),path("get_cookie/",get_cookie),
]
3.子类 HttpResponseRedirect 重定向对象
当一个视图处理完成后,不需要向客户端呈现数据,而是跳转到其他页面,比如用户登录成功之后,需要跳转到登录成功后的页面。此时就需要模拟一个用户请求的效果,从一个视图转到另外一个视图,就称为重定向。
django中提供 HttpResponseRedirect 对象实现重定向功能。HttpResponseRedirect 定义在django.http模块中
下面我们用模拟登录做示例: 定义一个登录页面模板。当登录成功之后跳转到博客首页。如果账号密码校验错误,返回原来的登录页面。
1.设置视图函数
第一个用于返回登录界面,第二个用于核对post提交的信息
在执行if语句时,因为当用户登录成功时,通常需要跳转到一个新页面(如首页 /index
),而不是直接渲染同一个页面。
在执行else语句时
-
原因:当登录失败时,需要:
-
显示错误信息(如“帐号或密码错误”)。
-
保留用户输入(通过
uname
和upwd
回填表单,避免用户重新输入)。 -
保持当前页面状态(仍然是登录页,无需跳转)。
-
-
为什么不重定向?
-
如果重定向(如
return HttpResponseRedirect('/login')
),会导致:-
丢失错误信息和用户输入(重定向是新的 GET 请求,无法直接传递
context
)。 -
需要额外机制(如 Django Messages 框架或 Session)临时存储错误信息,增加复杂度。
-
-
def login(request):return render(request, 'login.html')
def login1(request):if request.method == 'GET':return render(request, 'login.html',context={"no_login":0})if request.method =="POST":list = request.POSTuname = list.get('uname')upwd = list.get('upwd')if uname == 'admin' and upwd == '123':# 如果接收到的帐号密码与初始设在的一样那么表示登录成功需要跳转到首页# 重定向# 效果同样# return redirect('/myweb/index/')return HttpResponseRedirect('/index')else:# 帐号或密码错误,重新返回登录模板,并将用户输入的账号密码当参数传递回去。context = {'iserror': '帐号或密码错误','uname': uname,'upwd': upwd,"no_login": 1}return render(request, 'login.html', context=context)
2.登录界面HTML
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form method="post" action="{% url 'myday:login1' %}">用户名:<input name="uname" value="{{ uname }}"> #用于回填表单密码: <input name="upwd" value="{{ upwd }}"><input type="submit" value="登录">
</form>
{% if no_login %}<p>{{ iserror }}</p><p>{{ uname }}</p>
{% endif %}
<span style="color: red"></span></body>
</html>
3.配置路由
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("login/",login),path("login1/",login1,name='login1'),
]
4.改进:使用reverse
reverse (viewname, args=(参数,)) viewname是您要解析的网址路径。args是参数元祖
Django 提供了更简洁的 redirect()
函数,内部自动调用 reverse()
:
def login1(request):name = "admin"pwd = "123"if request.method == 'GET':return render(request, 'login.html',context={"no_login":0})if request.method =="POST":list = request.POSTuname = list.get('uname')upwd = list.get('upwd')if uname == name and upwd == pwd:# 如果接收到的帐号密码与初始设在的一样那么表示登录成功需要跳转到首页# 重定向# 效果同样# return redirect('/myweb/index/')#return HttpResponseRedirect(reverse("myday:index"))return redirect("myday:index") #使用reverseelse:# 帐号或密码错误,重新返回登录模板,并将用户输入的账号密码当参数传递回去。context = {'iserror': '帐号或密码错误','uname': uname,'upwd': upwd,"no_login": 1}return render(request, 'login.html', context=context)
四.session设置
对于敏感、重要的信息不能存在浏览器,也就不能使用cookie,如用户名、余额、等级、验证码等信息 如何确保数据的安全性呢,那就是将数据保存在服务端。这就利用到session技术。
Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。
django项目默认启用Session: settings.py文件,在项MIDDLEWARE_CLASSES中启用Session中间件
存储方式: 设置SESSION_ENGINE项指定Session数据存储的方式,可以存储在数据库、缓存、Redis等 存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式
SESSION_ENGINE='django.contrib.sessions.backends.db'
存储在缓存中:存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快
SESSION_ENGINE='django.contrib.sessions.backends.cache'
混合存储:优先从本机内存中存取,如果没有则从数据库中存取
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
如果存储在数据库中,需要在项INSTALLED_APPS中安装Session应用
1.设置session
def set_session(request):request.session['is_login']=1request.session['username']="admin"request.session['name']="name1"#设置session过期时间#request.session.set_expiry(1800) #单位是秒,0是关闭浏览器后会过期,None是不会过期return HttpResponse("set_session")def get_session(request):print(request.session.keys())is_login=request.session.get('is_login')username=request.session.get('username')name=request.session.get('name')return JsonResponse({'is_login':is_login,'username':username,'name':name,})def del_session(request):del request.session['is_login']#request.session.clear()#清空数据,但会保留sessionkey 数据库里还有key#request.session.flush()#删除整个会话, 数据库没有这条记录return HttpResponse("del_session")
五.动态路由
在视图中编写index与detail函数,并给detail函数增加一个动态路由
def index(request):post_list = Post.post_object.all()return render(request,'index1.html',{'post_list':post_list})def detail(request,id):post = get_object_or_404(Post,id=id)return render(request,'detail1.html',{'post':post})
在urls中使用正则化进行动态路由,注意路由后一定要加入name这个参数,不然后面HTML渲染模板会报错
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("index/",index,name="index"),re_path("detail/(\d+)",detail,name="detail"),
]
index.html 在题目处进行链接跳转到详情页
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<p>内容</p>
{% for post in post_list %}<a href="{% url 'myday:detail' post.id %}"><p style="color: red">题目:{{ post.title }}</p></a><p>内容:{{ post.content }}</p><p>作者:{{ post.author }}</p>
{% endfor %}</body>
</html>
各个详情页的detail.html文件
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<p>题目{{ post.title }}</p>
<p>标签{{ post.tag }}</p>
<p>分类{{ post.category }}</p>
</body>
</html>
六.基于类的通用视图
在开发网站的过程中,有一些视图函数虽然处理的对象不同,但是其大致的代码逻辑是一样的。比如一个博客和一个论坛,通常其首页都是展示一系列的文章列表或者帖子列表。对处理首页的视图函数来说,虽然其处理的对象一个是文章,另一个是帖子,但是其处理的过程是非常类似的。先从数据库取出文章或者帖子列表,然后将这些数据传递给模板并渲染模板。
Django 把这些相同的逻辑代码抽取了出来,写成了一系列的通用视图函数,即基于类的通用视图。
先导入类视图
from django.views.generic import ListView
在我们的博客应用中,index视图函数是从数据库中获取文章(Post)列表数据的: 博客首页的函数视图
def index(request):post_list = Post.post_object.all()return render(request, 'personal_blog/index.html', context={"post_list": post_list})
将 index 视图函数改写为类视图
class IndexView(ListView):model = Posttemplate_name = 'personal_blog/index.html'context_object_name = 'post_list'
要写一个类视图,首先要继承django提供的类视图,这里是获取POST文章列表,继承ListView列表视图。列表视图 ListView 就是用来获取模型类的列表数据,所以我们首页需要继承ListView.
- model。将 model 指定为 Post,告诉 Django 我要获取的模型是 Post。
- template_name。指定这个视图渲染的模板是 'personal_blog/index.html'。
- context_object_name。指定获取的模型列表数据保存的变量名。这个变量会被传递给模板,相当于函数视图中的context参数。
- index 视图函数首先通过 Post.objects.all() 从数据库中获取文章(Post)列表数据,并将其保存到 post_list 变量中。在类视图中这个过程 ListView 已经帮我们做了。我们只需告诉 ListView 去数据库获取的模型是 Post,即指定 model = Post
- 将获得的模型数据列表保存到 post_list 里,即指定 context_object_name = 'post_list'。
- 然后渲染 'personal_blog/index.html' 模板文件,index 视图函数中使用 render 函数。但这个过程 ListView 已经帮我们做了,我们只需指定渲染哪个模板即可。
- 配置url,函数视图的url跟类视图url有所区别 , IndexView 是一个类,不能直接替代 index 函数需要先将类视图转换成函数视图,调用类视图的 as_view() 方法.
1.index-detail
1.编写视图函数
class IndexView(ListView):model = Posttemplate_name = 'index1.html'context_object_name = 'post_list'class DetailView(IndexView):model = Posttemplate_name = 'detail1.html'context_object_name = 'post'def get_queryset(self):print(self.args) #self.args用于接收路由中的动态参数return self.model.post_object.get(id=self.args[0])
因为后面的detailview是继承于indexview的,而indexview是Post.objects.all(),所以要进行父类方法重写
2.编写HTML文件
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<p>内容</p>
{% for post in post_list %}<a href="{% url 'myday:detail1' post.id %}"><p style="color: red">题目:{{ post.title }}</p></a><p>内容:{{ post.content }}</p><p>作者:{{ post.author }}</p>
{% endfor %}</body>
</html>
detail.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<p>题目{{ post.title }}</p>
<p>标签{{ post.tag }}</p>
<p>分类{{ post.category }}</p>
</body>
</html>
3.配置路由
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("index1",IndexView.as_view(),name="index1"),re_path("detail1/(\d+)",DetailView.as_view(),name="detail1"),
]
2.添加上下文
通常来说,get_context_data会将当前类中的上下文数据,合并父类中的上下文数据,传给模板。但前提是你调用了父类的get_context_data方法。
class IndexView(ListView):model = Posttemplate_name = 'index1.html'context_object_name = 'post_list'class DetailView(IndexView):model = Posttemplate_name = 'detail1.html'context_object_name = 'post'def get_queryset(self):print(self.args) #self.args用于接收路由中的动态参数return self.model.post_object.get(id=self.args[0])def get_context_data(self, **kwargs):context = super().get_context_data(**kwargs) #注意:要调用父类context ["dt"]= "独特" #增加字段return context
3.登录类视图
1.视图函数
class LoginView(View):name = "admin"pwd = "123"template_name = "login.html"def get(self, request, *args, **kwargs):return render(request, self.template_name)def post(self, request):list = request.POSTuname = list.get('uname')upwd = list.get('upwd')if uname == self.name and upwd == self.pwd:# 如果接收到的帐号密码与初始设在的一样那么表示登录成功需要跳转到首页# 重定向# 效果同样# return redirect('/myweb/index/')# return HttpResponseRedirect(reverse("myday:index"))return redirect("myday:index")else:# 帐号或密码错误,重新返回登录模板,并将用户输入的账号密码当参数传递回去。context = {'iserror': '帐号或密码错误','uname': uname,'upwd': upwd,"no_login": 1}return render(request, self.template_name, context=context)
2.HTML页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<form method="post" action="{% url 'myday:login_class' %}">用户名:<input name="uname" value="{{ uname }}">密码: <input name="upwd" value="{{ upwd }}"><input type="submit" value="登录">
</form>
{% if no_login %}<p>{{ iserror }}</p><p>{{ uname }}</p>
{% endif %}
<span style="color: red"></span></body>
</html>
3.配置路由
from django.contrib import admin
from django.urls import path,include,re_path
from .views import *
app_name = 'myday'
urlpatterns = [path("login_class",LoginView.as_view(),name="login_class"),
]
总结(如何选择)
类视图 | 用途 | 对应 HTTP 方法 |
---|---|---|
ListView | 显示多条数据(列表) | GET |
DetailView | 显示单条数据(详情) | GET |
CreateView | 创建新数据(表单提交) | GET (表单) + POST (提交) |
UpdateView | 修改已有数据(表单提交) | GET (表单) + POST (提交) |
DeleteView | 删除数据(确认删除) | GET (确认页) + POST (执行删除) |