7.1 基于类的视图

本章以“在线学习”的应用为载体。深化对Django的理解,继续讲解一些新的技能。下面先做好准备工作。

  1. 创建一个新的应用。

     

  2. 在./testsite/settings.py中配置应用。

做好了开发准备,下面就从一个新技能--------“基于类的视图”开始。

在前述各章中,已经反复编写了视图函数------在视图文件(多数是views.py)中编写的函数,这种方式被称为“基于函数的视图”,以这种方式能够完成所有Django开发中的功能。尽管如此,Django还提供了一种完成同样功能的代码编写方式-------class-based views,通常称为“基于类的视图”。

暂且不讨论“基于类的视图”与前面所编写的视图函数有什么区别,也不解释为什么还有这样一种写法-----“凡是存在的就是合理的,凡是合理的就是存在的”(黑格尔)。我们还是奉行“在做中学”的观点,请读者一边阅读,一边动手敲代码并进行对比和思考。基于类的视图如下图所示。

 

7.1.1 最简类视图

假设需要在网站中设计一个页面,这个页面用于向浏览者介绍网站,也就是所谓的“关于本站”。请读者运用前面已经学习的技能,想想这个功能应该如何实现?在此驻足几分钟,有了想法,再继续阅读。

读者肯定能够用以往学习的技能实现上述功能,但此处我们换一种方式(这种方法曾经在前文出现过)。

在./testsite/urls.py中为course应用配置URL,代码如下。

path('course/',include('course.urls',namespace='course')),

创建./course/urls.py文件,并输入如下代码。

from django.urls import path
from django.views.generic import TemplateView  #①
app_name = "course"
urlpatterns = [ path('about/',TemplateView.as_view(template_name="course/about.html")), #②
]

这个URL的配置与以往有所不同,能发现区别吗?仔细观察,不难发现区别。暂不解释,继续敲代码。

在./templates这个模板目录中创建一个子目录course,然后在里面创建文件about.html,即./templates/course/about.html,输入如下代码。

{% extends "base.html" %}
{% block title %}About itdiffer.com{% endblock %}
{% block content %}
<div class="container">
    <h1 class="text-center">Welcome itdiffer.com</h1>
    <p class="text-center">www.itdiffer.com是老齐用来发布自己所编写的教程网站,供广大编程者学习。</p>
    <p class="text-center">QQ群号:26913719</p>
    <h2 class="text-center">欢迎访问。</h2>
</div> 
{% endblock %}

运行Django服务,在浏览器的地址栏中输入127.0.0.1:8000/course/about/,效果如下图所示。

仔细思考,是不是感觉上面的过程比以往少了点什么?没有写视图函数,但也能完成访问,其根源就在于语句①和语句②。

语句①引入的TemplateView就是最常用使用的基于类的通用视图之一,它的作用是返回模板,在语句②中直接使用TemplateView.as_view()是它的用法之一,并且通过参数template_name指定模板。

请重点关注一下语句②,这里没有直接使用TemplateView(template_name="course/about.html”)的方式,而是使用了TemplateView的方法as_view(),这是因为在URL解析中,Django依然只认函数,这跟前面在这里写视图函数是类似的。不仅是TemplateView,后面遇到的其他类也都有as_view()方法,它作为类的可调用入口,传递给as_view()的参数覆盖类中的属性,在语句②中就借此声明了模板文件。

在视图函数中,要向函数传递一个HttpRequest对象,需要request参数,这是我们已经了解的。在语句②中,也是通过as_view()函数传给了request,并且创建类视图的实例,然后调用类视图的dispatch()方法,注意这个方法并没有在上面显示出来。dispatch()方法会根据不同的访问方式做出响应。

对于刚才的功能,还可以换另外一种写法。

在./course/views.py中,编写基于类的视图,代码如下。

from django.views.generic import TemplateView

class AboutView(TemplateView):
    template_name = "course/about.html"

除引入TemplateView外,再创建一个类AboutView,它继承TemplateView,并且设置一个属性template_name,用来声明模板文件,这跟前面语句②中的效果是一样的。那么,在./course/urls.py中就需要进行修改了(或者将原来的URL设置语句注释掉)。

from django.urls import path
from .views import AboutView #③

app_name = "course"
urlpatterns = [
    path('about/',AboutView.as_view(),name="about"),  #④
]

语句③引入了在./course/views.py中写的类AboutView,因为它继承了TemplateView,所以也有as_view()方法,在语句④中依然使用了这个方法,但是不需要转入template_name参数,因为已经在AboutView类中声明了。

确保Django服务已经启动,再次访问http://127.0.0.1:8000/course/about/,效果同前。

最后,把刚才的“关于本站”放到顶部导航中。打开./templates/header.html文件,在导航栏中增加如下代码:

<li><a href="{% url 'course:about' %}">关于本站</a> </li>

注意代码的位置。

以上仅对基于类的视图进行了初步了解,读者可能还没有体会到它的魅力。

7.1.2 读取数据

前面做的都是静态页面,没有跟数据库打交道。如果要对数据库进行读、写,该怎么办?

Django中定义了一系列的通用视图,存放在django.views.generic中,包括ListView、CreateView、UpdataView、DeleteView等,本节将逐一用到。

为了实现对数据库的操作,先要有数据库表,对此我们已经很熟悉了,需要创建数据模型类。在./course/models.py文件中创建Course类,用来保存课程名称和相关描述,代码如下。

from django.db import models
from django.contrib.auth.models import User
from slugify import slugify

class Course(models.Model):
    user = models.ForeignKey(User,on_delete=models.CASCADE, related_name="courses_user")
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200,unique=True)
    overview = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-created',)

    def save(self,*args,**kargs):
        self.slug = slugify(self.title)
        super(Course,self).save(*args,**kargs)

    def __str__(self):
        return self.title

 

