~cytrogen/blog-public

ref: 88eebf3dfdd8ab819fa1a84e1976a8a75d5af2b6 blog-public/posts/407.html -rw-r--r-- 96.3 KiB
88eebf3dCytrogen Deploy 2026-02-19 08:34:27 3 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>IBM 全栈开发【9】:使用 SQL 和数据库开发 Django 应用程序 · Cytrogen 的个人博客</title><meta name="description" content="本文是 IBM 全栈开发课程的第九篇学习笔记,内容涵盖数据库基础与 Python Django 框架的实战应用。笔记首先对比了关系型(RDBMS)与四种主流 NoSQL 数据库的特点,随后重点讲解了 Django 框架及其强大的对象关系映射(ORM)功能,演示了如何用 Python 模型替代原生 SQL 进行增删查改(CRUD)操作。最后,通过一个完整的在线课程项目,手把手教你如何为 Django 应用添加考试功能,内容涉及模型设计、后台管理、视图逻辑编写和前端模板渲染,提供了一条从数据库理论到全栈开发的清晰学习路径。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/407.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
  font-family: 'Open Sans';
  src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  size-adjust: 107%;
  ascent-override: 97%;
  descent-override: 25%;
  line-gap-override: 0%;
}
</style><script>(function() {
  try {
    // 优先级:用户选择 > 系统偏好 > 默认浅色
    const saved = localStorage.getItem('theme');
    const theme = saved || 
      (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
    
    document.documentElement.setAttribute('data-theme', theme);
    document.documentElement.style.colorScheme = theme;
  } catch (error) {
    // 失败时使用默认主题,不阻塞渲染
    document.documentElement.setAttribute('data-theme', 'light');
  }
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const theme = document.documentElement.getAttribute('data-theme');
    const pageWrapper = document.getElementById('page-wrapper');
    if (pageWrapper && theme) {
      pageWrapper.setAttribute('data-theme', theme);
    }
  });
})();

