| 
1 |  | -## Django 2.x实战(03) - 请求和响应详解  | 
 | 1 | +## Django 2.x实战(03) - 静态资源和Ajax请求  | 
2 | 2 | 
 
  | 
3 | 3 | 基于前面两个章节讲解的知识,我们已经可以使用Django框架来实现Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入系统首先来到“登录页”;登录成功后可以查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“讲师详情”页面,该页面展示了该学科所有讲师的详细情况,可以在该页面上给讲师点击“好评”或“差评”;对于未注册的用户,可以在登录页点击“新用户注册”进入“注册页”完成用户注册,注册成功或失败都会获得相应的提示信息,注册成功后会返回“登录页”。  | 
4 | 4 | 
 
  | 
5 |  | -由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,我们将项目命名为`hellodjango`,在项目下创建了一个名为`demo`的应用。从“学科介绍”和“讲师详情”页面的需求,我们可以首先分析出两个业务实体,一个是学科,一个是讲师,二者之前是一对多关联。因此,我们首先修改应用`demo`下的models.py文件来定义数据模型。  | 
 | 5 | +### 准备工作  | 
 | 6 | + | 
 | 7 | +由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,我们将项目命名为hellodjango,在项目下创建了一个名为demo的应用。从“学科介绍”和“讲师详情”页面的需求,我们可以首先分析出两个业务实体,一个是学科,一个是讲师,二者之前是一对多关联。因此,我们首先修改应用demo下的models.py文件来定义数据模型。  | 
6 | 8 | 
 
  | 
