43.静态资源和Ajax请求.md 11.4 KB
Newer Older
J
jackfrued 已提交
1
## 静态资源和Ajax请求
2

3
基于前面两个章节讲解的知识,我们已经可以使用Django框架来完成Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入应用首先查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“老师介绍”页面,该页面展示了该学科所有老师的详细情况,可以在该页面上给老师点击“好评”或“差评”;如果用户没有登录,在投票时会先跳转到“登录页”要求用户登录,登录成功才能投票;对于未注册的用户,可以在“登录页”点击“新用户注册”进入“注册页”完成用户注册操作,注册成功后会跳转到“登录页”,注册失败会获得相应的提示信息。
4

5 6
### 准备工作

J
jackfrued 已提交
7
由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,从上面对投票应用需求的描述中我们可以分析出三个业务实体:学科、老师和用户。学科和老师之间通常是一对多关联关系(一个学科有多个老师,一个老师通常只属于一个学科),用户因为要给老师投票,所以跟老师之间是多对多关联关系(一个用户可以给多个老师投票,一个老师也可以收到多个用户的投票)。首先修改应用下的models.py文件来定义数据模型,先给出学科和老师的模型。
8 9

```Python
10
from django.db import models
J
jackfrued 已提交
11

12 13 14

class Subject(models.Model):
    """学科"""
J
jackfrued 已提交
15 16 17 18 19
    no = models.IntegerField(primary_key=True, verbose_name='编号')
    name = models.CharField(max_length=20, verbose_name='名称')
    intro = models.CharField(max_length=511, default='', verbose_name='介绍')
    create_date = models.DateField(null=True, verbose_name='成立日期')
    is_hot = models.BooleanField(default=False, verbose_name='是否热门')
20 21 22 23 24 25

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'tb_subject'
J
jackfrued 已提交
26
        verbose_name = '学科'
27 28 29 30 31 32
        verbose_name_plural = '学科'


class Teacher(models.Model):
    """老师"""
    no = models.AutoField(primary_key=True, verbose_name='编号')
J
jackfrued 已提交
33 34 35
    name = models.CharField(max_length=20, verbose_name='姓名')
    detail = models.CharField(max_length=1023, default='', blank=True, verbose_name='详情')
    photo = models.CharField(max_length=1023, default='', verbose_name='照片')
36 37 38 39 40 41
    good_count = models.IntegerField(default=0, verbose_name='好评数')
    bad_count = models.IntegerField(default=0, verbose_name='差评数')
    subject = models.ForeignKey(to=Subject, on_delete=models.PROTECT, db_column='sno', verbose_name='所属学科')

    class Meta:
        db_table = 'tb_teacher'
J
jackfrued 已提交
42
        verbose_name = '老师'
43
        verbose_name_plural = '老师'
44 45
```

46
模型定义完成后,可以通过“生成迁移”和“执行迁移”来完成关系型数据库中二维表的创建,当然这需要提前启动数据库服务器并创建好对应的数据库,同时我们在项目中已经安装了PyMySQL而且完成了相应的配置,这些内容此处不再赘述。
47

48
```Shell
49
(venv)$ python manage.py makemigrations vote
50 51 52 53
...
(venv)$ python manage.py migrate
...
```
54

55
> 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
56

J
jackfrued 已提交
57
完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类,可以通过修改``。
58

59
```SQL
60 61
from django.contrib import admin

J
jackfrued 已提交
62 63
from poll2.forms import UserForm
from poll2.models import Subject, Teacher
64 65


J
jackfrued 已提交
66 67
class SubjectAdmin(admin.ModelAdmin):
    list_display = ('no', 'name', 'create_date', 'is_hot')
68 69 70
    ordering = ('no', )


J
jackfrued 已提交
71 72 73
class TeacherAdmin(admin.ModelAdmin):
    list_display = ('no', 'name', 'detail', 'good_count', 'bad_count', 'subject')
    ordering = ('subject', 'no')
J
jackfrued 已提交
74

75

J
jackfrued 已提交
76 77
admin.site.register(Subject, SubjectAdmin)
admin.site.register(Teacher, TeacherAdmin)
78 79 80 81 82
```

接下来,我们就可以修改views.py文件,通过编写视图函数先实现“学科介绍”页面。

