一、认识基本的 CBV
CBV 就是 Class Base View ,基于类的视图。
基于类的视图提供另一种将视图实现为 Python 对象而不是函数的方法。它们不能替代基于函数的视图,但与基于函数的视图相比,它们是有某些不同和优势的。
与特定的 HTTP 方法(GET, POST, 等等)关联的代码组织能通过单独的方法替代条件分支来解决。
面向对象技术(比如 mixins 多重继承)可用于将代码分解为可重用组件。
本质上来说,基于类的视图允许你使用不同的类实例方法响应不同 HTTP 请求方法,而不是在单个视图函数里使用有条件分支的代码。
因此在视图函数里处理 HTTP GET 的代码应该像下面这样:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
而在基于类的视图里,会变成:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
return HttpResponse('result')
类视图工作流程:
因为 Django 的 URL 解析器期望发送请求和相关参数来调动函数而不是类,基于类的视图有一个
as_view()
类方法,当一个请求到达的 URL 被关联模式匹配时,这个类方法返回一个函数。这个函数创建一个类的实例,调用setup()
初始化它的属性,然后调用dispatch()
方法。dispatch
观察请求并决定它是GET
和POST
,等等。如果它被定义,那么依靠请求来匹配方法,否则会引发HttpResponseNotAllowed
。
# urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
说明:
CBV 的基于方法的视图返回值和基于函数的视图(FBV)返回值是相同的,既某种形式的HttpResponse
。
内置通用显示视图
示例 model
from django.db import models
from django.utils import timezone
# Create your models here.
from users.models import UsersProfile
class ArticlePost(models.Model):
'''
博客文章数据模型
'''
# 文章作者。参数 on_delete 用于指定数据删除的方式
author = models.ForeignKey(UsersProfile, related_name="users", verbose_name="作者 ID", on_delete=models.CASCADE)
# 文章标题。models.CharField 为字符串字段,用于保存较短的字符串,比如标题
title = models.CharField("标题", max_length=100)
# 文章正文。保存大量文本使用 TextField
body = models.TextField("正文")
# 文章浏览量 PositiveIntegerField是用于存储正整数的字段
views_counts = models.PositiveIntegerField("浏览量", default=0)
# 文章创建时间。参数 default=timezone.now 指定其在创建数据时将默认写入当前的时间
created = models.DateTimeField("创建时间", auto_now_add=True)
# 文章更新时间。参数 auto_now=True 指定每次数据更新时自动写入当前时间
updated = models.DateTimeField("更新时间", auto_now=True)
# 内部类 class Meta 用于给 model 定义元数据
class Meta:
# ordering 指定模型返回的数据的排列顺序
# '-created' 表明数据应该以倒序排列
ordering = ('-created',)
verbose_name = '文章'
verbose_name_plural = verbose_name
db_table = "article_post"
# 函数 __str__ 定义当调用对象的 str() 方法时的返回值内容
def __str__(self):
# return self.title 将文章标题返回
return f"{self.title}"
class Tag(models.Model):
"""
文章标签模型
"""
name = models.CharField("标签名称", max_length=12)
aticle = models.ManyToManyField(ArticlePost, related_name="tag")
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
db_table = 'tag'
def __str__(self):
return f"{self.name}"
ListView
返回一个模型对象列表。
可以指定模板文件路径名
自定义返回给前端模板语言中使用的对象的变量名
视图
class ArticlePostListView(ListView):
# 指定获取数据的 model
model = ArticlePost
# 指定模板语言中使用的变量名
context_object_name = 'postList'
# 指明模板名称, 默认是 `model所在的应用名/model 名称的小写_list.html`
template_name = 'blog/post-list.html'
说明:
模板文件要放置在设置好的 templates 正确目录下。
路由
path('post_list/', ArticlePostListView.as_view(), name='postList'),
模板
<table class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">文章标题</th>
<th scope="col">作者名字</th>
<th scope="col">浏览量</th>
<th scope="col">创建日期</th>
</tr>
</thead>
<tbody>
{% for article in postList %}
<tr>
<th scope="row">{{ forloop.counter }}</th>
<td>{{ article.id }}</td>
<td>{{ article.author.username }}</td>
<td>{{ article.views_counts }}</td>
<td>{{ article.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
对返回的对象列表进行过滤
指定 ·model = ArticlePost
实际上是
queryset = ArticlePost.objects.all()的简写。 所以,通过使用
queryset` 属性可以对返回的对象列表进行过滤控制。
例如下面的示例只是返回了以id
字段进行倒序排列的对象列表
class ArticlePostListView(ListView):
queryset = ArticlePost.objects.order_by("-id")
context_object_name = 'postList'
template_name = 'blog/post-list.html'
其他的都不需要改变
获取到路由的传参
大多数情况下,我们会通过 URL 给视图传递参数,视图通过这个参数作为过滤条件。
当基于类的视图被调用的时候,各种常用的东西被存储在 self
上,而且请求 (self.request
) 会根据(self.args) 从 URLconf 中抓取位置参数,根据 (self.kwargs) 抓取基于名称的关键字参数。
示例
path('post_list/<author>/', ArticlePostListView.as_view(), name='postAuthorList'),
当然,仅仅这样做还不够,接下来需要在视图中覆盖 LlistView
中的 了解我get_queryset()
方法。这个方法用于返回 queryset
属性的值。
我们可以在这个方法里添加更多的逻辑。
from django.shortcuts import get_object_or_404
from blog.models import ArticlePost,
from users.models import UsersProfile
class ArticlePostListView(ListView):
context_object_name = 'postList'
template_name = 'blog/post-list.html'
def get_queryset(self):
# 从用户 model 中获取到对应的作者对象
self.author = get_object_or_404(UsersProfile, username=self.kwargs['author'])
# 根据作者对象返回其所有的文章
return ArticlePost.objects.filter(author__username=self.author)
获取 GET 请求的参数
# views.py
uid = self.request.GET.get("id")
添加额外的数据
重写超类的 get_context_data
方法可以给返回前端模板的数据中增加额外的数据。
get_context_data
(*** kwargs*)
返回用于显示对象的上下文数据。
它返回包含以下内容的字典:
object
:此视图正在显示的对象(self.object
)。context_object_name
示例,在返回 model 中对象的同时,返回当前的时间。
from django.utils import timezone
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
模板中使用
context['now']
中的now
就是模板中使用的变量名
<span>Date: {{ now }}</span>
DetailView
用于通过条件过滤,获取到的 Model 中的单个对象,就是表中的一条数据。
同样可以指定返回的模板文件和模板语言中使用的数据变量名。
可以添加额外的数据。
注意需要从 URLconf 中捕获到参数,一般是用于基于主键的查找。
注意和 ListView 中添加过滤的区别,同样都是过滤,但是 DetailView 返回的是
单个对象
,就是一条数据
。
Example myapp/urls.py:
path('post_detail/<slug:pk>/', ArticlePostDetailView.as_view(), name='postDetail'),
Example myapp/views.py:
class ArticlePostDetailView(DeleteView):
model = ArticlePost
context_object_name = 'postDetail'
template_name = 'blog/post-detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
Example myapp/article_detail.html:
<h1> {{ postDetail.title}}</h1>
<p>{{ postDetail.author.username }}</p>
<p>日期: {{ now |date }}</p>
<p>{{ postDetail.body }}</p>
注意:
<slug:pk>
中的pk
, 是 URLconf 传给视图的一个关键字参数, 比如pk=1
。
DetailView 中的get_object
方法会依据该值执行基于主键的查找。
相当于model.Mymodelname.objects.filter(id=pk)
通过非主键字段进行过滤(扩展知识)
如果找不到参数
pk
,它会在 URLconf 查找一个slug_url_kwarg
指定的参数,默认值是slug
,并使用slug_field
的值, 默认是slug
。执行一个slug 查找, 这也表名了会进行非主键的过滤查找 。
相当于model.Mymodelname.objects.filter(slug=slug)
依据上面的意思,一般我们的 model 中不会存在 slug
字段,因此,我们往往会对 slug_field
的值进行更改,改为自己 model 中存在的,假设想使用此字段进行过滤查找的情况下。比如下面我们使用字段 updated
, 可以这样修改:
path('post_detail/<slug:slug>/', ArticlePostDetailView.as_view(), name='postDetail'),
class ArticlePostDetailView(DeleteView):
...
# 设置 model 中的字段名
slug_field = 'updated'
...
这样的情况下,查询数据库会是这样的
ArticlePost.objects.filter(updated=sulg)
> 注:slug 会是 url 中传递过来的值,比如 2019-07-01
注意: DetailView 视图的
get_object
方法只允许返回单个对象。
因此, 必须保证这个字段和其对应的值,只能在数据库表中查的一条数据。
添加额外的动作
有时候,经常会在访问一个视图的时候,可能想更新操作的 model 中的某个字段。
比如在 ArticlePost 中有个 last_accessed 字段,用来记录这条数据最后一次被访问的时间
class ArticlePostDetailView(DeleteView):
model = ArticlePost
context_object_name = 'postDetail'
template_name = 'blog/post-detail.html'
queryset = ArticlePost.objects.all()
def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj
关于验证的类
Form
基本示例
比如下面是用于对用户注册功能的 form
验证
1. 先创建一个 forms 类
# register_forms.py
from django import forms
class RegiterUserForm(forms.Form):
username = forms.CharField(
required=True,
)
password = forms.CharField(
required=True,
)
email = forms.EmailField(
required=True,
)
mobile = forms.CharField(
min_length=11,
max_length=11,
required=True,
)
2. 之后可以使用 FormView 构建视图:
# views.py
from django.views.generic.edit import FormView
class UserRegisterView(FormView):
template_name = 'register.html'
form_class = RegiterUserForm
success_url = reverse_lazy('users:usersLogin')
def form_valid(self, form):
# 数据有效,存库
from django.contrib.auth.hashers import make_password
pwd_md5 = make_password(form.cleaned_data['password'])
# 方式一
# form.cleaned_data['password'] = pwd_md5
# UsersProfile.objects.create(**form.cleaned_data)
# 方式二
user = UsersProfiles(**form.cleaned_data)
user.set_password(pwd_md5)
user.save()
return super().form_valid(form)
def form_invalid(self, form):
# 数据无效返回原来的模板,并且携带原来提交的数据
print("form-->", form)
return super().form_invalid(form)
注意:
FormView
继承了TemplateResponseMixin
,因此可以在这里使用template_name
。
form_valid()
默认实现了简单的重定向至success_url
。
3. 路由
path('register/', views.UserRegisterView.as_view(), name="register"),
4. HTML 文档
主意:基于 Bootstrap4
<form action="{% url 'users:register' %}" method="post">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
<label for="inputEmail4">用户名</label>
<input type="text" name="username" value="{{ form.username.value|default:'' }}" class="form-control" id="inputEmail4" placeholder="用户名">
{% if form.errors.username %}
<span class="alert alert-danger" role="alert">
{{ form.errors.username.0 }}
</span>
{% endif %}
</div>
<div class="form-group col-md-6">
<label for="inputPassword4">密码</label>
<input type="password" name="password" class="form-control" id="inputPassword4" placeholder="密码">
{% if form.errors.password %}
<span class="alert alert-danger" role="alert">
{{ form.errors.password.0 }}
</span>
{% endif %}
</div>
</div>
<div class="form-group">
<label for="inputAddress">邮箱地址</label>
<input type="email" value="{{ form.email.value|default:'' }}" name="email" class="form-control" id="inputAddress" placeholder="邮箱地址">
{% if form.errors.email %}
<span class="alert alert-danger" role="alert">
{{ form.errors.email.0 }}
</span>
{% endif %}
</div>
<div class="form-group">
<label for="inputAddress2">手机号</label>
<input type="text" name="mobile" value="{{ form.mobile.value|default:'' }}" class="form-control" id="inputAddress2" placeholder="手机号">
{% if form.errors.mobile %}
<span class="alert alert-danger" role="alert">
{{ form.errors.mobile.0 }}
</span>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
vlaue = {{ form.username.value|default:'' }}
假如有错误,返回此字段上次输入的内容到表单里。
default:''
是内置的过滤器,在此的意思是,假如返回的是控制,让页面上默认显示的是空字符串。
{% if form.errors.mobile %}
判断返回的 form 中的 mobile 字典是否有错误信息。
{{ form.errors.mobile.0 }}
取出错误信息。
从模型中创建表单
假如你对已有的 model 进行表单的验证,无需像之前使用 forms.Form
中那样定义每个字段的属性,应用在 model 中已经定义过了。
使用 ModelForm 可以让这件事很实现的异常简单。
model 示例
假设有如下 model, 用于记录用户的留言
from django.utils import timezone
ffrom django.db import models
class UserAsk(models.Model):
name = models.CharField('姓名', max_length=20)
mobile = models.CharField('手机', max_length=11)
article_name = models.CharField('文章名', max_length=50)
content = models.TextField('留言内容')
add_time = models.DateTimeField('添加时间', default=timezone.now)
class Meta:
verbose_name_plural = '用户留言'
db_table = 'user_ask'
def __str__(self):
return self.name
创建表
# 项目根目录下执行如下命令
# 1\. 生成数据移植文件
on git:master x$ python3 manage.py makemigrations
# 2\. 创建表结构
on git:master x$ python3 manage.py migrate
接下来创建一个自定义的 Form 类,需要继承 forms.ModelForm
from django import forms
from blog.models import UserAsk
class UserAskForm(forms.ModelForm):
class Meta:
model = UserAsk
fields = ['name', 'mobile', 'content', 'article_name']
model
属性指定自己的 modelfieldes
属性指定要要验证哪些字段
这里还可添加一些自定义的验证,比如验证手机号的合法性
import re
from django import forms
from blog.models import UserAsk
class UserAskForm(forms.ModelForm):
class Meta:
model = UserAsk
fields = ['name', 'mobile', 'article_name','content']
# 针对指定字段添加自定义验证
# 方法名格式要求: clean_字段名
def clean_mobile(self):
"""
验证手机号是否合法
:return: 合法的数据或者错误信息
"""
mobile = self.cleaned_data['mobile']
PRGEX_MOBILE = r'^1[358]\d{9}|^147\d{8}|^176\d{8}$'
regex = re.compile(PRGEX_MOBILE)
if regex.match(mobile):
return mobile
else:
raise forms.ValidationError(
'无效的手机号',
code='mobile_invalid'
)
如何使用
1. 路由
form .views import UserAskView
path('user_ask', UserAskView.as_view(), name='user_ask'),
2. 视图
class UserAskView(FormView):
# GET 请求时,返回此 模板
template_name = 'blog/user_ask.html'
# 自定义的Form 类
form_class = UserAskForm
# 提交数据成功后跳转到此 url
success_url = reverse_lazy('cbv:user_ask')
def form_valid(self, form):
# 数据有效
# 使用 save 方法可直接存入数据库
form.save()
return super().form_valid(form)
def form_invalid(self, form):
# 数据无效返回原来的模板,并且携带原来提交的数据
print(form.errors)
return super().form_invalid(form)
save() 方法
每个 ModelForm 都有一个save()
方法,此方法根据绑定到表单的数据创建并保存数据库对象。
3. HTML 模板
<form action="{% url 'cbv:user_ask' %}" method="post">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-6">
<label for="inputEmail4">用户名</label>
<input type="text" name="name"
value="{{ form.name.value|default:'' }}"
class="form-control" id="inputEmail4"
placeholder="用户名"
>
{% if form.errors.name %}
<span class="alert alert-danger" role="alert">
{{ form.errors.name.0 }}
</span>
{% endif %}
</div>
</div>
<div class="form-group">
<label for="inputAddress2">手机号</label>
<input type="text" name="mobile" value="{{ form.mobile.value|default:'' }}" class="form-control" id="inputAddress2" placeholder="手机号">
{% if form.errors.mobile %}
<span class="alert alert-danger" role="alert">
{{ form.errors.mobile.0 }}
</span>
{% endif %}
</div>
<div class="form-group">
<label for="articleName">文章名</label>
<input
name="article_name" type="text"
class="form-control" id="articleName"
placeholder="文章名"
value="{{form.article_name.value|default:''}}"
>
{% if form.errors.article_name %}
<span class="alert alert-danger" role="alert">
{{ form.errors.article_name.0 }}
</span>
{% endif %}
</div>
<div class="form-group">
<label for="exampleFormControlTextarea1">留言内容</label>
<textarea
class="form-control"
id="exampleFormControlTextarea1"
name='content'
>
{{form.content.value|default:''}}
</textarea>
<small class="form-text text-muted">
这里可以添加一些提示帮助的信息,比如:
欢迎留言
</small>
{% if form.errors.content %}
<span class="alert alert-danger" role="alert">
{{ form.errors.content.0 }}
</span>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
关于更新
ModelForm 的子类可接受一个现有的模型实例作为关键字参数 instance 的值;如果提供了,则 save() 会更新这个实例。如果没有,则 save() 会创建一个对应模型的新实例。
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
save() 方法接受一个可选参数 commit ,它的值是 True 或者 False 。默认是 True。
调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象。
ModelForm 与普通的表单工作方式一样。例如,用 is_valid() 方法来检查合法性,用 is_multipart() 方法来确定表单是否需要multipart文件上传(之后是否必须将 request.FILES 传递给表单),等等。
选择要使用的字段
Django 强烈建议您使用 fields 属性来显式设置所有应在表单中编辑的字段。
这是处于安全考虑。
- 明确指明使用所有的字段进行验证
fields = '__all__'
- 明确的排除不用验证的字段
exclude = ['article_name']
覆盖默认字段的属性
自定义小部件
如果您希望 Author 的 name 属性的 CharField 由 <textarea> 代替默认的 <input type="text"> 来表示,您可以重写字段的部件:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
widgets = {
'name': Textarea(attrs={'cols': 80, 'rows': 20}),
}
自定义错误信息
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
labels = {
'name': _('Writer'),
}
help_texts = {
'name': _('Some useful help text.'),
}
error_messages = {
'name': {
'max_length': _("This writer's name is too long."),
},
}
提供初始值
与普通表单一样,可以在实例化表单时通过指定 initial
参数来指定表单的初始值。以这种方式提供的初始值会覆盖表单字段的初始值以及对应模型实例的初始值。例如:
>>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'</pre>
网友评论