7 | 9 | ```Python  | 
8 | 10 | 
 
  | 
@@ -124,3 +126,255 @@ def show_subjects(request):  | 
124 | 126 | </html>  | 
125 | 127 | ```  | 
126 | 128 | 
 
  | 
 | 129 | +启动服务器,运行效果如下图所示。  | 
 | 130 | + | 
 | 131 | +  | 
 | 132 | + | 
 | 133 | +### 加载静态资源  | 
 | 134 | + | 
 | 135 | +在上面的模板中,我们为每个学科添加了一个超链接,点击超链接可以查看该学科的讲师信息,为此我们得修改项目的urls.py文件配置一个新的URL。  | 
 | 136 | + | 
 | 137 | +```Python  | 
 | 138 | + | 
 | 139 | +from django.contrib import admin  | 
 | 140 | +from django.urls import path  | 
 | 141 | + | 
 | 142 | +from demo import views  | 
 | 143 | + | 
 | 144 | +urlpatterns = [  | 
 | 145 | +    path('', views.show_subjects),  | 
 | 146 | +    path('subjects/<int:no>', views.show_teachers),  | 
 | 147 | +    path('admin/', admin.site.urls),  | 
 | 148 | +]  | 
 | 149 | +```  | 
 | 150 | + | 
 | 151 | +Django 2.x在配置URL时可以使用如上面所示的占位符语法,而且可以指定占位符的类型,因为在查询学科讲师信息时,需要传入该学科的编号作为条件,而学科编号在定义模型时设定为`AutoField`,其本质就是`int`类型。相较于Django 1.x中使用正则表达式的命名捕获组来从URL中获取数据(如果对Django 1.x并没有什么概念,这句话可以暂时忽略不计),这种更加优雅的写法可以让我们在视图函数中直接获得学科编号,代码如下所示。  | 
 | 152 | + | 
 | 153 | +```Python  | 
 | 154 | + | 
 | 155 | +def show_teachers(request, no):  | 
 | 156 | +    teachers = Teacher.objects.filter(subject__no=no)  | 
 | 157 | +    ctx = {'teachers_list': teachers}  | 
 | 158 | +    return render(request, 'demo/teacher.html', ctx)  | 
 | 159 | +```  | 
 | 160 | + | 
 | 161 | +接下来我们可以定制“讲师详情”的模板页。  | 
 | 162 | + | 
 | 163 | +```HTML  | 
 | 164 | + | 
 | 165 | +<!DOCTYPE html>  | 
 | 166 | +{% load staticfiles %}  | 
 | 167 | +<html lang="en">  | 
 | 168 | +<head>  | 
 | 169 | +    <meta charset="UTF-8">  | 
 | 170 | +    <title>讲师信息</title>  | 
 | 171 | +    <style>  | 
 | 172 | +        .container {  | 
 | 173 | +            width: 960px;  | 
 | 174 | +            margin: 0 auto;  | 
 | 175 | +        }  | 
 | 176 | +        .basic {  | 
 | 177 | +            width: 60%;  | 
 | 178 | +            float: left;  | 
 | 179 | +        }  | 
 | 180 | +        .potrait {  | 
 | 181 | +            width: 40%;  | 
 | 182 | +            float: left;  | 
 | 183 | +            text-align: right;  | 
 | 184 | +        }  | 
 | 185 | +        hr {  | 
 | 186 | +            clear: both;  | 
 | 187 | +        }  | 
 | 188 | +        .button {  | 
 | 189 | +            display: inline-block;  | 
 | 190 | +            width: 80px;  | 
 | 191 | +            height: 30px;  | 
 | 192 | +            background-color: red;  | 
 | 193 | +            color: white;  | 
 | 194 | +            font: 16px/30px Arial;  | 
 | 195 | +            text-decoration: none;  | 
 | 196 | +            text-align: center;  | 
 | 197 | +        	margin-bottom: 10px;  | 
 | 198 | +		}  | 
 | 199 | +    </style>  | 
 | 200 | +</head>  | 
 | 201 | +<body>  | 
 | 202 | +    {% for x in teachers_list %}  | 
 | 203 | +    <div class="container">  | 
 | 204 | +        <div class="basic">  | 
 | 205 | +            <h1>{{ x.name }}老师</h1>  | 
 | 206 | +            <p><strong>讲师简介</strong></p>  | 
 | 207 | +            <p>{{ x.intro }}</p>  | 
 | 208 | +            <p><strong>教学理念</strong></p>  | 
 | 209 | +            <p>{{ x.motto }}</p>  | 
 | 210 | +            <a href="/good/{{ x.no }}" class="button">好评({{ x.gcount }})</a>  | 
 | 211 | +            <a href="/bad/{{ x.no }}" class="button">差评({{ x.bcount }})</a>  | 
 | 212 | +        </div>  | 
 | 213 | +        <div class="potrait">  | 
 | 214 | +            {% if x.photo %}  | 
 | 215 | +            <img src="{% static x.photo %}">  | 
 | 216 | +            {% endif %}  | 
 | 217 | +        </div>  | 
 | 218 | +        <hr>  | 
 | 219 | +    </div>  | 
 | 220 | +    {% endfor %}  | 
 | 221 | +</body>  | 
 | 222 | +</html>  | 
 | 223 | +```  | 
 | 224 | + | 
 | 225 | +请注意上面的模板页面,我们在第2行和`<img>`标签中使用了加载静态资源的模板指令,通过加载静态资源的指令我们可以显示讲师的头像。当然,我们还得创建放置静态资源的文件夹并在项目的配置文件中指明静态资源文件夹的所在以及静态资源的URL。  | 
 | 226 | + | 
 | 227 | +```Shell  | 
 | 228 | + | 
 | 229 | +(venv)$ mkdir static  | 
 | 230 | +(venv)$ cd static  | 
 | 231 | +(venv)$ mkdir css js images  | 
 | 232 | +```  | 
 | 233 | + | 
 | 234 | +首先在项目根目录下创建static文件,再进入static目录,创建css、js和images三个文件夹,分别用来放置层叠样式表、JavaScript文件和图片资源。  | 
 | 235 | + | 
 | 236 | +```Python  | 
 | 237 | + | 
 | 238 | +# 此处省略上面的代码  | 
 | 239 | + | 
 | 240 | +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]  | 
 | 241 | +STATIC_URL = '/static/'  | 
 | 242 | + | 
 | 243 | +# 此处省略下面的代码  | 
 | 244 | +```  | 
 | 245 | + | 
 | 246 | +接下来运行项目查看结果。  | 
 | 247 | + | 
 | 248 | +  | 
 | 249 | + | 
 | 250 | +### Ajax请求  | 
 | 251 | + | 
 | 252 | +接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)来实现“好评”和“差评”。  | 
 | 253 | + | 
 | 254 | +首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL,跟上面一样我们在URL中使用了占位符语法来绑定讲师的编号。  | 
 | 255 | + | 
 | 256 | +```Python  | 
 | 257 | + | 
 | 258 | +from django.contrib import admin  | 
 | 259 | +from django.urls import path  | 
 | 260 | + | 
 | 261 | +from demo import views  | 
 | 262 | + | 
 | 263 | +urlpatterns = [  | 
 | 264 | +    path('', views.login),  | 
 | 265 | +    path('subjects/', views.show_subjects),  | 
 | 266 | +    path('subjects/<int:no>/', views.show_teachers),  | 
 | 267 | +    path('good/<int:no>/', views.make_comment),  | 
 | 268 | +    path('bad/<int:no>/', views.make_comment),  | 
 | 269 | +    path('admin/', admin.site.urls),  | 
 | 270 | +]  | 
 | 271 | +```  | 
 | 272 | + | 
 | 273 | +设计视图函数`make_comment`来支持“好评”和“差评”功能,可以通过`json`模块的`dumps`函数实现将字典转成JSON字符串并作为`HttpResponse`返回给浏览器的内容。在创建`HttpResponse`对象时,可以通过`content_type`参数来指定响应的[MIME类型](http://www.w3school.com.cn/media/media_mimeref.asp)为JSON且使用UTF-8编码(避免JSON字符串中的中文出现乱码)。  | 
 | 274 | + | 
 | 275 | +```Python  | 
 | 276 | + | 
 | 277 | +def make_comment(request, no):  | 
 | 278 | +    ctx = {'code': 200}  | 
 | 279 | +    try:  | 
 | 280 | +        teacher = Teacher.objects.get(pk=no)  | 
 | 281 | +        if request.path.startswith('/good'):  | 
 | 282 | +            teacher.good_count += 1  | 
 | 283 | +            ctx['result'] = f'好评({teacher.gcount})'  | 
 | 284 | +        else:  | 
 | 285 | +            teacher.bad_count += 1  | 
 | 286 | +            ctx['result'] = f'差评({teacher.bcount})'  | 
 | 287 | +        teacher.save()  | 
 | 288 | +    except Teacher.DoesNotExist:  | 
 | 289 | +        ctx['code'] = 404  | 
 | 290 | +    return HttpResponse(json.dumps(ctx),  | 
 | 291 | +                        content_type='application/json; charset=utf-8')  | 
 | 292 | +```  | 
 | 293 | + | 
 | 294 | +修改模板页引入jQuery库来实现事件处理、Ajax请求和DOM操作。  | 
 | 295 | + | 
 | 296 | +```HTML  | 
 | 297 | + | 
 | 298 | +<!DOCTYPE html>  | 
 | 299 | +{% load staticfiles %}  | 
 | 300 | +<html lang="en">  | 
 | 301 | +<head>  | 
 | 302 | +    <meta charset="UTF-8">  | 
 | 303 | +    <title>讲师信息</title>  | 
 | 304 | +    <style>  | 
 | 305 | +        .container {  | 
 | 306 | +            width: 960px;  | 
 | 307 | +            margin: 0 auto;  | 
 | 308 | +        }  | 
 | 309 | +        .basic {  | 
 | 310 | +            width: 60%;  | 
 | 311 | +            float: left;  | 
 | 312 | +        }  | 
 | 313 | +        .potrait {  | 
 | 314 | +            width: 40%;  | 
 | 315 | +            float: left;  | 
 | 316 | +            text-align: right;  | 
 | 317 | +        }  | 
 | 318 | +        hr {  | 
 | 319 | +            clear: both;  | 
 | 320 | +        }  | 
 | 321 | +        .button {  | 
 | 322 | +            display: inline-block;  | 
 | 323 | +            width: 80px;  | 
 | 324 | +            height: 30px;  | 
 | 325 | +            background-color: red;  | 
 | 326 | +            color: white;  | 
 | 327 | +            font: 16px/30px Arial;  | 
 | 328 | +            text-decoration: none;  | 
 | 329 | +            text-align: center;  | 
 | 330 | +            margin-bottom: 10px;  | 
 | 331 | +        }  | 
 | 332 | +    </style>  | 
 | 333 | +</head>  | 
 | 334 | +<body>  | 
 | 335 | +    {% for x in teachers_list %}  | 
 | 336 | +    <div class="container">  | 
 | 337 | +        <div class="basic">  | 
 | 338 | +            <h1>{{ x.name }}老师</h1>  | 
 | 339 | +            <p><strong>讲师简介</strong></p>  | 
 | 340 | +            <p>{{ x.intro }}</p>  | 
 | 341 | +            <p><strong>教学理念</strong></p>  | 
 | 342 | +            <p>{{ x.motto }}</p>  | 
 | 343 | +            <a href="/good/{{ x.no }}" class="button">好评({{ x.gcount }})</a>  | 
 | 344 | +            <a href="/bad/{{ x.no }}" class="button">差评({{ x.bcount }})</a>  | 
 | 345 | +        </div>  | 
 | 346 | +        <div class="potrait">  | 
 | 347 | +            {% if x.photo %}  | 
 | 348 | +            <img src="{% static x.photo %}">  | 
 | 349 | +            {% endif %}  | 
 | 350 | +        </div>  | 
 | 351 | +        <hr>  | 
 | 352 | +    </div>  | 
 | 353 | +    {% endfor %}  | 
 | 354 | +    <script src="{% static 'js/jquery.min.js' %}"></script>  | 
 | 355 | +    <script>  | 
 | 356 | +       $(function() {  | 
 | 357 | +           $('.basic .button').on('click', function(evt) {  | 
 | 358 | +               evt.preventDefault();  | 
 | 359 | +               var $a = $(evt.target);  | 
 | 360 | +               var url = $a.attr('href');  | 
 | 361 | +               $.ajax({  | 
 | 362 | +                   'url': url,  | 
 | 363 | +                   'type': 'get',  | 
 | 364 | +                   'dataType': 'json',  | 
 | 365 | +                   'success': function(json) {  | 
 | 366 | +                       if (json.code == 200) {  | 
 | 367 | +                           $a.text(json.result);  | 
 | 368 | +                       }  | 
 | 369 | +                   }  | 
 | 370 | +               });  | 
 | 371 | +           });  | 
 | 372 | +       });  | 
 | 373 | +    </script>  | 
 | 374 | +</body>  | 
 | 375 | +</html>  | 
 | 376 | +```  | 
 | 377 | + | 
 | 378 | +### 小结  | 
 | 379 | + | 
 | 380 | +到此,这个小项目的核心功能已然完成,在下一个章节中我们会增加用户登录和注册的功能,稍后我们还会限定登录后的用户才能进行投票操作,而且每个用户只能投出3票。  | 
0 commit comments