```Python
83
def show_subjects(request):
84 85 86
    """查看所有学科"""
    subjects = Subject.objects.all()
    return render(request, 'subject.html', {'subjects': subjects})
87
```
88

J
jackfrued 已提交
89
至此,我们还需要一个模板页,模板的配置以及模板页中模板语言的用法在之前已经进行过简要的介绍,如果不熟悉可以看看下面的代码,相信这并不是一件困难的事情。
90 91 92 93 94 95

```HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
J
jackfrued 已提交
96
    <title>所有学科信息</title>
97
    <style>/* 此处略去了层叠样式表的选择器 */</style>
98 99
</head>
<body>
J
jackfrued 已提交
100
    <h1>所有学科</h1>
101
    <hr>
J
jackfrued 已提交
102 103 104 105 106 107 108 109 110
    {% for subject in subjects %}
    <div>
        <h3>
            <a href="/teachers/?sno={{ subject.no }}">{{ subject.name }}</a>
            {% if subject.is_hot %}
            <img src="/static/images/hot.png" width="32" alt="">
            {% endif %}
        </h3>
        <p>{{ subject.intro }}</p>
111
    </div>
J
jackfrued 已提交
112
    {% endfor %}
113 114
</body>
</html>
115 116
```

117
在上面的模板中,我们为每个学科添加了一个超链接,点击超链接可以查看该学科的讲师信息,为此需要再编写一个视图函数来处理查看指定学科老师信息。
118 119

```Python
120
def show_teachers(request):
J
jackfrued 已提交
121
    """显示指定学科的老师"""
122 123 124
    try:
        sno = int(request.GET['sno'])
        subject = Subject.objects.get(no=sno)
J
jackfrued 已提交
125 126
        teachers = subject.teacher_set.all()
        return render(request, 'teachers.html', {'subject': subject, 'teachers': teachers})
127 128
    except (KeyError, ValueError, Subject.DoesNotExist):
        return redirect('/')
129 130
```

131
显示老师信息的模板页。
132 133 134

```HTML
<!DOCTYPE html>
135
{% load static %}
136 137 138
<html lang="en">
<head>
    <meta charset="UTF-8">
J
jackfrued 已提交
139
    <title>老师</title>
140
    <style>/* 此处略去了层叠样式表的选择器 */</style>
141 142
</head>
<body>
143
    <h1>{{ subject.name }}学科老师信息</h1>
144 145
    <hr>
    {% if teachers %}
J
jackfrued 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159
    {% for teacher in teachers %}
    <div>
        <div>
            <img src="{% static teacher.photo %}" alt="">
        </div>
        <div>
            <h3>{{ teacher.name }}</h3>
            <p>{{ teacher.detail }}</p>
            <p class="comment">
                <a href="">好评</a>
                (<span>{{ teacher.good_count }}</span>)
                <a href="">差评</a>
                (<span>{{ teacher.bad_count }}</span>)
            </p>
160
        </div>
161
    </div>
J
jackfrued 已提交
162
    {% endfor %}
163
    {% else %}
J
jackfrued 已提交
164
    <h3>暂时没有该学科的老师信息</h3>
165
    {% endif %}
J
jackfrued 已提交
166 167 168
    <p>
        <a href="/">返回首页</a>
    </p>
169 170 171 172
</body>
</html>
```

173
### 加载静态资源
174

175
在上面的模板页面中,我们使用了`<img>`标签来加载老师的照片,其中使用了引用静态资源的模板指令`{% static %}`,要使用该指令,首先要使用`{% load static %}`指令来加载静态资源,我们将这段代码放在了页码开始的位置。在上面的项目中,我们将静态资源置于名为static的文件夹中,在该文件夹下又创建了三个文件夹:css、js和images,分别用来保存外部层叠样式表、外部JavaScript文件和图片资源。为了能够找到保存静态资源的文件夹,我们还需要修改Django项目的配置文件settings.py,如下所示:
176 177 178 179 180 181 182 183 184 185

```Python
# 此处省略上面的代码

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
STATIC_URL = '/static/'

# 此处省略下面的代码
```

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
接下来修改urls.py文件,配置用户请求的URL和视图函数的对应关系。