Course数据模型建立好之后,执行python manage.py makemigrations和python manage.py migrate命令迁移数据,从而在数据库中创建了名为course_course的数据库表。

下面就使用ListView来读取课程数据(为了在页面上能够显示读取的结果,请读者根据自己的理解方式,向数据库表course_course中增加若干条记录)。

在./course/views.py中编写类视图,代码如下。

from django.views.generic import TemplateView,ListView #①
from .models import Course #②

class CourseListView(ListView): #③
    model = Course #④
    context_object_name = "courses" #⑤
    template_name = 'course/course_list.html'  #⑥

语句①引入ListView,语句②将曾经创建的数据模型类Course引入。

重点看语句③及其后的内容。语句③声明了类,并且继承ListView。语句④声明本类所用到的数据模型,通过语句④就能够得到相应数据库表(此处是course_course表)中的所有记录,但是没有使用Course.objects.all(),而是用语句④的方式简捷地表达了。

语句⑤声明了传入模板中的变量名称。如果不写语句⑤,则模板默认变量名称是object。

语句⑥是已经熟悉的声明模板文件。紧接着就根据这里的路径创建这个模板文件(./templates/course/course_list.html),并且输入如下代码。

{% extends "base.html" %}
{% block title %}Course List{% endblock %}
{% block content %}
<div class="container">
    <table class="table table-hover">
        <tr>
            <td>序号</td>
            <td>课程标题</td>
            <td>讲师</td>
            <td>发布日期</td>
        </tr>
        {% for course in courses %}
        <tr id={{ forloop.counter }}>
            <td>{{ forloop.counter }}</td>
            <td>{{ course.title }}</td>
            <td>{{ course.user.username }}</td>
            <td>{{ course.created }}</td>
        </tr>
        {% endfor %}
    </table>
</div>
{% endblock %}

为了能够浏览,还要在./course/urls.py文件中增加如下设置(在本文件中首先要引入CourseListView,即from.views import CourseListView)

path('course-list/',CourseListView.as_view(),name="course_list"),

启动Django服务,访问http://127.0.0.1:8000/course/course-list/,效果如下图所示。

看到了课程列表,把这个功能也放到导航栏中。在./templates/header.html文件中增加如下代码。 

<li><a href="{% url 'course:course_list' %}">课程</a> </li>

再刷新页面,欣赏自己的成果。

在CourseListView类的代码中,语句④相当于实现了Course.objects.all()功能,即将所有记录都读出来。而在某些时候,需要根据条件对数据进行筛选,例如在刚才显示的页面中,只显示某个讲师(admin)的课程,其中一种方法是重写语句④,用下面的语句替代语句④(为了使用下面的语句,不要忘记在文件中引入User,即from django.contrib.auth.models import User)。

queryset = Course.objects.filter(user=User.objects.filter(username="admin"))

此外,还有一种方法就是重写get_queryset()方法。这种方法在Django的很多类中都有,它的作用就是读取数据库并返回结果(QuerySet)。在CourseListView类中增加如下代码。

def get_queryset(self):
    qs = super(CourseListView,self).get_queryset()  #⑦
    return qs.filter(user=User.objects.filter(username="admin")) #⑧

语句⑦调用了父类的get_queryset()方法,语句⑧则对得到的对象依据条件进行筛选。

以上对基于类的视图做了初步讲解,这仅仅是开始。

7.1.3 初步了解Mixin