</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/407.html">永久链接</a><div class="p-summary visually-hidden"><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/Python/">Python</a><a class="p-category" href="../tags/IBM/">IBM</a><a class="p-category" href="../tags/Django/">Django</a><a class="p-category" href="../tags/SQL/">SQL</a></div><h1 class="post-title p-name">IBM 全栈开发【9】:使用 SQL 和数据库开发 Django 应用程序</h1><div class="post-info"><time class="post-date dt-published" datetime="2024-05-29T05:06:00.000Z">5/29/2024</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:54.721Z"></time></div><div class="post-content e-content"><html><head></head><body><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p>
<span id="more"></span>
<h1 id="1-数据库入门"><a class="markdownIt-Anchor" href="#1-数据库入门"></a> 1. 数据库入门</h1>
<h2 id="11-sql"><a class="markdownIt-Anchor" href="#11-sql"></a> 1.1. SQL</h2>
<p>SQL 全称是 Structured Query Language,是为管理关系数据库中的数据而设计的,对于处理结构化数据非常有用。</p>
<p>数据是由文字、数字和图片组成的事实集合。而数据库是一个数据存储库,提供添加、修改和查询数据的功能。</p>
<p>关系数据库将表格数据存储为相关项目的集合,列中包含项目属性。非关系型数据库提供了一种灵活、可扩展的数据存储和检索方法。</p>
<p>关系数据库是优化存储、检索和处理大量数据的理想选择。</p>
<p>实体 - 关系模型(Entity-Relationship model)是设计关系数据库的工具。实体变为表、属性变为列。</p>
<p>基本 SQL 语句包括 <code>CREATE TABLE</code><code>INSERT</code><code>SELECT</code><code>UPDATE</code><code>DELETE</code></p>
<p>IBM DB2、SQL Server、MySQL、Oracle Database 和 PostgreSQL 都是有名的关系数据库。</p>
<p>部分有名的云关系数据库包括 RDS、Google Cloud SQL、IBM DB 2 on Cloud、Oracle Cloud 和 SQL Azure。</p>
<h4 id="111-用例"><a class="markdownIt-Anchor" href="#111-用例"></a> 1.1.1. 用例</h4>
<p>OLTP(Online Transaction Processing,在线事务处理)应用程序侧重于高速运行的事务型任务。关系数据库非常适合 OLTP 应用程序,因为它们可以容纳大量用户、支持插入更新或删除少量数据,还支持频繁查询和更新以及快速响应时间。</p>
<p>在数据仓库环境中,关系数据库可针对在线分析处理(或 OLAP)进行优化。在 OLAP 中,历史数据可用于商业智能分析。</p>
<p>物联网解决方案要求速度以及从边缘设备收集和处理数据的能力,这就需要一个轻量级的数据库解决方案。</p>
<h2 id="12-rdbms"><a class="markdownIt-Anchor" href="#12-rdbms"></a> 1.2. RDBMS</h2>
<p>DBRM 全称为 Database Management System,也就是数据库管理系统,是一组创建和维护数据库的程序。它允许存储、修改和查询数据库中的信息。</p>
<p>RDBMS 全称是 Rational Database Management System,也就是关系数据库管理系统。这是一种成熟的、文档齐全的技术,具有灵活性、减少冗余、易于备份和灾难恢复,并符合 ACID 标准。</p>
<blockquote>
<p>ACID 标准是指数据库管理系统在写入或更新资料的过程中,为保证交易是正确可靠的,所必须具备的四个特性:</p>
<ul>
<li>原子性,或称不可分割性(Atomicity)</li>
<li>一致性(Consistency)</li>
<li>隔离性(Isolation)</li>
<li>持久性(Durability)</li>
</ul>
</blockquote>
<h4 id="121-限制"><a class="markdownIt-Anchor" href="#121-限制"></a> 1.2.1. 限制</h4>
<p>RDBMS 不能很好地处理半结构化或非结构化数据,因此不适合对此类数据进行广泛分析。</p>
<p>要在两个 RDBMS 之间进行迁移,源表和目标表之间的模式和数据类型必须相同。</p>
<p>关系数据库对数据字段的长度有限制,这意味着如果尝试输入的信息超过字段所能容纳的长度,信息将不会被存储。</p>
<p>尽管数据有其局限性和演变性,但在大数据云计算、物联网设备和社交媒体时代,RDBMS 仍然是处理结构化数据的主要技术。</p>
<h2 id="13-nosql"><a class="markdownIt-Anchor" href="#13-nosql"></a> 1.3. NoSQL</h2>
<p>NoSQL 在过去的文章中有提到过,全称为 Not Only SQL(不只是 SQL),有时也指非 SQL,是一种非关系型数据库设计,为数据的存储和检索提供灵活的模式。</p>
<p>NoSQL 数据库有四种常见类型:</p>
<ul>
<li>键值存储</li>
<li>基于文档</li>
<li>基于列</li>
<li>基于图形</li>
</ul>
<h4 id="131-键值存储"><a class="markdownIt-Anchor" href="#131-键值存储"></a> 1.3.1. 键值存储</h4>
<p>键值数据库中的数据以键值对集合的形式存储。键代表数据的一个属性,是唯一的标识符。键和值可以是简单的整数或字符串,也可以是复杂的 JSON 文档。</p>
<p>键值存储非常适用于存储用户会话数据和用户偏好、实时推荐和定向广告以及内存数据缓存。</p>
<p>但是,如果想查询特定数据值的数据、需要数据值之间的关系,或者需要多个唯一键,键值存储可能不是最合适的选择。</p>
<p>Redis、Memcached 和 DynamoDB 就是这类数据库中的一些著名例子。</p>
<h4 id="132-基于文档"><a class="markdownIt-Anchor" href="#132-基于文档"></a> 1.3.2. 基于文档</h4>
<p>基于文档的文档数据库将每条记录及其相关数据存储在单个文档中。它们可以对文档集合进行灵活的索引、强大的临时查询和分析。文档数据库适用于电子商务平台、医疗记录、存储、客户关系管理平台和分析平台。</p>
<p>但是如果希望运行复杂的搜索查询和多操作事务,那么基于文档的数据库可能就不是最佳选择。</p>
<p>MongoDB、DocumentDB、CouchDB 和 Cloudant 是一些流行的基于文档的数据库。</p>
<h4 id="133-基于列"><a class="markdownIt-Anchor" href="#133-基于列"></a> 1.3.3. 基于列</h4>
<p>基于列的模型将数据存储在以数据列而不是行分组的单元格中。</p>
<p>列的逻辑分组,即通常一起访问的列,称为列族。例如,客户的姓名和个人资料信息很可能会一起被访问,但他们的购买历史记录却不会一起被访问,因此客户姓名和个人资料信息数据可以归入一个列族。</p>
<p>由于列数据库将与列相对应的所有单元格存储为连续的磁盘条目,因此访问和搜索数据的速度非常快。对于需要大量写入请求、存储时间序列数据、天气数据和物联网数据的系统来说,列式数据库是个不错的选择。</p>
<p>但是,如果您需要使用复杂的查询或经常改变查询模式,这可能不是最佳选择。</p>
<p>最流行的列数据库是 Cassandra 和 HBase。</p>
<h4 id="134-基于图形"><a class="markdownIt-Anchor" href="#134-基于图形"></a> 1.3.4. 基于图形</h4>
<p>基于图形的数据库使用图形模型来表示和存储数据。</p>
<p>它们特别适用于可视化、分析和查找不同数据之间的联系。圆圈是节点,包含数据,箭头代表关系。图形数据库是处理关联数据(即包含大量相互关联关系的数据)的最佳选择。</p>
<p>图形数据库非常适合社交网络、实时产品推荐、网络图、欺诈检测和访问管理。</p>
<p>但如果您想处理大量事务,它可能不是最佳选择,因为图形数据库没有针对大量分析查询进行优化。</p>
<p>Ne04J 和 CosmosDB 是比较流行的图数据库。</p>
<h2 id="14-测验"><a class="markdownIt-Anchor" href="#14-测验"></a> 1.4. 测验</h2>
<p>您正在为一家网上商店建立一个网站。您需要存储产品信息、客户详细信息和订单历史记录。以下哪项最恰当地描述了数据库在这种情况下的作用?</p>
<blockquote>
<p>在这种情况下,数据库是相关表格的集合。它提供了一种结构化的方式,将产品信息、客户详情和订单历史记录存储在不同的表格中,从而实现高效管理和数据检索。</p>
</blockquote>
<p>以下哪项是 RDBMS 的最大优势?</p>
<blockquote>
<p>符合 ACID 标准是 RDBMS 的重要优势之一。ACID 代表原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),可确保数据事务得到可靠处理。</p>
</blockquote>
<p>在图书馆实体 - 关系数据模型中,图书是 _____ 的一个示例,而图书的书名、版本和出版日期则是 _______ 的示例。</p>
<blockquote>
<p>在实体 - 关系模型中,实体是一个名词(人、地点或事物)。属性是实体的属性或特征。</p>
</blockquote>
<p>关于数据库,以下哪些说法是正确的?</p>
<blockquote>
<p>数据库是逻辑上连贯的数据集合,具有一定的内在含义。</p>
</blockquote>
<p>关系型数据库的主要优势是什么?</p>
<blockquote>
<p>关系模型提供了逻辑和物理数据的独立性,允许在不影响底层数据库结构的情况下访问和修改数据。</p>
</blockquote>
<h1 id="2-orm弥合现实世界与关系模型之间的差距"><a class="markdownIt-Anchor" href="#2-orm弥合现实世界与关系模型之间的差距"></a> 2. ORM:弥合现实世界与关系模型之间的差距</h1>
<h2 id="21-django"><a class="markdownIt-Anchor" href="#21-django"></a> 2.1. Django</h2>
<p>Django 是一个可访问的高级开源 Python 网页框架。</p>
<p>Django 采用模型视图控制器 MVC 模式,可帮助开发人员快速高效地构建网络应用程序,从而实现快速开发和代码重用。用户几乎可以用 Django 构建任何网络应用程序,包括内容管理系统(CMS)、社交媒体平台、商业应用程序和新网站。</p>
<p>Django 提供了一系列开箱即用的特性和功能:Django 提供了一个对象关系映射或称 ORM 层,允许使用 Python 类定义数据模型。这使得使用数据库和执行操作变得更加容易。例如查询、插入、更新和删除记录。</p>
<p>Django 有一个内置模板引擎,使开发人员能够将应用程序的业务逻辑与表现逻辑分开。</p>
<p>Django 还提供根据应用程序中的模型自动生成的管理界面。它为管理网站内容提供了友好的用户界面,并可根据具体要求进行定制。Django 提供强大的安全功能,包括防止常见的网络漏洞,如跨站脚本(xss)、跨站请求伪造(CSRF)和 SQL 注入。</p>
<p>它还提供密码散列和用户会话管理机制。Django 内置身份验证和授权机制,允许管理用户账户,包括注册登录和密码管理。</p>
<p>它还提供细粒度的授权控制,以定义用户权限和访问限制。Django 可借助模块(也称为 Django 应用程序或包)进行扩展。这些模块是可重复使用的组件,可以将其集成到 Django 项目中,以添加特定的功能或特性。例如,Django 使用 <code>gettext</code> 模块支持语言本地化,Django 提供第三方软件包,如用于验证码集成的 Django Simple Captcha,它有助于防止表单中的自动僵尸提交,Django 的内置表单模块包括强大的表单。验证逻辑可确保表单数据符合标准。</p>
<p>Django 倡导的架构是每个网络服务器实例独立运行,称为无共享或无状态。该架构中的每个网络服务器实例都能自主处理请求和响应,而无需依赖共享资源或在请求之间维护任何服务器端状态。这使扩展变得容易,无状态使得开发人员可以添加更多应用程序实例,并在不丢失数据的情况下跨模型传输用户体验。</p>
<p>Django 支持各种测试,包括单元测试、集成测试和功能测试。Django 的测试框架包括一个测试运行器,允许开发人员快速运行测试并提供详细的测试报告。它提供各种测试工具、断言和固定装置,以方便编写和执行测试。</p>
<p>由于 Django 是基于 Python 构建的,因此与平台无关,这意味着它可以在许多平台上运行。这种平台独立性得益于 Python 的可移植性和在不同操作系统上的广泛可用性。这使得开发人员可以选择最适合其需求和基础设施的托管平台。Django 应用程序几乎可以在所有云提供商上运行。Django 的平台无关性使得可以根据具体要求和偏好将应用程序部署到各种云平台上。</p>
<p>一些著名的网页应用程序就是使用 Django 构建的。Instagram 是一个分享照片和视频的流行社交媒体平台,最初就是使用 Django 构建的。著名的音乐流媒体平台 Spotify 也使用了 Django 及其基础设施。著名的新闻出版物《华盛顿邮报》也采用了 Django 作为其内容管理系统或 CNS。</p>
<h2 id="12-ooad"><a class="markdownIt-Anchor" href="#12-ooad"></a> 1.2. OOAD</h2>
<p>面向对象的分析和设计(简称 OOAD)是一种分析和设计软件系统的方法,当系统将使用面向对象的编程语言来开发时,就需要使用这种方法。</p>
<p>在讨论 OOAD 之前,我们先来了解一下 Java 或 C++ 或 Python 等语言中的面向对象编程。OOAD 的核心是对象。对象包含数据,也有规定对象可以采取的行动的行为。</p>
<p>例如,我可以创建一个代表病人的对象。假设病人的名字是 Nia Patel、Nia 需要取消预约。不过,在创建 Nia 之前,我们必须先创建一个病人对象的通用版本。对象的通用版本称为类。接下来,让我们先讨论类,然后再详细讨论 Nia。</p>
<p>类是对象的蓝图或模板,从类中创建特定对象(也称为实例)。 考虑到 Nia 将是病人类的一个实例。类包含对象、通用属性、属性和方法,但只有在创建对象时,也就是代码中所谓的实例化时,这些通用属性才会被设置为特定值。病人类可能有一个名为 <code>LastName</code> 的变量,它是一个属性,但并不指定 <code>LastName</code> 是什么。<code>Lastname</code> 是一个占位符,直到创建了对象并分配了名称。一旦对象被实例化,就可以调用其方法使对象执行某些操作,如预约或取消预约。OOAD 可用于将系统分解为相互交互的对象。这样,多个开发人员就可以同时处理应用程序的不同方面。</p>
<h4 id="121-orm"><a class="markdownIt-Anchor" href="#121-orm"></a> 1.2.1. ORM</h4>
<p>对象关系映射(ORM)工具被广泛应用于现代软件开发中,为面向对象的编程语言和关系数据库之间架起了一座桥梁。ORM 工具提供了一种使用编程语言对象和概念与数据库交互的方便高效的方法,使开发人员无需编写复杂重复的 SQL 查询。</p>
<p>编程语言都有自己的 ORM 工具,可以简化数据库操作和开发流程。</p>
<ul>
<li>
<p>Django 是一个 Python 网络框架,内置 ORM,提供与数据库交互的高级应用编程接口(API)。Django 提供模型定义、查询构建、数据库迁移和自动查询优化等功能。</p>
</li>
<li>
<p>SQLAlchemy 是一个流行的 Python 综合 ORM 库,提供了一个灵活而富有表现力的 API,用于与数据库交互。SQLAlchemy 提供高级和低级 ORM 方法,允许开发人员选择所需的抽象级别。</p>
</li>
<li>
<p>web2py 是一个用 Python 编写的开源全栈网络框架,旨在通过提供包括网络服务器、数据库抽象层和基于网络的开发环境在内的一体化解决方案来简化网络应用程序开发。</p>
</li>
<li>
<p>Hibernate 广泛用于 Java 应用程序,因为它提供了功能强大、特性丰富的 API,可将 Java 对象映射到关系数据库。Hibernate 支持各种数据库系统,并提供懒加载、缓存和事务管理等高级功能。</p>
</li>
<li>
<p>EclipseLink 是另一种流行的 Java 工具。它是一个开源框架,为 Java 与关系数据库的映射提供广泛支持。EclipseLink 支持 Java Persistence API (JPA)、缓存、高级查询功能以及与各种应用服务器的集成等功能。</p>
</li>
<li>
<p>Apache OpenJPA 是 JPA 规范的开源实现。它便于将 Java 类映射到关系数据库表,并提供透明的 Java 对象持久性。</p>
</li>
<li>
<p>Entity Framework 是微软用于 .NET 应用程序的 ORM 框架。它提供了一个易于使用的 API,用于将 .NET 对象映射到关系数据库。Entity Framework 支持不同的数据库提供商,提供查询功能,并包含代码优先和数据库优先的模型创建方法等功能。</p>
</li>
<li>
<p>Dapper <a target="_blank" rel="noopener" href="http://xn--0iv.NET">是.NET</a> 的微型操作系统,注重性能和简单性。它为查询数据库提供了轻量级、高效的 API。Dapper 适用于需要直接控制 SQL 查询和执行的场景。</p>
</li>
<li>
<p>NHibernate 是 .NET 平台上广泛使用的 ORM 工具。它的灵感来自 Java 的 Hibernate ORM 工具。NHibernate 在 .NET 面向对象编程和关系数据库之间架起了一座桥梁,允许开发人员使用 .NET 对象与数据库交互。</p>
</li>
<li>
<p>ActiveRecord 是 Ruby on Rails(一种流行的 Ruby 网络框架)中的默认 ORM。它提供了一种「约定重于配置」的方法,可轻松将 Ruby 对象映射到数据库表。ActiveRecord 提供了模型关联、查询生成和数据库迁移等功能。</p>
</li>
<li>
<p>Sequel 是一款灵活、功能丰富的 Ruby ORM 工具,它强调简单性和以 SQL 为中心的方法。Sequel 支持各种数据库系统,提供高级查询功能,并提供插件以实现更多功能。</p>
</li>
<li>
<p>DataMapper 注重简洁性、灵活性和易用性。它旨在为在 Ruby 应用程序中使用数据库提供简洁直观的 API。DataMapper 的功能包括灵活的映射、数据抽象、查询和存储库模式。</p>
</li>
<li>
<p>Propel 是一款适用于 PHP 的高性能 ORM 工具,专注于代码生成和简化。它采用代码优先的方法,由 PHP 类和对象定义数据库模式。Propel 可生成高效的 SQL 查询,并提供懒加载、急迫加载和缓存等功能。</p>
</li>
<li>
<p>CakePHP 包含一个内置的 ORM 组件,为数据库工作提供了一个强大而直观的 API,具有数据库抽象、查询构建和关联管理等功能。</p>
</li>
<li>
<p>Eloquent 是流行的 PHP 框架 Laravel 框架提供的默认 ORM。它为定义数据库模型和关系提供了简单明了、表现力丰富的语法。Eloquent 简化了数据库操作,包括查询、数据检索和数据库迁移,并支持不同的数据库引擎。</p>
</li>
</ul>
<p>软件开发人员通常使用数据库作为应用程序的主要数据存储库,因此需要将 SQL 集成到应用程序代码中。SQL 语句必须在应用程序代码中组装,并使用数据库 API 在数据库系统中执行 —— 检索到的数据库行将作为 <code>cursor</code>(一种用于遍历数据库中行的特殊控制数据结构)返回给应用程序代码。</p>
<p>在 Python 中运行 SQL 的例子:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">connection = sqlite3.connect(<span class="string">'course.db'</span>)</span><br><span class="line">cursor = connection.cursor()</span><br><span class="line"></span><br><span class="line">insert_statement = <span class="string">'INSERT INTO data_learner (first_name, last_name, dob, occupation) VALUES ("John", "Doe", "1962-01-01", "Developer");'</span></span><br><span class="line">cursor.execute(insert_statement)</span><br><span class="line"></span><br><span class="line">cursor.execute(<span class="string">'SELECT * FROM data_learner'</span>)</span><br><span class="line">learner = cursor.fetchone()</span><br></pre></td></tr></tbody></table></figure>
<p>与使用表格、行和列来模拟实体的 SQL 不同,面向对象语言使用类和对象来模拟实体。在 OOP 中,<code>Course</code> 实体将被定义为一个类,该类有两个基本属性:<code>name</code><code>description</code>;一个引用属性,即 <code>learner</code> 列表。</p>
<p>数据操作方法也需要与类属性一起定义。这里我们定义了一个简单的方法:<code>get Learners</code><code>Learner</code> 实体也将被定义为一个具有四个属性的类:名、姓、出生日期和职业。我们还将定义一个简单的打印个人资料方法。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">connection = sqlite3.connect(<span class="string">'course.db'</span>)</span><br><span class="line">cursor = connection.cursor()</span><br><span class="line"></span><br><span class="line">cursor.execute(<span class="string">'SELECT * FROM data_learner'</span>)</span><br><span class="line">learner_row = cursor.fetchone()</span><br><span class="line"></span><br><span class="line">learner = Learner()</span><br><span class="line">learner.first_name = learner_row[<span class="string">'first_name'</span>]</span><br><span class="line">learner.last_name = learner_row[<span class="string">'last_name'</span>]</span><br><span class="line">learner.dob = learner_row[<span class="string">'dob'</span>]</span><br><span class="line">learner.occupation = learner_row[<span class="string">'occupation'</span>]</span><br><span class="line"></span><br><span class="line">learner.print_profile()</span><br></pre></td></tr></tbody></table></figure>
<p>我们已经看到,OOP 和 SQL 的数据建模方式是不同的。OOP 使用类、对象和属性对实体建模,而 SQL 则使用表、行和列对实体建模。最后,OOP 使用方法对数据进行 CRUD,而 SQL 则使用数据操作语言(如 SQL 语句插入、删除和更新)对数据进行 CRUD。</p>
<p>既然我们通常使用 OOP 构建现代应用程序,那么我们是否也可以使用 OOP 而不是 SQL 访问数据库呢?这样,我们就可以坚持使用一种编程范式进行开发。人们发明对象关系映射的主要原因是为了弥合 OOP 与 SQL 之间的差距,使使用 OOP 语言访问数据库成为可能。ORM 库或工具可以将关系数据库中存储的数据映射和传输为行到对象或对象到行。</p>
<p>假设我们有一个由开发人员使用 OOP 创建的 <code>Learner</code> 对象模型。ORM 可以帮助将 <code>Learner</code> 对象转移到 <code>Learner</code> 表中的 <code>Learner</code> 行中,并将其读回 <code>Learner</code> 对象。这就减少了开发人员的工作量,因为他们只需关注对象操作。下面是 ORM 如何以一行代码的形式传输三个表连接的 SQL 查询。</p>
<p>SQL 代码例子:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">connection = sqlite3.connect(<span class="string">'sample.db'</span>)</span><br><span class="line">cursor = connection.cursor()</span><br><span class="line">select_statement = <span class="string">'SELECT learner.name FROM learner INNER JOIN enrollment ON (learner_id = enrollment.learner_id) INNER JOIN course ON (course_id = enrollment.course_id) WHERE course.name = "Introduction to Python"'</span></span><br><span class="line"></span><br><span class="line">cursor.execute(select_statement)</span><br><span class="line">learners = cursor.fetchall()</span><br></pre></td></tr></tbody></table></figure>
<p>ORM 代码例子:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Course.objects.get(name=<span class="string">'Introduction to Python'</span>).learners.<span class="built_in">all</span>()</span><br></pre></td></tr></tbody></table></figure>
<p>在开发 OOP 应用程序时,只需定义类和创建对象,无需编写 SQL 即可使用数据库。此外还可以使用一个 ORM 接口来管理多个数据库系统,而不必担心 SQL 语法的差异。所有这些优势都将加快应用程序的交付速度。</p>
<p>但是 SQL 和 OOP 仍然是两种不同的语言,具有不同的建模概念,而且 ORM 可能无法将对象映射到数据库表中。此外,由于 ORM 将数据访问逻辑与应用程序代码结合在一起,因此任何数据库变更都需要同时更改应用程序逻辑和数据访问逻辑。由于 ORM 隐藏了实现细节,因此调试可能很困难。ORM 也有可能会降低应用程序的性能:ORM 增加了一个额外的翻译层,但不能保证翻译后的 SQL 语句得到优化。</p>
<h4 id="122-django-orm"><a class="markdownIt-Anchor" href="#122-django-orm"></a> 1.2.2. Django ORM</h4>
<p>在 Django ORM 中,每个 Django 模型都映射到一个数据库表。当您创建一个类对象时,它代表一个表行。每个字段代表一个表列。一旦定义了模型类,模式和表格就会自动生成。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">User</span>(models.Model):</span><br><span class="line">    first_name = models.CharField(max_length=<span class="number">30</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>例如,我们可以定义一个 <code>User</code> 类,它是 Django 模型的子类。然后,Django 将通过生成 <code>table create</code> 语句并根据类字段创建列,在数据库中创建相应的 <code>User</code> 表。在我们的例子中,将根据 <code>first_name</code> 字段创建 <code>first_name</code> 列。模型中的每个字段都应定义为一个 <code>Field</code> 类。</p>
<p>上面的 Python 代码等同于:</p>
<figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> data_user</span><br><span class="line">(</span><br><span class="line">    "id" serial <span class="keyword">NOT NULL</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    "first_name" <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">NOT NULL</span></span><br><span class="line">)</span><br></pre></td></tr></tbody></table></figure>
<p>模型中的每个字段都应定义为 <code>Field</code> 类。Django 会将每个字段映射为列类型。在这里,<code>first_name</code> 被定义为字符字段,将被转换为 <code>varchar</code>,而 <code>dob</code> 是日期字段,将被转换为 <code>date</code>。对于每一列,我们通过在 Django <code>Field</code> 类中指定参数来定义其元数据,如类型和约束条件。例如,对于 <code>first_name</code> 字段,我们使用 <code>max_length</code> 参数指定 <code>varchar</code> 的长度。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">User</span>(models.Model):</span><br><span class="line">    first_name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    last_name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    dob = models.DateField()</span><br></pre></td></tr></tbody></table></figure>
<p>等同于:</p>
<figure class="highlight sql"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> data_user</span><br><span class="line">(</span><br><span class="line">    "id" serial <span class="keyword">NOT NULL</span> <span class="keyword">PRIMARY KEY</span>,</span><br><span class="line">    "first_name" <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    "last_name" <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">NOT NULL</span>,</span><br><span class="line">    "dob" <span class="type">date</span> <span class="keyword">NOT NULL</span></span><br><span class="line">)</span><br></pre></td></tr></tbody></table></figure>
<p>接下来,我们为实体之间的关系建模。Django ORM 支持常见的关系,如一对一、多对一和多对多。</p>
<p>我们从一对一关系开始。假设我们有一个 <code>Instructor</code><code>User</code> ER 图。<code>User</code> 类拥有一些常用字段,如姓名或出生日期,而 <code>Instructor</code> 类拥有一些特殊字段,如是否全职或讲师拥有的学员总数。一个教员只能在一个 <code>User</code> 类中存储基本信息,一个用户只能有一个角色,如 <code>Instructor</code><code>Learner</code></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Class: Instructor</span><br><span class="line">is_full_time: boolean</span><br><span class="line">total_learners: int</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Class: User</span><br><span class="line">first_name: string</span><br><span class="line">last_name: float</span><br><span class="line">dob: date</span><br></pre></td></tr></tbody></table></figure>
<p>将以上 ER 图转换为 Django 模型:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Instructor</span>(models.Model):</span><br><span class="line">    is_full_time = models.BooleanField()</span><br><span class="line">    total_learners = models.IntegerField()</span><br><span class="line">    user = models.OneToOneField(User)</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">User</span>(models.Model):</span><br><span class="line">    first_name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    last_name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    dob = models.CharField(max_length=<span class="number">30</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>我们将使用 <code>Project</code><code>Course</code> ER 图来说明「多对一」关系。<code>Project</code> 类表示一个课程项目,包含项目名称和成绩等字段。<code>Course</code> 类代表在线课程实体。它有原子字段,如名称和描述。更重要的是,它有一个名为 <code>projects</code> 的集合字段,代表一组课程项目对象。因此,一门课程会有许多课程项目,而一个课程项目只属于一门课程。这是一种典型的多对一关系。要在 Django 中创建模型,我们需要添加一个额外的 <code>Course</code> 字段作为外键。请注意,这不是一对一字段,因为多个项目可能拥有相同的课程字段。</p>
<p>关于「多对多」关系,让我们来看看 <code>Course</code><code>Learner</code> ER 图。</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Class: Course</span><br><span class="line">name: string</span><br><span class="line">description: string</span><br><span class="line">learners: set</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Class: Learner</span><br><span class="line">occupation: enum</span><br><span class="line">social_link: URL</span><br></pre></td></tr></tbody></table></figure>
<p>为了模拟这种关系,我们在其中一个模型中添加了一个 <code>ManyToManyField</code>。通常,<code>ManyToManyField</code> 应放在最常编辑的模型中。例如,<code>Course</code> 可能会随着学习者的添加或删除而每小时被编辑一次,但每个学习者可能每周或每月才注册一次新课程。有时,您可能需要有关模型之间关系的额外信息。</p>
<p>例如,您可能需要学习者和课程之间的注册信息,如注册日期。Django 允许您指定用于管理多对多关系的模型。在我们的例子中,中间模型是 <code>Enrollment</code> 表。它使用 <code>through</code> 参数与 <code>ManyToManyField</code> 关联。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Course</span>(models.Model):</span><br><span class="line">    name = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    description = models.CharField(max_length=<span class="number">30</span>)</span><br><span class="line">    learners = models.ManyToManyField(Instructor, through=<span class="string">'Enrollment'</span>)</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Enrollment</span>(models.Model):</span><br><span class="line">    course = models.ForeignKey(...)</span><br><span class="line">    learner = models.ForeignKey(...)</span><br><span class="line">    date_enrolled = models.DateField()</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Learner</span>(models.Model):</span><br><span class="line">    occupation = models.CharField(max_length=<span class="number">20</span>)</span><br><span class="line">    social_link = models.URLField()</span><br></pre></td></tr></tbody></table></figure>
<p>Django 中的模型继承就像 Python 中的继承一样。你需要确定父模型是否只是通过子模型才能看到的通用信息的持有者,或者父模型是否应该有自己的表。</p>
<h4 id="123-crud"><a class="markdownIt-Anchor" href="#123-crud"></a> 1.2.3. CRUD</h4>
<blockquote>
<p>CRUD 就是喜闻乐见的 <strong>增删查改</strong></p>
</blockquote>
<p>Django 模型提供了全面的 CRUD API,无需编写 SQL 查询即可操作对象。让我们回顾一下我们将在示例中使用的在线课程模型。基本 <code>User</code> 模型包含有关 <code>Instructor</code><code>Learner</code> 模型的通用信息。<code>Instructor</code><code>User</code> 模型继承而来,拥有 <code>is_full_time</code> 或学习者总数等字段。<code>Learner</code> 也是继承的。它有 <code>occupation</code><code>social_link</code> 等字段。<code>Course</code> 模型与 <code>Instructor</code> 模型和 <code>Learner</code> 模型都有多对多关系。<code>Course</code> 模型还与 <code>Project</code> 模型有多对一关系。在 Django 模型中,创建一个对象并调用模型的保存方法将其作为记录插入数据库。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">course_cloud_app = Course(name=<span class="string">'Cloud Application Development with Database'</span>, description=<span class="string">'Develop and deploy application on cloud'</span>)</span><br><span class="line">course_cloud_app.save()</span><br></pre></td></tr></tbody></table></figure>
<p>如果创建的对象包含对另一个模型的引用,如外键或多对多字段,则使用相关模型引用来创建关系。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">project_orm = Project(name=<span class="string">'Object-relational mapping project'</span>, grade=<span class="number">0.2</span>, course=course_cloud_app)</span><br><span class="line">project_orm.save()</span><br></pre></td></tr></tbody></table></figure>
<p>我们创建了一个 <code>Project</code> 对象,并将其 <code>Course</code> 外键指向我们刚刚创建的 <code>course_cloud_app</code>。将对象及其关系插入数据库后,我们就可以进行查询了。首先,让我们看看如何读取模型的所有对象。这相当于在 SQL 中使用 <code>SELECT *</code> 从表中获取表的所有行。一般来说,要使用 Django Model API 读取对象,需要在模型类上使用 <code>Manager</code> 构建一个 <code>QuerySet</code></p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">courses = Course.objects.<span class="built_in">all</span>()</span><br></pre></td></tr></tbody></table></figure>
<p><code>filter</code> 方法可以有许多查找参数,如大于、小于、包含或为空。这就像 SQL <code>WHERE</code> 子句中的条件用法。查询参数包含字段名称和查询表达式,对于等价检查,查询表达式可以为空,对于其他检查,查询表达式可以用下划线分隔。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">part_time_instructors = Instructor.objects.<span class="built_in">filter</span>(is_full_time=<span class="literal">False</span>)</span><br></pre></td></tr></tbody></table></figure>
<p><code>exclude</code> 方法会返回一个与给定查找参数不匹配的新 <code>QuerySet</code>。由于 <code>exclude</code><code>filter</code> 方法都会返回一个 <code>QuerySet</code>,我们可以进一步追加 <code>exclude</code><code>filter</code> 方法,形成一个过滤链。这就像在 SQL 的 <code>Where</code> 子句中使用 <code>and</code> 添加多个条件一样。我们可以同时使用 <code>exclude</code><code>filter</code> 方法查找 <code>Instructor</code> 子集。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">filtered_instructors = Instructor.objects.exclude(full_time=<span class="literal">False</span>).<span class="built_in">filter</span>(total_learners__gt=<span class="number">30000</span>).<span class="built_in">filter</span>(first_name__startswith=<span class="string">'J'</span>)</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">filtered_instructors = Instructor.objects.<span class="built_in">filter</span>(full_time=<span class="literal">True</span>, total_learners__gt=<span class="number">30000</span>, first_name__startswith=<span class="string">'J'</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>如果只有一个对象符合您的查询,您可以使用 <code>Get</code> 方法返回该对象。例如,如果我们知道只有一个教员的名字是 John,我们就可以使用带有 <code>first_name</code> 查找参数的 <code>Get</code> 方法来获取教员。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">instructor_john = Instructor.objects.get(first_name=<span class="string">'John'</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>更新对象的原始字段。对于我们的学习者对象,我们可以将 <code>dob</code> 字段更改为 1985 年 3 月 16 日,然后使用 <code>save</code> 方法将更改更新到数据库。Django 模型将创建并执行相应的 SQL 更新语句。我们还可以更新相关字段,如外键字段或多对多字段。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">learner_john.dob = date(<span class="number">1985</span>,<span class="number">3</span>,<span class="number">16</span>)</span><br><span class="line">learner_john.save()</span><br></pre></td></tr></tbody></table></figure>
<p>我们还可以更新相关字段,如外键字段或多对多字段。例如,我们可以更新课程外键字段,使其指向不同的课程。我们还可以 <code>Add</code> 方法将另一位学员添加到课程中。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">project_orm.course = course_python</span><br><span class="line">project_orm.save()</span><br></pre></td></tr></tbody></table></figure>
<p>要删除数据库中的记录,需要在模型对象或 <code>QuerySet</code> 上调用 <code>Delete</code> 方法。Django ORM 支持不同的 <code>on delete</code> 选项。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">project_orm.delete()</span><br><span class="line"></span><br><span class="line">Course.objects.<span class="built_in">filter</span>(name__contains=<span class="string">'Python'</span>).delete()</span><br></pre></td></tr></tbody></table></figure>
<h1 id="3-全栈-django-开发"><a class="markdownIt-Anchor" href="#3-全栈-django-开发"></a> 3. 全栈 Django 开发</h1>
<h2 id="31-mvc"><a class="markdownIt-Anchor" href="#31-mvc"></a> 3.1. MVC</h2>
<p>MVC 设计模式将应用程序逻辑分为三个部分:</p>
<ol>
<li>模型访问和操作数据。</li>
<li>视图以各种形式显示数据。</li>
<li>控制器在模型和视图之间进行协调。</li>
</ol>
<p>Django 的 MVT 模式与 MVC 相似,只是没有控制器,Django 服务器自身会执行控制器的功能。</p>
<blockquote>
<p>MVT:Model-View-Template。</p>
</blockquote>
<h2 id="32-django"><a class="markdownIt-Anchor" href="#32-django"></a> 3.2. Django</h2>
<p>在 Django 中,视图(View)是一个 Python 函数。Django 视图接收网络请求,如 HTTP <code>GET</code><code>POST</code><code>DELETE</code><code>UPDATE</code>,并返回网络响应。网络响应可以是字符串、JSON / XML 文件、HTML 页面,也可以是表示客户端或服务器端错误的错误状态。</p>
<p>Django 使用包含静态 HTML 元素和特殊 Python 代码的模板来生成动态网页。可以在 Django 中创建模板来指定数据的展示方式。Django 模板将静态 HTML 元素与描述如何插入动态部分的 Django 模板标记和变量相结合。这些元素共同作用生成一个 HTML 页面,在用户的网络浏览器中呈现。</p>
<p>创建 Django 项目时,Django 会创建一些核心文件:</p>
<ul>
<li><code>manage.py</code> 是用于与 Django 项目交互的命令行界面。</li>
<li><code>settings.py</code> 包含 Django 项目的设置和配置。</li>
<li><code>urls.py</code> 包含 Django 应用程序的 URL 和路由定义。</li>
</ul>
<p>可以通过创建一个管理员用户开始构建 Django 管理站点。然后,就可以以超级用户身份登录,并将模型注册到管理员站点,以便对其进行管理。</p>
<h1 id="4-整合和部署-django-应用程序"><a class="markdownIt-Anchor" href="#4-整合和部署-django-应用程序"></a> 4. 整合和部署 Django 应用程序</h1>
<p>在 Django 中,类视图必须与 URL 模式进行映射,几乎就像为基于函数的视图配置 URL 一样。唯一的区别是你需要指定以下哪一项?</p>
<blockquote>
<p><code>as_view</code> 函数。这是 Django 类视图的一个重要方法,它负责创建一个适合用作视图的实例,并调用其 <code>dispatch</code> 方法。这是在 URL 配置中使用类视图时需要指定的方法。</p>
</blockquote>
<p>在通过登录 HTML 模板从用户处接收用户名和密码时,哪种 HTTP 方法将用户名或密码发送到在线课程登录视图以验证用户?</p>
<blockquote>
<p><code>POST</code> 请求。</p>
</blockquote>
<p>Django 提供了一个默认的静态文件应用,它将所有静态文件收集到一个目录中。当你将你的应用部署到生产 Web 服务器时,你可以将所有静态文件移动到这个目录。要做到这一点,有几个步骤。在最后一步,你需要调用什么?</p>
<blockquote>
<p>运行 <code>collectstatic</code> 命令。这是 Django 的一个管理命令,用于收集所有应用的静态文件到一个指定的位置,以便在生产环境中使用。</p>
</blockquote>
<p>ASGI 是 Django 应用支持的 Web 服务器接口。WSGI 和 ASGI 之间的主要区别是什么?</p>
<blockquote>
<p>ASGI 支持异步代码,而 WSGI 适合同步 Web 应用。这是 WSGI(Web 服务器网关接口)和 ASGI(异步服务器网关接口)之间的主要区别。ASGI 是 WSGI 的扩展,它增加了对异步编程的支持,这使得在处理大量并发连接时,可以更有效地利用系统资源。</p>
</blockquote>
<p>为了解决 Django 视图的可扩展性和可重用性问题,创建了哪种类型的视图?</p>
<blockquote>
<p>基于类的视图。Django 引入了基于类的视图(Class-Based Views),以提供更好的可重用性和可扩展性。基于类的视图允许你通过继承和混入(mixins)来重用和定制视图的行为。</p>
</blockquote>
<p>授权是在认证过程的什么时候发生的?</p>
<blockquote>
<p>认证之后。在大多数系统中,用户必须首先通过认证(例如,通过提供有效的用户名和密码),然后系统才会进行授权,以确定用户对资源的访问权限。</p>
</blockquote>
<p>如果你想在不手动下载和导入的情况下使用 Bootstrap CSS 样式类,你应该怎么做?</p>
<blockquote>
<p>在你的 HTML 模板的 <code>head</code> 元素中添加一个链接到最新的 Bootstrap 版本。这样,你就可以在你的网页中直接使用 Bootstrap 的 CSS 样式类,而无需手动下载和导入 Bootstrap。</p>
</blockquote>
<p>Django 应用可以有特定于应用的静态文件和外部静态文件。你在哪里定义外部静态文件的目录,以便你可以找到它们?</p>
<blockquote>
<p><code>settings.py</code> 文件中的 <code>STATICFILES_DIRS</code> 列表。这是 Django 设置中的一个变量,用于指定包含你的静态文件的额外目录的列表。Django 将在这些目录中查找静态文件。</p>
</blockquote>
<p>WSGI 是 Python 用于在 Web 服务器和应用程序之间通信的主要标准。为了使 Django 应用与 WSGI 一起工作,启动项目命令创建了一个默认声明可调用应用程序的文件。这个文件叫什么名字?</p>
<blockquote>
<p><code>wsgi.py</code>。这是 Django 项目中的一个文件,它包含一个可调用的 WSGI 应用。当你使用 Django 的 <code>startproject</code> 命令创建一个新项目时,这个文件会被自动创建。</p>
</blockquote>
<h1 id="5-利用新功能增强在线课程应用程序"><a class="markdownIt-Anchor" href="#5-利用新功能增强在线课程应用程序"></a> 5. 利用新功能增强在线课程应用程序</h1>
<h2 id="51-最终项目概述和方案"><a class="markdownIt-Anchor" href="#51-最终项目概述和方案"></a> 5.1. 最终项目概述和方案</h2>
<p>您将有机会掌握 Django 技能并将其应用到项目中。您将收到一个在线课程应用程序的模板代码。然后,您将利用在本课程中学到的技能,计划并实施对应用程序的改进;这些改进包括:</p>
<ol>
<li>创建问题、选择和提交模型</li>
<li>使用管理站点创建带有考试相关模型的新课程对象</li>
<li>更新课程详细信息模板,以显示问题和选项</li>
<li>创建新的考试结果模板,以显示提交结果</li>
<li>创建新的考试结果提交视图</li>
<li>创建一个新视图来显示和评估考试结果</li>
</ol>
<p><a target="_blank" rel="noopener" href="https://github.com/ibm-developer-skills-network/tfjzl-final-cloud-app-with-database"><img src="https://gh-card.dev/repos/ibm-developer-skills-network/tfjzl-final-cloud-app-with-database.svg" alt="ibm-developer-skills-network/tfjzl-final-cloud-app-with-database - GitHub"></a></p>
<p>让我们建立一个虚拟环境,其中包含我们需要的所有软件包:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pip install --upgrade distro-info</span><br><span class="line">pip3 install --upgrade pip==23.2.1</span><br><span class="line">pip install virtualenv</span><br><span class="line">virtualenv djangoenv</span><br><span class="line"><span class="built_in">source</span> djangoenv/bin/activate</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install -U -r requirements.txt</span><br></pre></td></tr></tbody></table></figure>
<p>创建初始迁移并生成数据库模式:</p>
<blockquote>
<p>迁移是 Django 将对模型所做的更改(添加字段、删除模型等)传播到数据库模式的方式。迁移主要是自动进行的,但你需要了解何时进行迁移、何时运行迁移以及可能遇到的常见问题。你将使用几条命令与迁移和 Django 处理数据库模式进行交互:</p>
<p><code>migrate</code>:负责应用和取消应用迁移。<br>
<code>makemigrations</code>:负责根据对模型所做的更改创建新的迁移。<br>
<code>sqlmigrate</code>:用于显示迁移的 SQL 语句。<br>
<code>showmigrations</code>:用于列出项目的迁移及其状态。</p>
</blockquote>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python manage.py makemigrations</span><br><span class="line">python manage.py migrate</span><br></pre></td></tr></tbody></table></figure>
<p>这次成功运行服务器:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py runserver</span><br></pre></td></tr></tbody></table></figure>
<blockquote>
<p>访问 <code>8000/onlinecourse</code></p>
</blockquote>
<h4 id="511-建立新模型"><a class="markdownIt-Anchor" href="#511-建立新模型"></a> 5.1.1. 建立新模型</h4>
<p>你需要在 <code>onlinecourse/models.py</code> 中创建几个新模型。</p>
<p><code>Question</code> 模型将保存具有以下特征的考试问题:</p>
<ul>
<li>用于保存课程试题</li>
<li>与课程有多对一关系</li>
<li>具有问题文本</li>
<li>每道题都有一个分数</li>
</ul>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Question</span>(models.Model):</span><br><span class="line">    course = models.ForeignKey(Course, on_delete=models.CASCADE)</span><br><span class="line">    content = models.CharField(max_length=<span class="number">200</span>)</span><br><span class="line">    grade = models.IntegerField(default=<span class="number">50</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__str__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"Question: "</span> + <span class="variable language_">self</span>.content</span><br></pre></td></tr></tbody></table></figure>
<p>此外,你还可以在 <code>Question</code> 模型中添加以下函数来计算分数:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">is_get_score</span>(<span class="params">self, selected_ids</span>):</span><br><span class="line">    all_answers = <span class="variable language_">self</span>.choice_set.<span class="built_in">filter</span>(is_correct=<span class="literal">True</span>).count()</span><br><span class="line">    selected_correct = <span class="variable language_">self</span>.choice_set.<span class="built_in">filter</span>(is_correct=<span class="literal">True</span>, id__in=selected_ids).count()</span><br><span class="line">    <span class="keyword">if</span> all_answers == selected_correct:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">False</span></span><br></pre></td></tr></tbody></table></figure>
<p><code>Choice</code> 模型可保存问题的所有选项:</p>
<ul>
<li><code>Question</code> 模型的多对一关系</li>
<li>选项文本</li>
<li>表示该选项是否正确</li>
</ul>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Choice</span>(models.Model):</span><br><span class="line">    question = models.ForeignKey(Question, on_delete=models.CASCADE)</span><br><span class="line">    content = models.CharField(max_length=<span class="number">200</span>)</span><br><span class="line">    is_correct = models.BooleanField(default=<span class="literal">False</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>我们为您提供了已注释的 <code>Submission</code> 模型,该模型具有以下特点:</p>
<ul>
<li><code>Exam Submissions</code> 之间的多对一关系,例如,多个考试提交可能属于一个课程注册。</li>
<li>与选项或问题的多对多关系。为简单起见,您可以将提交与 <code>Choice</code> 模型相关联。</li>
</ul>
<p>您需要取消对 <code>Submission</code> 模型的注释,并使用它来关联选定的选择。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Submission</span>(models.Model):</span><br><span class="line">    enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE)</span><br><span class="line">    choices = models.ManyToManyField(Choice)</span><br></pre></td></tr></tbody></table></figure>
<p>迁移:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python manage.py makemigrations onlinecourse</span><br><span class="line">python manage.py migrate</span><br></pre></td></tr></tbody></table></figure>
<h4 id="512-注册模式变更"><a class="markdownIt-Anchor" href="#512-注册模式变更"></a> 5.1.2. 注册模式变更</h4>
<p>现在,您将修改 <code>onlinecourse/admin.py</code>,以便使用您创建的新功能。</p>
<p>首先您需要导入 <code>Question</code><code>Choice</code><code>Submission</code></p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> Course, Lesson, Instructor, Learner, Question, Choice, Submission</span><br></pre></td></tr></tbody></table></figure>
<p>创建 <code>QuestionInline</code><code>ChoiceInline</code> 类,这样就可以在管理网站的一个页面上一起编辑它们。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ChoiceInline</span>(admin.StackedInline):</span><br><span class="line">    model = Choice</span><br><span class="line">    extra = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">QuestionInline</span>(admin.StackedInline):</span><br><span class="line">    model = Question</span><br><span class="line">    extra = <span class="number">2</span></span><br></pre></td></tr></tbody></table></figure>
<p>创建 <code>QuestionAdmin</code> 类:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">QuestionAdmin</span>(admin.ModelAdmin):</span><br><span class="line">    inlines = [ChoiceInline]</span><br><span class="line">    list_display = [<span class="string">'content'</span>]</span><br></pre></td></tr></tbody></table></figure>
<p>注册新模型后,您可以使用管理网站创建一个包含课程、问题和问题选项的新课程。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">admin.site.register(Question, QuestionAdmin)</span><br><span class="line">admin.site.register(Choice)</span><br><span class="line">admin.site.register(Submission)</span><br></pre></td></tr></tbody></table></figure>
<p>接着让我们创建一个管理员用户,其详细信息如下:</p>
<ol>
<li>用户名:<code>admin</code></li>
<li>邮箱:<em>留空</em></li>
<li>密码:可以使用 <code>p@ssword123</code></li>
</ol>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python manage.py createsuperuser</span><br></pre></td></tr></tbody></table></figure>
<p>运行 Django 开发服务器,检查是否可以使用管理站点添加 <code>Question</code><code>Choice</code> 对象。</p>
<h4 id="513-更新课程详细信息模板"><a class="markdownIt-Anchor" href="#513-更新课程详细信息模板"></a> 5.1.3. 更新课程详细信息模板</h4>
<p>现在,您将更新课程详情模板,创建一个包含问题和选项列表的考试部分。一次考试包含多个问题,每个问题都应该有一个以上的正确答案(多选)。</p>
<p>更改将在 <code>templates/onlinecourse/course_details_bootstrap.html</code> 中进行。</p>
<p>如果用户已通过身份验证,则显示带有问题和选项列表的课程考试:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{% if user.is_authenticated %}</span><br><span class="line"><span class="tag">&lt;/<span class="name">br</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Remaining code will go here --&gt;</span></span><br><span class="line">{% endif %}</span><br></pre></td></tr></tbody></table></figure>
<p>添加一个开始考试的按钮:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn btn-primary btn-block"</span> <span class="attr">data-toggle</span>=<span class="string">"collapse"</span> <span class="attr">data-target</span>=<span class="string">"#exam"</span>&gt;</span>Start Exam<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>添加一个可折叠 <code>div</code></p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"exam"</span> <span class="attr">class</span>=<span class="string">"collapse"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>在表单中添加 <code>Question</code> 逻辑:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"exam"</span> <span class="attr">class</span>=<span class="string">"collapse"</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">form</span> <span class="attr">id</span>=<span class="string">"questionform"</span> <span class="attr">action</span>=<span class="string">"{% url 'onlinecourse:submit' course.id %}"</span> <span class="attr">method</span>=<span class="string">"POST"</span>&gt;</span></span><br><span class="line">       {% for question in course.question_set.all %}</span><br><span class="line">           <span class="comment">&lt;!-- Question UI components will go here --&gt;</span></span><br><span class="line">       {% endfor %}</span><br><span class="line">   <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>添加 <code>Quesion</code> UI:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"card mt-1"</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"card-header"</span>&gt;</span><span class="tag">&lt;<span class="name">h5</span>&gt;</span>{{ question.content }}<span class="tag">&lt;/<span class="name">h5</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">   {% csrf_token %}</span><br><span class="line">   <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-group"</span>&gt;</span></span><br><span class="line">       <span class="comment">&lt;!-- Choices components go here --&gt;</span></span><br><span class="line">   <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>添加 <code>Choices</code> 组件:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">{% for choice in question.choice_set.all %}</span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-check"</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">label</span> <span class="attr">class</span>=<span class="string">"form-check-label"</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">name</span>=<span class="string">"choice_{{choice.id}}"</span></span></span><br><span class="line"><span class="tag">              <span class="attr">class</span>=<span class="string">"form-check-input"</span> <span class="attr">id</span>=<span class="string">"{{choice.id}}"</span></span></span><br><span class="line"><span class="tag">              <span class="attr">value</span>=<span class="string">"{{choice.id}}"</span>&gt;</span>{{ choice.content }}</span><br><span class="line">   <span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">{% endfor %}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="514-提交评估"><a class="markdownIt-Anchor" href="#514-提交评估"></a> 5.1.4. 提交评估</h4>
<p>由于您已创建了多个新模型,因此现在需要在 <code>views.py</code> 的顶部导入它们。</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> .models <span class="keyword">import</span> Course, Enrollment, Question, Choice, Submission</span><br></pre></td></tr></tbody></table></figure>
<p>现在,您将为表单提交创建一个基于函数的视图。创建提交视图 <code>def submit(request,course_id)</code> 来为课程注册创建考试提交记录。您可以根据以下逻辑来实现它:</p>
<ul>
<li>获取当前用户和课程对象,然后获取相关的注册对象</li>
<li>参照注册信息创建一个新的提交对象</li>
<li>从 HTTP 请求对象中收集所选选项</li>
<li>将每个选定的选择对象添加到提交对象中</li>
<li>使用提交 ID 重定向到 <code>show_exam_result</code> 视图,以显示考试结果</li>
<li>配置 <code>urls.py</code> 以路由新的提交视图</li>
</ul>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">submit</span>(<span class="params">request, course_id</span>):</span><br><span class="line">    course = get_object_or_404(Course, pk=course_id)</span><br><span class="line">    user = request.user</span><br><span class="line">    enrollment = Enrollment.objects.get(user=user, course=course)</span><br><span class="line">    submission = Submission.objects.create(enrollment=enrollment)</span><br><span class="line">    choices = extract_answers(request)</span><br><span class="line">    submission.choices.<span class="built_in">set</span>(choices)</span><br><span class="line">    submission_id = submission.<span class="built_in">id</span></span><br><span class="line">    <span class="keyword">return</span> HttpResponseRedirect(reverse(viewname=<span class="string">'onlinecourse:exam_result'</span>, args=(course_id, submission_id,)))</span><br></pre></td></tr></tbody></table></figure>
<p><code>urls.py</code> 中路由提交视图按钮:</p>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">path(<span class="string">'&lt;int:course_id&gt;/submit/'</span>, views.submit, name=<span class="string">"submit"</span>),</span><br></pre></td></tr></tbody></table></figure>
<h4 id="515-评估视图"><a class="markdownIt-Anchor" href="#515-评估视图"></a> 5.1.5. 评估视图</h4>
<p>创建考试结果视图 <code>def show_exam_result(request,course_id,submission_id)</code> 来检查学员是否通过考试以及他们的答题结果。您可以根据以下逻辑实现视图:</p>
<ul>
<li>根据视图参数中的 id 获取课程对象和提交对象</li>
<li>从提交记录中获取所选选项的 id</li>
<li>检查每个所选选项是否为正确答案</li>
<li>将课程中所有问题的分数相加,计算总分</li>
<li>将课程、选项和成绩添加到上下文中,以便渲染 HTML 页面</li>
<li>配置 <code>urls.py</code> 以路由新的 <code>show_exam_result</code> 视图</li>
</ul>
<figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">show_exam_result</span>(<span class="params">request, course_id, submission_id</span>):</span><br><span class="line">    context = {}</span><br><span class="line">    course = get_object_or_404(Course, pk=course_id)</span><br><span class="line">    submission = Submission.objects.get(<span class="built_in">id</span>=submission_id)</span><br><span class="line">    choices = submission.choices.<span class="built_in">all</span>()</span><br><span class="line">    total_score = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> choice <span class="keyword">in</span> choices:</span><br><span class="line">        <span class="keyword">if</span> choice.is_correct:</span><br><span class="line">            total_score += choice.question.grade</span><br><span class="line">    context[<span class="string">'course'</span>] = course</span><br><span class="line">    context[<span class="string">'grade'</span>] = total_score</span><br><span class="line">    context[<span class="string">'choices'</span>] = choices</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> render(request, <span class="string">'onlinecourse/exam_result_bootstrap.html'</span>, context)</span><br></pre></td></tr></tbody></table></figure>
<h4 id="516-完成考试结果模板以显示考试提交结果"><a class="markdownIt-Anchor" href="#516-完成考试结果模板以显示考试提交结果"></a> 5.1.6. 完成考试结果模板以显示考试提交结果</h4>
<p>完成用于提交结果的 <code>exam_result_bootstrap.html</code> HTML 模板,该模板应显示学员是否通过考试,并提供总分、每道题的成绩等详细信息。</p>
<p>使用 Bootstrap 对更新后的模板进行样式调整,以符合设计团队的用户界面设计。</p>
<p>通过考试的学员应看到最终分数和祝贺信息:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">b</span>&gt;</span>Congratulations, {{ user.first_name }}!<span class="tag">&lt;/<span class="name">b</span>&gt;</span> You have passed the exam and completed the course with score {{ grade }}/100</span><br></pre></td></tr></tbody></table></figure>
<p>对于考试不及格的学员,应向其显示最终分数和错误选项。应允许学员重考并重新提交:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">b</span>&gt;</span>Failed<span class="tag">&lt;/<span class="name">b</span>&gt;</span> Sorry, {{ user.first_name }}! You have failed the exam with score {{ grade }}/100</span><br></pre></td></tr></tbody></table></figure>
<p>您还必须显示考试成绩,以便学员了解自己的成绩:</p>
<figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">{% for question in course.question_set.all %}</span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"card mt-1"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"card-header"</span>&gt;</span><span class="tag">&lt;<span class="name">h5</span>&gt;</span>{{ question.content }}<span class="tag">&lt;/<span class="name">h5</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-group"</span>&gt;</span></span><br><span class="line">        {% for choice in question.choice_set.all %}</span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-check"</span>&gt;</span></span><br><span class="line">            {% if choice.is_correct and choice in choices %}</span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"text-success"</span>&gt;</span>Correct answer: {{ choice.content }}<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            {% else %}{% if choice.is_correct and not choice in choices %}</span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"text-warning"</span>&gt;</span>Not selected: {{ choice.content }}<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            {% else %}{% if not choice.is_correct and choice in choices %}</span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"text-danger"</span>&gt;</span>Wrong answer: {{ choice.content }}<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            {% else %}</span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span>&gt;</span>{{ choice.content }}<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            {% endif %}{% endif %}{% endif %}</span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        {% endfor %}</span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">{% endfor %}</span><br></pre></td></tr></tbody></table></figure>
</body></html></div></article></div></main><footer><div class="paginator"><a class="prev" href="52000.html">上一篇</a><a class="next" href="5d57.html">下一篇</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/407.html" data-full-url="https://cytrogen.icu/posts/407.html" data-mode="static">
              <h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
              <div class="webmention-list"></div>
              <span>暂无 Webmentions</span>
            </div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
  const codeBlocks = document.querySelectorAll('figure.highlight');
  
  codeBlocks.forEach(block => {
    let caption = block.querySelector('figcaption');
    if (!caption) {
      caption = document.createElement('figcaption');
      block.insertBefore(caption, block.firstChild);
    }

    const info = document.createElement('div');
    info.className = 'info';
    
    const filename = caption.querySelector('span');
    if (filename) {
      filename.className = 'filename';
      info.appendChild(filename);
    }
    
    const lang = block.className.split(' ')[1];
    if (lang) {
      const langSpan = document.createElement('span');
      langSpan.className = 'lang-name';
      langSpan.textContent = lang;
      info.appendChild(langSpan);
    }

    const sourceLink = caption.querySelector('a');
    if (sourceLink) {
      sourceLink.className = 'source-link';
      info.appendChild(sourceLink);
    }

    const actions = document.createElement('div');
    actions.className = 'actions';

    const codeHeight = block.scrollHeight;
    const threshold = 300;

    if (codeHeight > threshold) {
      block.classList.add('folded');
      
      const toggleBtn = document.createElement('button');
      toggleBtn.textContent = '展开';
      toggleBtn.addEventListener('click', () => {
        block.classList.toggle('folded');
        toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
      });
      actions.appendChild(toggleBtn);
    }

    const copyBtn = document.createElement('button');
    copyBtn.textContent = '复制';
    copyBtn.addEventListener('click', async () => {
      const codeLines = block.querySelectorAll('.code .line');
      const code = Array.from(codeLines)
        .map(line => line.textContent)
        .join('\n')
        .replace(/\n\n/g, '\n');
      
      try {
        await navigator.clipboard.writeText(code);
        copyBtn.textContent = '已复制';
        copyBtn.classList.add('copied');
        
        setTimeout(() => {
          copyBtn.textContent = '复制';
          copyBtn.classList.remove('copied');
        }, 3000);
      } catch (err) {
        console.error('复制失败:', err);
        copyBtn.textContent = '复制失败';
        
        setTimeout(() => {
          copyBtn.textContent = '复制';
        }, 3000);
      }
    });
    actions.appendChild(copyBtn);

    caption.innerHTML = '';
    caption.appendChild(info);
    caption.appendChild(actions);

    const markedLines = block.getAttribute('data-marked-lines');
    if (markedLines) {
      const lines = markedLines.split(',');
      lines.forEach(range => {
        if (range.includes('-')) {
          const [start, end] = range.split('-').map(Number);
          for (let i = start; i <= end; i++) {
            const line = block.querySelector(`.line-${i}`);
            if (line) line.classList.add('marked');
          }
        } else {
          const line = block.querySelector(`.line-${range}`);
          if (line) line.classList.add('marked');
        }
      });
    }
  });
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const themeToggle = document.querySelector('.theme-toggle');
    
    if (!themeToggle) return;
    
    const getCurrentTheme = () => {
      return document.documentElement.getAttribute('data-theme') || 'light';
    };
    
    const updateUI = (theme) => {
      const isDark = theme === 'dark';
      themeToggle.setAttribute('aria-pressed', isDark.toString());
    };
    
    const setTheme = (theme) => {
      document.documentElement.setAttribute('data-theme', theme);
      document.documentElement.style.colorScheme = theme;
      
      const pageWrapper = document.getElementById('page-wrapper');
      if (pageWrapper) {
        pageWrapper.setAttribute('data-theme', theme);
      }
      
      // Find and remove the temporary anti-flicker style tag if it exists.
      // This ensures the main stylesheet takes full control after the initial load.
      const antiFlickerStyle = document.getElementById('anti-flicker-style');
      if (antiFlickerStyle) {
        antiFlickerStyle.remove();
      }
      
      localStorage.setItem('theme', theme);
      updateUI(theme);
    };
    
    const toggleTheme = () => {
      const current = getCurrentTheme();
      const newTheme = current === 'light' ? 'dark' : 'light';
      setTheme(newTheme);
    };
    
    updateUI(getCurrentTheme());
    
    themeToggle.addEventListener('click', toggleTheme);
    
    if (window.matchMedia) {
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      mediaQuery.addEventListener('change', function(e) {
        if (!localStorage.getItem('theme')) {
          const theme = e.matches ? 'dark' : 'light';
          setTheme(theme);
        }
      });
    }
  });
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const backToTopBtn = document.querySelector('.back-to-top');
    
    if (!backToTopBtn) return;
    
    const toggleButtonVisibility = () => {
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      const shouldShow = scrollTop > 200;
      
      if (shouldShow) {
        backToTopBtn.classList.add('is-visible');
      } else {
        backToTopBtn.classList.remove('is-visible');
      }
    };
    
    let ticking = false;
    const handleScroll = () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          toggleButtonVisibility();
          ticking = false;
        });
        ticking = true;
      }
    };
    
    const scrollToTop = (event) => {
      event.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    };
    
    window.addEventListener('scroll', handleScroll);
    backToTopBtn.addEventListener('click', scrollToTop);
    
    toggleButtonVisibility();
  });
})();</script></body></html>