```Python
from django.contrib import admin
from django.urls import path

from vote import views

urlpatterns = [
    path('', views.show_subjects),
    path('teachers/', views.show_teachers),
    path('admin/', admin.site.urls),
]
```

J
jackfrued 已提交
201
启动服务器运行项目,进入首页查看学科信息。
202

J
jackfrued 已提交
203
![](./res/show_subjects.png)
204

J
jackfrued 已提交
205 206
点击学科查看老师信息。

J
jackfrued 已提交
207
![](./res/show_teachers.png)
208 209 210

### Ajax请求

211
接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”,Ajax技术我们在Web前端部分已经介绍过了,此处不再赘述。
212

213
首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。
214 215 216 217 218

```Python
from django.contrib import admin
from django.urls import path

219
from vote import views
220 221

urlpatterns = [
222 223 224 225
    path('', views.show_subjects),
    path('teachers/', views.show_teachers),
    path('praise/', views.prise_or_criticize),
    path('criticize/', views.prise_or_criticize),
226 227 228 229
    path('admin/', admin.site.urls),
]
```

230
设计视图函数`praise_or_criticize`来支持“好评”和“差评”功能,该视图函数通过Django封装的JsonResponse类将字典序列化成JSON字符串作为返回给浏览器的响应内容。
231 232

```Python
233 234
def praise_or_criticize(request):
    """好评"""
235
    try:
236 237
        tno = int(request.GET['tno'])
        teacher = Teacher.objects.get(no=tno)
238
        if request.path.startswith('/praise'):
239 240 241 242
            teacher.good_count += 1
        else:
            teacher.bad_count += 1
        teacher.save()
243 244 245 246
        data = {'code': 200, 'hint': '操作成功'}
    except (KeyError, ValueError, Teacher.DoseNotExist):
        data = {'code': 404, 'hint': '操作失败'}
    return JsonResponse(data)
247 248
```

249
修改显示老师信息的模板页,引入jQuery库来实现事件处理、Ajax请求和DOM操作。
250 251

```HTML
252 253 254 255 256
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
    <meta charset="UTF-8">
J
jackfrued 已提交
257 258
    <title>老师</title>
    <style>/* 此处略去了层叠样式表的选择器 */</style>
259 260
</head>
<body>
J
jackfrued 已提交
261
    <h1>{{ subject.name }}学科老师信息</h1>
262
    <hr>
J
jackfrued 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    {% if teachers %}
    {% for teacher in teachers %}
    <div class="teacher">
        <div class="photo">
            <img src="{% static teacher.photo %}" height="140" alt="">
        </div>
        <div class="info">
            <h3>{{ teacher.name }}</h3>
            <p>{{ teacher.detail }}</p>
            <p class="comment">
                <a href="/praise/?tno={{ teacher.no }}">好评</a>
                (<span>{{ teacher.good_count }}</span>)
                &nbsp;&nbsp;
                <a href="/criticize/?tno={{ teacher.no }}">差评</a>
                (<span>{{ teacher.bad_count }}</span>)
            </p>
279 280
        </div>
    </div>
J
jackfrued 已提交
281 282 283 284 285 286 287 288
    {% endfor %}
    {% else %}
    <h3>暂时没有该学科的老师信息</h3>
    {% endif %}
    <p>
        <a href="/">返回首页</a>
    </p>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
289 290
    <script>
        $(() => {
J
jackfrued 已提交
291
            $('.comment>a').on('click', (evt) => {
292
                evt.preventDefault()
J
jackfrued 已提交
293 294 295 296 297
                let anchor = $(evt.target)
                let url = anchor.attr('href')
                $.getJSON(url, (json) => {
                    if (json.code == 10001) {
                        let span = anchor.next()
298 299
                        span.text(parseInt(span.text()) + 1)
                    } else {
J
jackfrued 已提交
300
                        alert(json.hint)
301 302
                    }
                })
303 304
            })
        })
305 306 307
    </script>
</body>
</html>
308 309 310 311
```

### 小结

骆昊的技术专栏's avatar
骆昊的技术专栏 已提交
312
到此为止,这个投票项目的核心功能已然完成,在下面的章节中我们会要求用户必须登录才能投票,没有账号的用户可以通过注册功能注册一个账号。