CourseListView类继承ListView类,CourseListView是单继承。在Python的类中,除能够单继承外,还能够实现多重继承,即一个类可以有多个父类。Django中有一种常用的继承方式-----Mixin------可用于多重继承,一些人把它翻译为“迷信”。什么是Mixin?

Mix-in(注意写法)本不是编程术语,它是指一种冰激凌和另外一种风味的甜食混合而成的食物,Mix即英文mixture的缩写。后来,在面向对象的编程语言中出现了Mixin,这个术语是否还有“混合”的含义呢?

在类CourseListView中,我们要设定model=Course,这是声明在当前类中读取数据的对象就是Course,随后在增加、删除等类中也都要有model=Course,同样的代码要不断重复写。编码要尽可能避免重复,这是程序员的共识,避免重复的一种有效方法是使用“类继承”。我们可以先写一个类,比如这个类就是包含model=Course的,后面用到它时就继承这个类,而不用重复写这句话。

写一个类供其他类继承,的确是一个好方法。为了更明显地标记这个类,使其在名称上就可以体现出它的存在价值就是被别的类继承,或者类比Mix-in那种食物,这个类就是要跟别的类混合在一起用的,所以在命名时常常以Mixin结尾。

在./course/views.py中输入如下代码。

class UserMixin: #①
    def get_queryset(self):
        qs = super(UserMixin, self).get_queryset()
        return qs.filter(user=self.request.user)
    
class UserCourseMixin(UserMixin): #②
    model = Course
    
class ManageCourseListView(UserCourseMixin,ListView): #③
    context_object_name = "course"
    template_name = 'course/manage/manage_course_list.html'

以上代码用于用户登录后,进入“后台管理”,对课程进行“增删改查”等操作。

语句①创建了类UserMixin,表示这个类将被用于后面的类中,而不是作为视图使用。对于Python3而言,语句①的参数object可写可不写,因为每个类都以object为父类。

语句②还是一个Mixin,但它继承了UserMixin,意味着语句①中所定义的方法也被带入到语句②中。

请读者认真观察语句③中的继承顺序,这种顺序是有讲究的,一般将Mixin类放在左边,其他类放在右边。这里显然是多重继承,类UserCourseMixin所代入的就是在语句①和语句②两个类中所定义的方法和属性。类似的,还能够创建其他的类,继承语句①或者语句②中的类,从而不必在类中重复相同的代码。

这就是Mixin。

在Django中,已经有一些做好的Mixin,可以让开发者直接拿过来使用,例如TemplateResponseMixin和SingleObjectMixin等,甚至还有django-braces(http://django-braces.readthedocs.io/en/latest/index.html)提供更多唾手可得的Mixin,后面我们将使用到。

7.1.4 知识点

1、JSON

JSON的全称是JavaScript Object Notation,意思是JavaScript对象表示法,是一种独立于语言的轻量级数据交换格式,它使得人们很容易地进行阅读和编写,同时也方面了机器进行解析和生成。

JSON有以下两种表示结构。

  • 结构1:类Python字典模式,例如obj_json = {"name":"django","age":23,"driver":false}或者obj_json = {name:"django",age:23,driver:false}(注意观察区别)。在这种“键值”对的结构中,“键”只能是字符串(可以用引号包裹,也可以不写引号),“值”可以是字符串(string)、数值(number)、布尔值(true、fasle)、null、对象(object)和数组(array)。
  • 结构2:类Python列表模式,在JSON中有一个专有名称-----数组,例如arr_json=["python","php","java"],数组中的值也必须是字符串、数值、对象、数组、布尔值和null类型。

在JavaScript的代码中使用JSON时,常说“JSON字符串”,这个字符串非常简单用引号包裹的字符串(如var lady = "xlaoshi"),而是符合JSON格式的JavaScript字符串,例如var lady = "{age:23,name:'xlaoshi',site:'itdiffer.com'}"。

下面要演示如何在Django的视图函数中返回JSON值,代码如下。

import json
from django.http import HttpResponse

def foo_view(request):
    data = {'name':'xlaoshi','web':'django'}
    return HttpResponse(json.dumps(data),content_type='application/json')

如果使用本章开始的基于类的视图,比如响应前端的POST请求,要返回JSON值,可以使用django-braces提供的第三方Mixin解决此问题,代码如下。

from braces.views import CsrfExemptMixin,JsonRequestResponseMixin
class FooView(CsrfExemptMixin,JsonRequestResponseMixin):
    def post(self,request,*args, **kwargs):
        data = {'name’:‘xlaoshi’,'web':'django'}
        return self.render_json_response(data)

 

 

posted @ 2019-06-10 14:41  taoziya  阅读(248)  评论(0)    收藏  举报