[
    {
        "type": "text",
        "text": "Django ",
        "text_level": 1,
        "bbox": [
            149,
            112,
            294,
            143
        ],
        "page_idx": 0
    },
    {
        "type": "text",
        "text": "本身基于 MVC 模型（架构），即 Model（模型） $^ +$ View（视图） $^ +$ Controller（控制器）设计模式，MVC 模式使后续对程序的修改和扩展简化，并且使程序某一部分的重复利用成为可能。但在 Django 中更常被称为 MTV（Model-Template-View）。",
        "bbox": [
            144,
            161,
            848,
            212
        ],
        "page_idx": 0
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "模型（Model）：负责应用程序的数据和业务逻辑。通过将数据和逻辑从用户界面分离出来，使得模型可以独立于用户界面进行测试和修改。",
            "视图（View）：负责显示用户界面，但通常没有直接访问应用程序的数据。这使得可以更容易地更改应用程序的外观而不影响数据处理。",
            "控制器（Controller）：处理用户输入、更新模型和调整视图。通过将用户输入和应用程序逻辑分离，可以更容易地更改用户界面的交互方式而不影响数据和业务逻辑。"
        ],
        "bbox": [
            159,
            217,
            845,
            347
        ],
        "page_idx": 0
    },
    {
        "type": "text",
        "text": "Django 提供了全栈开发所需的工具，包括数据库ORM、模板引擎、路由系统、用户认证等，大幅减少重复代码。",
        "bbox": [
            144,
            366,
            815,
            379
        ],
        "page_idx": 0
    },
    {
        "type": "text",
        "text": "Django 的哲学:",
        "text_level": 1,
        "bbox": [
            146,
            397,
            245,
            410
        ],
        "page_idx": 0
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "DRY（Don't Repeat Yourself）: 避免重复代码，提倡复用（如模板继承、模型继承）。",
            "约定优于配置: 默认提供合理配置（如自动生成Admin 界面），减少决策成本。",
            "快速开发：Django 提供了大量内置功能，如认证、管理后台、表单处理等，让开发者专注于业务逻辑，而非底层实现。",
            "自动化管理后台：只需简单的模型定义，即可生成强大的后台管理界面，支持增删改查。",
            "ORM 数据库映射：Django 内置 ORM (Object-Relational Mapping)，可以让开发者使用 Python 类与数据库交互，无需编写 SQL。",
            "强大的 URL 路由：使用正则表达式灵活定义 URL，轻松实现页面路由。",
            "模板引擎：内置强大的模板系统，支持逻辑判断、循环处理，方便渲染 HTML 页面。",
            "国际化支持：Django 支持多语言国际化，非常适合全球化应用。",
            "高安全性：内置多种安全保护措施，如防止 SQL 注入、XSS 攻击、CSRF 攻击等。",
            " 丰富的社区与扩展：大量开源的第三方库，如 Django REST framework、Django CMS 等，快速扩展功能。"
        ],
        "bbox": [
            174,
            428,
            850,
            757
        ],
        "page_idx": 0
    },
    {
        "type": "image",
        "img_path": "images/f4487cc28d0ed6be5aa3b6d12a641d265ce457c6192d9a6a0c73c17c74dfe081.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            149,
            771,
            426,
            883
        ],
        "page_idx": 0
    },
    {
        "type": "text",
        "text": "内置功能",
        "text_level": 1,
        "bbox": [
            147,
            130,
            273,
            154
        ],
        "page_idx": 1
    },
    {
        "type": "table",
        "img_path": "images/5d60b0537c37007c990ae2b61e846c3457cfbc1fa80f09239c115f6a3a8da393.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>功能</td><td>说明</td></tr><tr><td>Admin后台</td><td>自动生成管理界面，无需手动编写CRUD逻辑。</td></tr><tr><td>ORM</td><td>用Python类操作数据库，无需写SQL。</td></tr><tr><td>表单处理</td><td>内置表单验证，防止CSRF攻击。</td></tr><tr><td>用户认证</td><td>提供登录、注册、权限管理（django.contrib.auth）。</td></tr><tr><td>路由系统</td><td>URL映射灵活，支持正则表达式。</td></tr><tr><td>缓存机制</td><td>支持Memcached、Redis等后端。</td></tr></table>",
        "bbox": [
            147,
            162,
            998,
            475
        ],
        "page_idx": 1
    },
    {
        "type": "image",
        "img_path": "images/9e8b7b91d694be7a68319af4130f4b3e88f070e1faac0f9b9d52e5a8c0d292a6.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            147,
            506,
            715,
            709
        ],
        "page_idx": 1
    },
    {
        "type": "text",
        "text": "MVC (Model-View-Controller) ",
        "text_level": 1,
        "bbox": [
            147,
            730,
            586,
            755
        ],
        "page_idx": 1
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "Model (模型)：处理与数据库的交互，定义数据的结构和业务逻辑。",
            "View (视图)：负责数据展示，生成用户看到的HTML 页面。",
            "Controller (控制器)：接收用户请求，调用Model 处理数据，并将结果传递给View 渲染页面。"
        ],
        "bbox": [
            174,
            766,
            761,
            841
        ],
        "page_idx": 1
    },
    {
        "type": "text",
        "text": "流程：",
        "text_level": 1,
        "bbox": [
            147,
            859,
            184,
            871
        ],
        "page_idx": 1
    },
    {
        "type": "text",
        "text": "1. 用户发送请求到 Controller。",
        "bbox": [
            176,
            890,
            371,
            902
        ],
        "page_idx": 1
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "2. Controller 处理逻辑，调用 Model 获取数据。",
            "3. Controller 将数据传递给 View。",
            "4. View 渲染并返回 HTML 页面给用户。"
        ],
        "bbox": [
            176,
            93,
            470,
            168
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "MVT (Model-Template-View) —— Django 的实现方式",
        "text_level": 1,
        "bbox": [
            144,
            191,
            826,
            253
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "Django 中采用了MVT 设计模式，类似于MVC，但有一些区别：",
        "bbox": [
            144,
            263,
            527,
            278
        ],
        "page_idx": 2
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "Model (模型)：与数据库交互，处理数据的创建、读取、更新、删除。",
            "Template (模板)：负责页面渲染，生成最终的HTML 内容。",
            "View (视图)：Django 的View 更偏向于控制器的角色，接收请求并决定使用哪个模板和数据。"
        ],
        "bbox": [
            174,
            296,
            757,
            370
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "流程：",
        "bbox": [
            146,
            388,
            184,
            401
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "用户访问 URL，请求被 Django 的 urls.py 映射到相应的 View。",
        "text_level": 1,
        "bbox": [
            144,
            426,
            789,
            445
        ],
        "page_idx": 2
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "url.py下  \nurlpatterns $=$ [url(r'^search-form/\\$,search.search_form)，#映射到search.py中search_form视图url(r'^search/\\$,search.search)， #映射到search.py中search视图url(r'^search-post/\\$,search2.search_post),#映射到search2.py中search_post视图]",
        "bbox": [
            206,
            460,
            747,
            627
        ],
        "page_idx": 2
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            ". 当访问 /search-form/ 时，Django 调用 search.search_form 视图函数",
            "当提交表单到 /search/ 时，调用 search.search 视图函数"
        ],
        "bbox": [
            176,
            645,
            645,
            688
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "View 处理业务逻辑，调用Model 获取数据。",
        "text_level": 1,
        "bbox": [
            144,
            714,
            589,
            733
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "(1) 渲染搜索表单（但是这里无Model交互）",
        "bbox": [
            206,
            747,
            470,
            760
        ],
        "page_idx": 2
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "# search.py\ndef search_form(request):\n    return render(request, 'search_form.html')  # 直接渲染模板",
        "guess_lang": "python",
        "bbox": [
            206,
            778,
            576,
            854
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "流程：",
        "bbox": [
            176,
            871,
            243,
            883
        ],
        "page_idx": 2
    },
    {
        "type": "text",
        "text": "View 只负责返回模板，未涉及Model 数据操作",
        "bbox": [
            205,
            890,
            489,
            902
        ],
        "page_idx": 2
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [
            "(2) 处理搜索请求（带参数校验）"
        ],
        "code_body": "def search(request): request_encoding $=$ 'utf-8' if $\\mathrm{q}^{\\prime}$ in request.GET and request.GET['q']: #业务逻辑判断 message $=$ '你搜索的内容为:' $^+$ request.GET['q'] else: message $=$ '你提交了空表单' return HttpResponse(message)#直接返回响应（未用模板） ",
        "bbox": [
            206,
            155,
            591,
            357
        ],
        "page_idx": 3
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [
            "（3）# 接收POST请求数据"
        ],
        "code_body": "def search_post(request):   \nctx $= \\{\\}$ if request.POST:   \nctx['rlt'] $=$ request.POST['q']   \nreturn render(request, \"post.html\", ctx) ",
        "bbox": [
            206,
            406,
            455,
            545
        ],
        "page_idx": 3
    },
    {
        "type": "text",
        "text": "View 将数据传递给 Template。",
        "text_level": 1,
        "bbox": [
            146,
            599,
            470,
            620
        ],
        "page_idx": 3
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return render(request, 'search_form.html') ",
        "guess_lang": "lua",
        "bbox": [
            206,
            663,
            460,
            676
        ],
        "page_idx": 3
    },
    {
        "type": "text",
        "text": "在这个例子中，search_form 只是用来展示HTML 表单页面，没有动态数据传入。",
        "bbox": [
            206,
            694,
            687,
            708
        ],
        "page_idx": 3
    },
    {
        "type": "text",
        "text": "虽然这里没有传递变量，但你可以通过 render 的第三个参数传数据给模板：",
        "bbox": [
            206,
            725,
            653,
            738
        ],
        "page_idx": 3
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return render(request, 'search_form.html', {'key': value}) ",
        "guess_lang": "lua",
        "bbox": [
            206,
            756,
            542,
            769
        ],
        "page_idx": 3
    },
    {
        "type": "text",
        "text": "第二个没有传输数据因为根本没有template",
        "bbox": [
            208,
            789,
            468,
            803
        ],
        "page_idx": 3
    },
    {
        "type": "text",
        "text": "第三个例子",
        "bbox": [
            208,
            822,
            278,
            835
        ],
        "page_idx": 3
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return render(request, \"post.html\", ctx) ",
        "guess_lang": "lua",
        "bbox": [
            206,
            854,
            438,
            866
        ],
        "page_idx": 3
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1. Template 渲染 HTML，最终返回给用户。",
            "模板文件: search_form.html"
        ],
        "bbox": [
            176,
            95,
            450,
            137
        ],
        "page_idx": 4
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "</form> ",
        "guess_lang": "txt",
        "bbox": [
            176,
            250,
            226,
            261
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "Tip：HTML <form> action 属性",
        "text_level": 1,
        "bbox": [
            146,
            291,
            500,
            311
        ],
        "page_idx": 4
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "<form action=\"/search/\" method=\"get\"> ",
        "guess_lang": "twig",
        "bbox": [
            176,
            351,
            413,
            363
        ],
        "page_idx": 4
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "<input type=\"text\" name=\"q\"> ",
        "guess_lang": "twig",
        "bbox": [
            184,
            384,
            363,
            395
        ],
        "page_idx": 4
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "<input type=\"submit\" value=\"搜索\"> ",
        "guess_lang": "txt",
        "bbox": [
            184,
            414,
            400,
            426
        ],
        "page_idx": 4
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "</form> ",
        "guess_lang": "txt",
        "bbox": [
            176,
            445,
            226,
            456
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "(1) <form> 标签 ",
        "bbox": [
            176,
            476,
            278,
            489
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "action=\"/search/\" ",
        "bbox": [
            176,
            508,
            322,
            518
        ],
        "page_idx": 4
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 表单提交的目标URL地址",
            "o /search/ 表示提交到当前网站的搜索端点（例如 https://example.com/search/）",
            "o 尾部斜杠/ 表示这是一个目录路径（符合RESTful风格）"
        ],
        "bbox": [
            236,
            539,
            744,
            612
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": ". method=\"get\"",
        "bbox": [
            176,
            632,
            300,
            643
        ],
        "page_idx": 4
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 使用 HTTP GET 方法提交数据",
            "o 表单数据会附加在URL后（如 /search/?q=关键词）",
            "o 适合不修改服务器状态的查询操作（如搜索）"
        ],
        "bbox": [
            236,
            663,
            576,
            736
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "2. 实际工作流程",
        "text_level": 1,
        "bbox": [
            146,
            755,
            247,
            766
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "1. 用户交互",
        "bbox": [
            176,
            787,
            265,
            799
        ],
        "page_idx": 4
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 在文本框中输入内容（如\"无人机\"）",
            "o 点击\"搜索\"按钮 "
        ],
        "bbox": [
            236,
            816,
            473,
            860
        ],
        "page_idx": 4
    },
    {
        "type": "text",
        "text": "2. 浏览器行为",
        "bbox": [
            176,
            879,
            278,
            891
        ],
        "page_idx": 4
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 自动组装 URL：/search/?q=无人机",
            "o 发起GET请求（HTTP头部示例）："
        ],
        "bbox": [
            236,
            93,
            485,
            137
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "GET /search/?q=无人机 HTTP/1.1",
        "bbox": [
            146,
            156,
            354,
            168
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "Host: example.com ",
        "bbox": [
            146,
            187,
            265,
            199
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "3. 服务器处理 ",
        "text_level": 1,
        "bbox": [
            176,
            218,
            278,
            230
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "o Django路由将请求交给对应的View处理：",
        "bbox": [
            236,
            249,
            517,
            261
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "# urls.py ",
        "bbox": [
            146,
            281,
            203,
            293
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "path('search/', views.search) ",
        "bbox": [
            146,
            311,
            319,
            323
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "# views.py ",
        "bbox": [
            146,
            343,
            215,
            354
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "def search(request): ",
        "bbox": [
            146,
            373,
            270,
            385
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "query $=$ request.GET.get('q') # 获取参数\"q\"的值 ",
        "bbox": [
            161,
            405,
            452,
            417
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "return HttpResponse(f\"搜索内容：{query}\")",
        "bbox": [
            161,
            436,
            421,
            449
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "渲染过程: ",
        "text_level": 1,
        "bbox": [
            147,
            535,
            248,
            554
        ],
        "page_idx": 5
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1. search_form 视图调用 render(request, 'search_form.html')",
            "2. Django 模板引擎找到 templates/search_form.html",
            "3. 生成纯静态HTML 返回给用户"
        ],
        "bbox": [
            236,
            569,
            638,
            642
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "第二个方式没有模板渲染，直接返回httpresponse响应。",
        "bbox": [
            146,
            661,
            482,
            674
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "第三个例子",
        "bbox": [
            206,
            694,
            278,
            707
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "<form action=\"/search-post/\" method=\"post\"> ",
        "bbox": [
            206,
            725,
            480,
            738
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "{% csrf_token %} ",
        "bbox": [
            221,
            756,
            329,
            769
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "<input type=\"text\" name=\"q\"> ",
        "bbox": [
            221,
            788,
            403,
            800
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "<input type $\\mathrel { \\mathop = } \\frac { \\ d } { \\ d t }$ \"submit\" value=\"搜索\"> ",
        "bbox": [
            221,
            819,
            436,
            831
        ],
        "page_idx": 5
    },
    {
        "type": "text",
        "text": "</form> ",
        "bbox": [
            206,
            850,
            258,
            860
        ],
        "page_idx": 5
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "<p>\\{rtt}></p> ",
        "guess_lang": "txt",
        "bbox": [
            206,
            95,
            299,
            107
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "项目 GET 示例 POST 示例",
        "bbox": [
            176,
            126,
            623,
            139
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "URL 个数 2 个 (/search-form/ 和 /search/) 1 个 (/search-post/)",
        "bbox": [
            176,
            159,
            684,
            173
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "表单方法 method=\"get\" method=\"post\"",
        "bbox": [
            176,
            192,
            660,
            205
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "数据传输方式 通过 URL 参数 (?q=...) 通过请求体",
        "bbox": [
            176,
            225,
            625,
            237
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "安全性 较低，数据暴露在 URL 中 更安全，数据在请求体中",
        "bbox": [
            176,
            256,
            705,
            271
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "CSRF Token 不需要 必须要",
        "bbox": [
            176,
            290,
            600,
            303
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "View 函数数量 2 个 1 个 ",
        "bbox": [
            176,
            323,
            584,
            335
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "显示与处理 分开处理 合并处理",
        "bbox": [
            176,
            356,
            611,
            369
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "(1) 您的两个视图函数",
        "text_level": 1,
        "bbox": [
            147,
            388,
            278,
            400
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "# 显示视图 （表面看只负责显示）",
        "bbox": [
            146,
            419,
            344,
            431
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "def search_form(request): ",
        "bbox": [
            146,
            450,
            305,
            462
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "return render(request, 'search_form.html') # 但本质上仍是View 层 ",
        "bbox": [
            161,
            481,
            563,
            494
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "# 处理视图 （表面看只负责处理）",
        "bbox": [
            146,
            512,
            344,
            524
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "def search(request): ",
        "bbox": [
            146,
            543,
            270,
            555
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "message $=$ process_request(request.GET) # 处理逻辑 ",
        "bbox": [
            161,
            574,
            495,
            586
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "return HttpResponse(message) # 响应生成",
        "bbox": [
            161,
            605,
            428,
            618
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "本质：",
        "text_level": 1,
        "bbox": [
            147,
            636,
            183,
            646
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "这两个函数都属于View 层，只是内部逻辑侧重不同。Django 的View 是统一的请求处理器，不强制分离显示与处理。",
        "bbox": [
            144,
            653,
            840,
            667
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "(2) 为什么不是两个MVT？",
        "text_level": 1,
        "bbox": [
            147,
            684,
            304,
            697
        ],
        "page_idx": 6
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "Model：两个视图可能共享同一个Model（如都查询Article 表）",
            "Template：search_form 使用模板，search 直接返回响应（动态选择）",
            "View：都是接收request 并返回响应的函数"
        ],
        "bbox": [
            176,
            715,
            633,
            791
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "创建第一个项目",
        "text_level": 1,
        "bbox": [
            147,
            820,
            428,
            850
        ],
        "page_idx": 6
    },
    {
        "type": "text",
        "text": "使用 django-admin 来创建 HelloWorld 项目：",
        "bbox": [
            147,
            868,
            413,
            881
        ],
        "page_idx": 6
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "django-admin startproject HelloWorld ",
        "guess_lang": "txt",
        "bbox": [
            147,
            93,
            426,
            107
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "$ cd HelloWorld/ ",
        "guess_lang": "txt",
        "bbox": [
            147,
            123,
            250,
            135
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "$tree ",
        "guess_lang": "txt",
        "bbox": [
            147,
            155,
            184,
            166
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "HelloWorld/ #项目根目录",
        "guess_lang": "txt",
        "bbox": [
            147,
            186,
            366,
            198
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "manage.py #项目管理脚本",
        "guess_lang": "txt",
        "bbox": [
            149,
            216,
            401,
            229
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "dbsqlite3 #SQLite数据库文件",
        "guess_lang": "txt",
        "bbox": [
            149,
            247,
            420,
            260
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "—— Pycache_// #Python字节码缓存",
        "guess_lang": "txt",
        "bbox": [
            149,
            278,
            440,
            292
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "—HelloWorld/#项目配置目录（与项目同名）",
        "guess_lang": "txt",
        "bbox": [
            149,
            309,
            482,
            322
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "init.py #包标识文件",
        "guess_lang": "txt",
        "bbox": [
            164,
            340,
            379,
            354
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "—settings.py #项目设置",
        "guess_lang": "txt",
        "bbox": [
            164,
            370,
            364,
            384
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "├—urls.py #主路由配置 ",
        "guess_lang": "txt",
        "bbox": [
            164,
            401,
            364,
            414
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "— asgi.py #ASGI 配置 ",
        "guess_lang": "txt",
        "bbox": [
            164,
            432,
            363,
            447
        ],
        "page_idx": 7
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "—wsgi.py #WSGI配置 ",
        "guess_lang": "txt",
        "bbox": [
            164,
            464,
            369,
            478
        ],
        "page_idx": 7
    },
    {
        "type": "text",
        "text": "目录说明：",
        "text_level": 1,
        "bbox": [
            147,
            502,
            253,
            521
        ],
        "page_idx": 7
    },
    {
        "type": "text",
        "text": "项目根目录 (HelloWorld/)：",
        "bbox": [
            147,
            538,
            312,
            552
        ],
        "page_idx": 7
    },
    {
        "type": "table",
        "img_path": "images/c43fbf5546482fbe70641375fef390807849c110805286d7c5ea772917c5c607.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>文件/目录</td><td>作用</td></tr><tr><td>manage.py</td><td>Django命令行工具入口，可让你以各种方式与该Django项目进行交互。用于运行开发服务器、数据库迁移等操作。</td></tr><tr><td>db.sql3</td><td>SQLite 数据库文件（默认数据库，开发环境使用）。</td></tr><tr><td>__pycache__I</td><td>Python字节码缓存目录（自动生成，无需手动修改）。</td></tr></table>",
        "bbox": [
            147,
            557,
            998,
            684
        ],
        "page_idx": 7
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "HelloWorld: 项目的容器。",
            "HelloWorld/__init__.py: 一个空文件，告诉 Python 该目录是一个 Python 包。",
            "HelloWorld/asgi.py: 一个 ASGI 兼容的 Web 服务器的入口，以便运行你的项目。",
            "HelloWorld/settings.py: 该 Django 项目的设置/配置。"
        ],
        "bbox": [
            132,
            709,
            643,
            815
        ],
        "page_idx": 7
    },
    {
        "type": "text",
        "text": "",
        "bbox": [
            183,
            841,
            194,
            848
        ],
        "page_idx": 7
    },
    {
        "type": "text",
        "text": "核心配置文件，包含：",
        "bbox": [
            213,
            858,
            344,
            871
        ],
        "page_idx": 7
    },
    {
        "type": "text",
        "text": "- 数据库设置 (DATABASES)",
        "bbox": [
            213,
            877,
            383,
            890
        ],
        "page_idx": 7
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "- 静态文件路径 (STATIC_URL)",
            "- 应用注册 (INSTALLED_APPS) ",
            "- 调试模式 (DEBUG=True/False)"
        ],
        "bbox": [
            213,
            101,
            411,
            152
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "HelloWorld/urls.py: 该 Django 项目的 URL 声明; 一份由 Django 驱动的网站\"目录\"。",
        "bbox": [
            134,
            206,
            665,
            221
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "主路由配置文件，定义URL 路径与视图的映射关系。",
        "bbox": [
            169,
            263,
            482,
            277
        ],
        "page_idx": 8
    },
    {
        "type": "image",
        "img_path": "images/a823f7d1709065a5407af080d7f7f97b7690b14cc995738e94875496ffb4c9e6.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            166,
            332,
            771,
            456
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "HelloWorld/wsgi.py: 一个 WSGI 兼容的 Web 服务器的入口，以便运行你的项目。",
        "bbox": [
            134,
            475,
            652,
            489
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "关键文件详解",
        "text_level": 1,
        "bbox": [
            147,
            517,
            292,
            536
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "1. settings.py（核心配置）",
        "bbox": [
            134,
            579,
            285,
            592
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            134,
            609,
            166,
            621
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "# HelloWorld/settings.py 示例片段",
        "bbox": [
            134,
            640,
            329,
            653
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "# 安全警告：生产环境必须关闭DEBUG！",
        "bbox": [
            134,
            678,
            369,
            690
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "DEBUG $=$ True ",
        "bbox": [
            134,
            697,
            215,
            707
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "# 允许访问的域名（DEBUG=False 时需配置）",
        "bbox": [
            134,
            734,
            389,
            746
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "ALLOWED_HOSTS = [] ",
        "bbox": [
            134,
            752,
            255,
            764
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "# 注册的 Django 应用",
        "bbox": [
            134,
            789,
            260,
            802
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "INSTALLED_APPS = [ ",
        "bbox": [
            134,
            807,
            248,
            820
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "'django.contrib.admin', # 管理员后台",
        "bbox": [
            147,
            826,
            359,
            838
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "'django.contrib.auth', # 认证系统",
        "bbox": [
            147,
            845,
            341,
            857
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "'django.contrib.contenttypes', ",
        "bbox": [
            147,
            863,
            314,
            875
        ],
        "page_idx": 8
    },
    {
        "type": "text",
        "text": "'django.contrib.sessions', ",
        "bbox": [
            147,
            882,
            287,
            892
        ],
        "page_idx": 8
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.messages',  \n'django.contrib(staticfiles', #静态文件处理  \n]  \n#数据库配置（默认SQLite）  \nDATABASES = {  \n    'default': {  \n        'ENGINE': 'django.db.backendssqlite3',  \n        'NAME': BASE_DIR / 'db.sqlite3', #数据库文件路径}  \n}  \n#静态文件URL（CSS/JS/图片）  \nSTATIC_URL = 'static/'",
        "guess_lang": "python",
        "bbox": [
            129,
            93,
            450,
            350
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "2. urls.py（路由配置）",
        "text_level": 1,
        "bbox": [
            134,
            366,
            263,
            379
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            134,
            397,
            168,
            410
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "# HelloWorld/urls.py 示例",
        "bbox": [
            134,
            428,
            285,
            441
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            134,
            466,
            327,
            478
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "from django.urls import path ",
        "bbox": [
            134,
            483,
            300,
            495
        ],
        "page_idx": 9
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "urlpatterns $= [$ path('admin／',admin.site.urls)，#后台管理路由 #可在此添加自定义路由，如： #path('blog／',include('blog URLs'))   \n]",
        "bbox": [
            134,
            521,
            416,
            608
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "3. manage.py（项目管理脚本）",
        "text_level": 1,
        "bbox": [
            134,
            626,
            314,
            639
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            134,
            657,
            168,
            669
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "#!/usr/bin/env python ",
        "bbox": [
            134,
            688,
            262,
            700
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "import os ",
        "bbox": [
            134,
            707,
            193,
            718
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "import sys ",
        "bbox": [
            134,
            725,
            196,
            737
        ],
        "page_idx": 9
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "if __name__ == \"_main_:  \nos.environ.setdefault(\"DJANGO.SettingsMODULE\", \"HelloWorld.Settings\")  \nfrom django.core.management import execute_from_command_line  \nexecute_from_command_line(sys.argv) ",
        "guess_lang": "python",
        "bbox": [
            134,
            762,
            571,
            832
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "启动服务器",
        "text_level": 1,
        "bbox": [
            147,
            854,
            265,
            873
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "接下来我们进入 HelloWorld 目录输入以下命令，启动服务器：",
        "bbox": [
            132,
            889,
            495,
            902
        ],
        "page_idx": 9
    },
    {
        "type": "text",
        "text": "0.0.0.0 让其它电脑可连接到开发服务器，8000 为端口号。如果不说明，那么端口号默认为 $8 0 0 0 _ { \\circ }$ ",
        "bbox": [
            132,
            126,
            702,
            139
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "在浏览器输入你服务器的 ip（这里我们输入本机 IP 地址： 127.0.0.1:8000） 及端口号。",
        "bbox": [
            132,
            156,
            640,
            170
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "常用 django-admin 命令详解",
        "text_level": 1,
        "bbox": [
            147,
            193,
            458,
            215
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "django-admin 是 Django 框架提供的一个命令行工具，它是管理 Django 项目的核心工具。",
        "bbox": [
            144,
            227,
            677,
            241
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "无论是创建新项目、运行开发服务器，还是执行数据库迁移，django-admin 都是不可或缺的工具。",
        "bbox": [
            144,
            249,
            724,
            262
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "要查看 django-admin 提供的所有命令，可以运行：",
        "bbox": [
            146,
            269,
            448,
            284
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "django-admin help ",
        "bbox": [
            146,
            292,
            263,
            305
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "1. 创建新项目",
        "text_level": 1,
        "bbox": [
            147,
            355,
            294,
            375
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "django-admin startproject 项目名称",
        "bbox": [
            134,
            417,
            339,
            429
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "这个命令会在当前目录下创建一个新的 Django 项目，包含基本的项目结构：",
        "bbox": [
            132,
            447,
            579,
            460
        ],
        "page_idx": 10
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "manage.py：项目管理脚本",
            "项目名称/：项目主目录"
        ],
        "bbox": [
            176,
            479,
            368,
            521
        ],
        "page_idx": 10
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o __init__.py",
            "o settings.py：项目设置文件",
            "o urls.py：URL 路由配置 ",
            "o wsgi.py：WSGI 应用入口"
        ],
        "bbox": [
            236,
            539,
            425,
            645
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "2. 创建新应用",
        "text_level": 1,
        "bbox": [
            146,
            675,
            295,
            694
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "虽然通常使用 manage.py 来创建应用，但也可以通过 django-admin：",
        "bbox": [
            134,
            736,
            529,
            749
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "django-admin startapp 应用名称",
        "bbox": [
            134,
            768,
            319,
            780
        ],
        "page_idx": 10
    },
    {
        "type": "text",
        "text": "这会创建一个新的 Django 应用，包含：",
        "bbox": [
            134,
            799,
            364,
            810
        ],
        "page_idx": 10
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "migrations/：数据库迁移文件目录",
            "__init__.py",
            "admin.py：管理后台配置"
        ],
        "bbox": [
            176,
            829,
            408,
            904
        ],
        "page_idx": 10
    },
    {
        "type": "header",
        "text": "python3 manage.py runserver 0.0.0.0:8000 ",
        "bbox": [
            134,
            95,
            376,
            107
        ],
        "page_idx": 10
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "apps.py：应用配置 ",
            "models.py：数据模型定义",
            "tests.py：测试代码 ",
            "views.py：视图函数"
        ],
        "bbox": [
            176,
            95,
            359,
            199
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "models.py ",
        "bbox": [
            176,
            219,
            238,
            229
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "定义数据模型，与数据库表对应：",
        "bbox": [
            176,
            250,
            374,
            261
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            178,
            281,
            208,
            292
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "from django.db import models ",
        "bbox": [
            176,
            312,
            349,
            323
        ],
        "page_idx": 11
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "class Product/models.Model): name $=$ models.CharField(max_length $\\coloneqq$ 100) price $=$ modelsDecimalField(maxDigits $\\coloneqq$ 10,decimal_places $\\coloneqq$ 2) description $=$ models.TextField() ",
        "bbox": [
            176,
            349,
            537,
            416
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "def __str__(self): ",
        "bbox": [
            189,
            441,
            287,
            453
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "return self.name ",
        "bbox": [
            201,
            461,
            295,
            470
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "views.py ",
        "bbox": [
            176,
            492,
            230,
            502
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "处理业务逻辑，返回响应：",
        "bbox": [
            176,
            521,
            334,
            533
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            178,
            552,
            208,
            564
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "from django.shortcuts import render ",
        "bbox": [
            176,
            583,
            383,
            595
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "from .models import Product ",
        "bbox": [
            176,
            602,
            342,
            613
        ],
        "page_idx": 11
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def product_list(request):\n    products = Product.objects.all()\n    return render(request, 'myapp/product_list.html', {'products': products}) ",
        "guess_lang": "python",
        "bbox": [
            176,
            640,
            589,
            688
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "admin.py ",
        "bbox": [
            176,
            708,
            233,
            719
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "配置 Django 管理后台：",
        "bbox": [
            176,
            739,
            310,
            751
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            178,
            770,
            208,
            781
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            176,
            800,
            369,
            812
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "from .models import Product ",
        "bbox": [
            176,
            820,
            342,
            829
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "@admin.register(Product) ",
        "bbox": [
            178,
            856,
            322,
            866
        ],
        "page_idx": 11
    },
    {
        "type": "text",
        "text": "3. 检查项目配置",
        "text_level": 1,
        "bbox": [
            146,
            186,
            319,
            205
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "django-admin check ",
        "bbox": [
            134,
            247,
            250,
            259
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "这个命令会检查你的 Django 项目是否有配置错误，包括：",
        "bbox": [
            132,
            278,
            470,
            291
        ],
        "page_idx": 12
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            ". 模型定义是否正确 ",
            "URL 配置是否有效",
            "模板设置是否正确",
            "静态文件配置等"
        ],
        "bbox": [
            176,
            309,
            319,
            414
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "4. 数据库迁移 ",
        "text_level": 1,
        "bbox": [
            146,
            444,
            295,
            463
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "Django 使用迁移系统来管理数据库模式变更：",
        "bbox": [
            134,
            505,
            400,
            517
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "django-admin makemigrations # 创建迁移文件",
        "bbox": [
            134,
            537,
            401,
            548
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "django-admin migrate # 应用迁移到数据库",
        "bbox": [
            134,
            568,
            401,
            579
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "5. 创建超级用户",
        "text_level": 1,
        "bbox": [
            146,
            608,
            317,
            627
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "django-admin createsuperuser ",
        "bbox": [
            134,
            671,
            305,
            681
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "这个命令会引导你创建一个可以访问 Django 管理后台的超级用户。",
        "bbox": [
            132,
            701,
            524,
            714
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "6.启动开发服务器",
        "text_level": 1,
        "bbox": [
            146,
            743,
            336,
            762
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "django-admin runserver ",
        "bbox": [
            134,
            804,
            273,
            816
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "基本语法:",
        "bbox": [
            134,
            835,
            196,
            847
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "python manage.py runserver [IP:端口] ",
        "bbox": [
            134,
            866,
            347,
            879
        ],
        "page_idx": 12
    },
    {
        "type": "header",
        "text": "class ProductAdmin(admin.ModelAdmin): ",
        "bbox": [
            176,
            95,
            410,
            107
        ],
        "page_idx": 12
    },
    {
        "type": "header",
        "text": "list_display $=$ ('name', 'price') ",
        "bbox": [
            189,
            112,
            351,
            124
        ],
        "page_idx": 12
    },
    {
        "type": "text",
        "text": "扩展目录（非自动生成，但常用）",
        "text_level": 1,
        "bbox": [
            146,
            101,
            477,
            120
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "HelloWorld/ ",
        "bbox": [
            134,
            135,
            206,
            145
        ],
        "page_idx": 13
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "├── apps/ # 推荐：存放所有自定义应用",
            "│ └── blog/ # 示例应用",
            "├── static/ # 静态文件（CSS/JS/图片）",
            "├── media/ # 用户上传文件",
            "├── templates/ # 全局模板目录",
            "└── requirements.txt # 项目依赖列表"
        ],
        "bbox": [
            136,
            165,
            410,
            332
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "1. apps/ 目录（推荐结构）",
        "text_level": 1,
        "bbox": [
            147,
            362,
            421,
            382
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "将应用集中管理，避免散落在项目根目录。",
        "bbox": [
            134,
            423,
            386,
            436
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "需在 settings.py 中配置 Python 路径：",
        "bbox": [
            134,
            454,
            347,
            467
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "import sys ",
        "bbox": [
            134,
            486,
            194,
            497
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))",
        "bbox": [
            134,
            516,
            401,
            527
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "2. 静态文件与媒体文件",
        "text_level": 1,
        "bbox": [
            144,
            558,
            389,
            577
        ],
        "page_idx": 13
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "static/：存放 CSS、JavaScript、图片等，通过 STATIC_URL 访问。",
            "media/：用户上传的文件（如头像），通过 MEDIA_URL 访问。需配置服务器在开发时提供访问：",
            ". # urls.py（仅开发环境）",
            "from django.conf import settings ",
            "from django.conf.urls.static import static "
        ],
        "bbox": [
            176,
            619,
            771,
            783
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "urlpatterns $+ =$ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ",
        "bbox": [
            134,
            804,
            584,
            818
        ],
        "page_idx": 13
    },
    {
        "type": "text",
        "text": "一个项目可以由多个应用",
        "text_level": 1,
        "bbox": [
            147,
            873,
            400,
            892
        ],
        "page_idx": 13
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "myproject/ ",
            "├── manage.py ",
            "├── requirements.txt ",
            "├── static/ ",
            "│ ── css/ ├ ",
            "│ ── js/ ├ ",
            "│ └── images/ ",
            "├── media/ ",
            "├── templates/ ",
            "│ └── base.html ",
            "└── myproject/ ",
            "── __init__.py ├",
            "── settings.py ├ ",
            "── urls.py ├ ",
            "── wsgi.py ├ ",
            "└── asgi.py ",
            "└── myapp1/ ",
            "── migrations/├ ",
            "── templates/ ├ ",
            "│ └── myapp1/ ",
            "── __init__.py ├",
            "── admin.py ├ ",
            "── apps.py ├ ",
            "── models.py ├ ",
            "── tests.py ├ "
        ],
        "bbox": [
            134,
            93,
            255,
            882
        ],
        "page_idx": 14
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "── urls.py ├ ",
            "└── views.py "
        ],
        "bbox": [
            147,
            93,
            221,
            137
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "└── myapp2/ ",
        "bbox": [
            136,
            156,
            211,
            168
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "└── ... (类似结构) ",
        "bbox": [
            147,
            187,
            248,
            200
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "实践建议",
        "text_level": 1,
        "bbox": [
            147,
            218,
            205,
            230
        ],
        "page_idx": 15
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1. 项目与应用分离：保持每个应用的独立性，便于复用",
            "2. 环境配置：使用不同的 settings 文件区分开发和生产环境",
            "3. 静态文件管理：开发时使用 STATICFILES_DIRS，生产时使用 collectstatic",
            "4. URL 设计：在应用级别定义 URL，然后在项目级别包含",
            "5. 模板组织：为每个应用创建子目录存放模板"
        ],
        "bbox": [
            176,
            249,
            618,
            386
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "生产环境vs 开发环境差异",
        "text_level": 1,
        "bbox": [
            146,
            404,
            304,
            418
        ],
        "page_idx": 15
    },
    {
        "type": "table",
        "img_path": "images/d1a1f6ba9036662acf8c0bf0be0cbc4df02f97c028a0669d048704990c5b36eb.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>文件/配置</td><td>开发环境</td><td>生产环境</td></tr><tr><td>DEBUG</td><td>True（显示错误详情）</td><td>False（隐藏错误，记录到日志）</td></tr><tr><td>数据库</td><td>SQLite（默认）</td><td>PostgreSQL/MySQL（性能优化）</td></tr><tr><td>静态文件</td><td>runserver 自动服务</td><td>使用 collectstatic 收集到 CDN</td></tr><tr><td>ALLOWED_HOSTS</td><td>空列表或 [&#x27;localhost&#x27;]</td><td>必须配置域名（如 [&#x27;example.com&#x27;])</td></tr></table>",
        "bbox": [
            147,
            432,
            998,
            657
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "path() 函数",
        "text_level": 1,
        "bbox": [
            147,
            695,
            309,
            722
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "Django path() 可以接收四个参数，分别是两个必选参数：route、view 和两个可选参数：kwargs、name。",
        "bbox": [
            146,
            728,
            766,
            743
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "语法格式：",
        "bbox": [
            147,
            747,
            203,
            760
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "path(route, view, kwargs=None, name=None) ",
        "bbox": [
            147,
            777,
            460,
            789
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "route：字符串，定义URL 的路径部分。可以包含变量，例如<int:my_variable>，以从URL 中捕获参数并将其传递给视图函数。",
        "bbox": [
            144,
            806,
            847,
            837
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "view：视图函数，处理与给定路由匹配的请求。可以是一个函数或一个基于类的视图。",
        "bbox": [
            146,
            843,
            658,
            856
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "kwargs（可选）：一个字典，包含传递给视图函数的额外关键字参数。",
        "bbox": [
            146,
            862,
            568,
            875
        ],
        "page_idx": 15
    },
    {
        "type": "text",
        "text": "name（可选）：为URL 路由指定一个唯一的名称，以便在代码的其他地方引用它。这对于在模板中生成URL 或在代码中进行重定向等操作非常有用。",
        "bbox": [
            144,
            93,
            848,
            126
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "这样我们就完成了使用模板来输出数据，从而实现数据与视图分离。",
        "bbox": [
            146,
            131,
            547,
            145
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "接下来我们将具体介绍模板中常用的语法规则。",
        "bbox": [
            146,
            149,
            428,
            162
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "Django 模板",
        "text_level": 1,
        "bbox": [
            147,
            193,
            329,
            222
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "Django 的模板系统（Template System）是用于将业务逻辑（Python）与展示层（HTML）分离的核心组件，它允许开发者通过简单的标签和变量动态生成 HTML 页面。",
        "bbox": [
            144,
            230,
            840,
            262
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "在上一章节中我们使用 django.http.HttpResponse() 来输出 \"Hello World！\"，该方式将数据与视图混合在一起，不符合 Django 的 MVT 思想。",
        "bbox": [
            144,
            269,
            843,
            302
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "本章节我们将为大家详细介绍Django 模板的应用，模板是一个文本，用于分离文档的表现形式和内容。",
        "bbox": [
            146,
            309,
            757,
            323
        ],
        "page_idx": 16
    },
    {
        "type": "table",
        "img_path": "images/d6715d65fc28226ad77843e37b6ae208fe41f5917292f96ae26d2c7786e130c3.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>功能</td><td>语法/示例</td><td>适用场景</td></tr><tr><td>变量渲染</td><td>{\\ variable }</td><td>动态显示数据</td></tr><tr><td>逻辑控制</td><td>{% if %}, {% for %}</td><td>条件/循环渲染</td></tr><tr><td>模板继承</td><td>{% extends %}, {% block %}</td><td>避免重复 HTML 结构</td></tr><tr><td>静态文件</td><td>{% static &#x27;path&#x27; %}</td><td>加载 CSS/JS/图片</td></tr><tr><td>自定义过滤器</td><td>@register.filter</td><td>扩展模板功能</td></tr></table>",
        "bbox": [
            147,
            329,
            1000,
            521
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "Django 模板标签",
        "text_level": 1,
        "bbox": [
            147,
            577,
            389,
            607
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "变量",
        "text_level": 1,
        "bbox": [
            147,
            621,
            201,
            640
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "模板语法：",
        "bbox": [
            147,
            653,
            211,
            668
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "view：｛\"HTML 变量名\" : \"views 变量名\"｝ ",
        "bbox": [
            147,
            683,
            413,
            697
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "HTML：｛｛变量名｝｝ ",
        "bbox": [
            147,
            712,
            275,
            726
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "列表",
        "text_level": 1,
        "bbox": [
            147,
            747,
            213,
            769
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "templates 中的 runoob.html 中，可以用 . 索引下标取出对应的元素。",
        "bbox": [
            146,
            782,
            552,
            796
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "HelloWorld/HelloWorld/views.py 文件代码：",
        "bbox": [
            146,
            800,
            472,
            815
        ],
        "page_idx": 16
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.shortcuts import render  \ndef runoob(request):  \n    views_list = [\"菜鸟教程1\", \"菜鸟教程2\", \"菜鸟教程3\"]  \n    return render(request, \"runoob.html\", {\"views_list\": views_list}) ",
        "guess_lang": "python",
        "bbox": [
            147,
            822,
            675,
            892
        ],
        "page_idx": 16
    },
    {
        "type": "text",
        "text": "templates 中的 runoob.html 中，可以用 .键 取出对应的值。",
        "bbox": [
            146,
            93,
            497,
            107
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "过滤器",
        "text_level": 1,
        "bbox": [
            146,
            122,
            225,
            142
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "模板语法：",
        "bbox": [
            146,
            156,
            203,
            168
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "{{ 变量名 | 过滤器：可选参数 }}",
        "bbox": [
            147,
            185,
            354,
            198
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "模板过滤器可以在变量被显示前修改它，过滤器使用管道字符，如下所示：",
        "bbox": [
            146,
            214,
            586,
            227
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "safe ",
        "bbox": [
            147,
            233,
            179,
            243
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "将字符串标记为安全，不需要转义。",
        "bbox": [
            146,
            250,
            357,
            263
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "要保证 views.py 传过来的数据绝对安全，才能用 safe。",
        "bbox": [
            146,
            269,
            470,
            282
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "和后端 views.py 的 mark_safe 效果相同。",
        "bbox": [
            146,
            288,
            391,
            300
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "Django 会自动对 views.py 传到HTML文件中的标签语法进行转义，令其语义失效。加safe 过滤器是告诉Django 该",
        "bbox": [
            144,
            307,
            835,
            319
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "数据是安全的，不必对其进行转义，可以让该数据语义生效。",
        "bbox": [
            146,
            326,
            505,
            338
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "if/else 标签",
        "text_level": 1,
        "bbox": [
            146,
            353,
            275,
            373
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "基本语法格式如下：",
        "bbox": [
            146,
            386,
            265,
            399
        ],
        "page_idx": 17
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "$\\{\\% \\text{if condition}\\}$ display{ $\\%$ endif} ",
        "bbox": [
            147,
            414,
            357,
            458
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "或者：",
        "bbox": [
            146,
            474,
            184,
            486
        ],
        "page_idx": 17
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "$\\{\\%$ if condition1 $\\}$ . display 1{% elif condition2 $\\}$ . display 2{% else $\\}$ display 3{%endif $\\}$ ",
        "bbox": [
            147,
            502,
            431,
            604
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "根据条件判断是否输出。if/else 支持嵌套。",
        "bbox": [
            146,
            621,
            396,
            633
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "$\\{ \\%$ if %} 标签接受 and ， or 或者 not 关键字来对多个变量做判断 ，或者对变量取反（ not )，例如：",
        "bbox": [
            146,
            639,
            729,
            653
        ],
        "page_idx": 17
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "$\\{\\%$ if athlete_list and coach_list $\\}$ athletes和coaches变量都是可用的。 $\\{\\%$ endif $\\}$ ",
        "bbox": [
            147,
            667,
            521,
            711
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "遍历字典: 可以直接用字典.items 方法，用变量的解包分别获取键和值。",
        "bbox": [
            146,
            727,
            571,
            739
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "for 标签 ",
        "text_level": 1,
        "bbox": [
            146,
            791,
            238,
            810
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "$\\{ \\%$ for $\\% \\}$ 允许我们在一个序列上迭代。",
        "bbox": [
            146,
            825,
            376,
            838
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "与 Python 的 for 语句的情形类似，循环语法是 for X in Y ，Y 是要迭代的序列而 X 是在每一个特定的循环中使用的变量名称。",
        "bbox": [
            144,
            844,
            836,
            873
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "每一次循环中，模板系统会渲染在 $6 \\%$ for $\\% 3$ 和 $6 \\%$ endfor $\\% 3$ 之间的所有内容。",
        "bbox": [
            146,
            881,
            648,
            894
        ],
        "page_idx": 17
    },
    {
        "type": "text",
        "text": "例如，给定一个运动员列表 athlete_list 变量，我们可以使用下面的代码来显示这个列表：",
        "bbox": [
            146,
            95,
            606,
            105
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "{% empty %} ",
        "bbox": [
            146,
            112,
            233,
            124
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "可选的 {% empty $\\% \\}$ 从句：在循环为空的时候执行（即 in 后面的参数布尔值为 False ）。",
        "bbox": [
            146,
            131,
            670,
            143
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "注释标签 ",
        "text_level": 1,
        "bbox": [
            147,
            159,
            248,
            179
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "Django 注释使用 {# #}。",
        "bbox": [
            146,
            193,
            285,
            206
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "{# 这是一个注释 #}",
        "bbox": [
            147,
            221,
            268,
            234
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "include 标签 ",
        "text_level": 1,
        "bbox": [
            147,
            256,
            292,
            278
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "{% include %} 标签允许在模板中包含其它的模板的内容。",
        "bbox": [
            146,
            291,
            485,
            304
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "下面这个例子都包含了 nav.html 模板：",
        "bbox": [
            147,
            310,
            374,
            322
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "{% include \"nav.html\" %} ",
        "bbox": [
            147,
            338,
            332,
            351
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "csrf_token ",
        "text_level": 1,
        "bbox": [
            147,
            373,
            307,
            395
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "csrf_token 用于form表单中，作用是跨站请求伪造保护。",
        "bbox": [
            146,
            407,
            482,
            419
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "如果不用 $\\{ \\%$ csrf_token $\\% \\}$ 标签，在用 form 表单时，要再次跳转页面会报403 权限错误。",
        "bbox": [
            146,
            425,
            684,
            437
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "用了{% csrf_token %} 标签，在 form 表单提交数据时，才会成功。",
        "bbox": [
            146,
            444,
            544,
            456
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "自定义标签和过滤器",
        "text_level": 1,
        "bbox": [
            149,
            468,
            431,
            493
        ],
        "page_idx": 18
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1、在应用目录下创建 templatetags 目录(与 templates 目录同级，目录名只能是 templatetags)",
            "4、利用装饰器 $@$ register.filter 自定义过滤器。"
        ],
        "bbox": [
            146,
            502,
            710,
            533
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "注意：装饰器的参数最多只能有 2 个。 ",
        "bbox": [
            146,
            539,
            371,
            551
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "4、利用装饰器 @register.filter 自定义过滤器。",
        "bbox": [
            146,
            558,
            418,
            570
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "注意：装饰器的参数最多只能有 2 个。 ",
        "bbox": [
            146,
            576,
            371,
            588
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "@register.filterdef my_filter(v1, v2): ",
        "bbox": [
            147,
            604,
            436,
            618
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "return v1 * v2 ",
        "bbox": [
            178,
            634,
            287,
            645
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "5、利用装饰器 @register.simple_tag 自定义标签。",
        "bbox": [
            146,
            664,
            445,
            677
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "配置静态文件",
        "text_level": 1,
        "bbox": [
            147,
            688,
            337,
            712
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "1、在项目根目录下创建 statics 目录。",
        "bbox": [
            146,
            722,
            369,
            734
        ],
        "page_idx": 18
    },
    {
        "type": "image",
        "img_path": "images/7a0bccfdc891cb8db6929d9ee4656cf5853f50e3339e9658e86f85cd0a8ec3cd.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            147,
            737,
            364,
            866
        ],
        "page_idx": 18
    },
    {
        "type": "text",
        "text": "2、在 settings 文件的最下方配置添加以下配置：",
        "bbox": [
            146,
            871,
            431,
            883
        ],
        "page_idx": 18
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "STATIC_url $=$ \"/static/'#别名  \nSTATICFILES_DIRS $=$ [os.path.join(Base_DIR，\"statics\")，]",
        "bbox": [
            147,
            93,
            453,
            165
        ],
        "page_idx": 19
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "3、在 statics 目录下创建 css 目录，js 目录，images 目录，plugins 目录， 分别放 css 文件，js 文件，图片，插件。",
            "4、把 bootstrap 框架放入插件目录 plugins。",
            "5、在 HTML 文件的 head 标签中引入 bootstrap。"
        ],
        "bbox": [
            144,
            181,
            826,
            231
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "注意：此时引用路径中的要用配置文件中的别名static，而不是目录 statics。",
        "bbox": [
            146,
            237,
            594,
            250
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "模板继承",
        "text_level": 1,
        "bbox": [
            146,
            281,
            275,
            306
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "模板可以用继承的方式来实现复用，减少冗余内容。",
        "bbox": [
            144,
            317,
            452,
            329
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "网页的头部和尾部内容一般都是一致的，我们就可以通过模板继承来实现复用。",
        "bbox": [
            146,
            336,
            611,
            348
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "父模板用于放置可重复利用的内容，子模板继承父模板的内容，并放置自己的内容。。",
        "bbox": [
            146,
            355,
            652,
            367
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "父模板",
        "text_level": 1,
        "bbox": [
            147,
            382,
            223,
            401
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "标签 block...endblock: 父模板中的预留区域，该区域留给子模板填充差异性的内容，不同预留区域名字不能相同。",
        "bbox": [
            144,
            416,
            821,
            429
        ],
        "page_idx": 19
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "$\\{\\% \\text{block} \\text{名称} \\% \\}$ 预留给子模板的区域，可以设置设置默认内容 $\\{\\% \\text{endblock} \\text{名称} \\% \\}$ ",
        "bbox": [
            147,
            445,
            662,
            458
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "子模板",
        "text_level": 1,
        "bbox": [
            147,
            480,
            223,
            500
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "子模板使用标签extends 继承父模板：",
        "bbox": [
            147,
            514,
            371,
            527
        ],
        "page_idx": 19
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "% extends \"父模板路径\"%}",
        "guess_lang": "txt",
        "bbox": [
            147,
            544,
            326,
            556
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "子模板如果没有设置父模板预留区域的内容，则使用在父模板设置的默认内容，当然也可以都不设置，就为空。",
        "bbox": [
            146,
            573,
            800,
            585
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "子模板设置父模板预留区域的内容：",
        "bbox": [
            147,
            590,
            357,
            602
        ],
        "page_idx": 19
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "{ $\\%$ block名称 $\\%$ }内容{ $\\%$ endblock名称 $\\%$ }",
        "bbox": [
            147,
            621,
            448,
            633
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "Django 模型使用自带的 ORM。",
        "bbox": [
            146,
            650,
            329,
            662
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "对象关系映射（Object Relational Mapping，简称ORM ）用于实现面向对象编程语言里不同类型系统的数据之间的转换。",
        "bbox": [
            144,
            671,
            843,
            702
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。",
        "bbox": [
            146,
            711,
            473,
            722
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "ORM 是通过使用描述对象和数据库之间的映射的元数据，将程序中的对象自动持久化到数据库中。",
        "bbox": [
            146,
            732,
            727,
            745
        ],
        "page_idx": 19
    },
    {
        "type": "text",
        "text": "ORM 解析过程:",
        "bbox": [
            147,
            772,
            243,
            785
        ],
        "page_idx": 19
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1、ORM 会将 Python 代码转成为 SQL 语句。",
            "2、SQL 语句通过 pymysql 传送到数据库服务端。",
            "3、在数据库中执行 SQL 语句并将结果返回。"
        ],
        "bbox": [
            134,
            791,
            423,
            865
        ],
        "page_idx": 19
    },
    {
        "type": "image",
        "img_path": "images/89ff050f91c03b4570c077c1d5a0b35eba5235ff5bcb76f7b6a8328a7ce6d1c7.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            157,
            91,
            963,
            218
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "ORM 对应关系表：",
        "bbox": [
            147,
            237,
            257,
            249
        ],
        "page_idx": 20
    },
    {
        "type": "image",
        "img_path": "images/e3b1faa6363d2686aa3e435194c96d28a466d037563692d969fb0f4eb9f90257.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            193,
            293,
            793,
            495
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "数据库配置",
        "text_level": 1,
        "bbox": [
            147,
            558,
            218,
            570
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "ORM 无法操作到数据库级别，只能操作到数据表",
        "bbox": [
            146,
            589,
            438,
            602
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "我们在项目的 settings.py 文件中找到 DATABASES 配置项，将其信息修改为：",
        "bbox": [
            146,
            651,
            584,
            664
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "HelloWorld/HelloWorld/settings.py: 文件代码：",
        "bbox": [
            146,
            682,
            415,
            695
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "DATABASES $=$ { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数 据 库 引 擎 'NAME': 'runoob', # 数 据 库 名 称 'HOST':'127.0.0.1', # 数据库地址，本机 ip 地址 127.0.0.1 'PORT': 3306, # 端口 'USER': 'root', # 数据库用户名 'PASSWORD': '123456',# 数据库密码 } }",
        "bbox": [
            144,
            713,
            848,
            764
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "上面包含数据库名称和用户的信息，它们与 MySQL 中对应数据库和用户的设置相同。Django 根据这一设置，与 MySQL中相应的数据库和用户连接起来。",
        "bbox": [
            144,
            781,
            848,
            812
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "接下来，告诉 Django 使用 pymysql 模块连接 mysql 数据库：",
        "bbox": [
            146,
            831,
            490,
            844
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            862,
            179,
            873
        ],
        "page_idx": 20
    },
    {
        "type": "text",
        "text": "# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置",
        "bbox": [
            144,
            93,
            534,
            109
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "import pymysql",
        "bbox": [
            146,
            112,
            238,
            124
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "pymysql.install_as_MySQLdb() ",
        "bbox": [
            146,
            131,
            317,
            143
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "定义模型",
        "text_level": 1,
        "bbox": [
            146,
            218,
            275,
            243
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "创建 APP",
        "text_level": 1,
        "bbox": [
            146,
            261,
            255,
            280
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "Django 规定，如果要使用模型，必须要创建一个 app。我们使用以下命令创建一个 TestModel 的 app:",
        "bbox": [
            144,
            294,
            751,
            307
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "django-admin startapp TestModel ",
        "guess_lang": "txt",
        "bbox": [
            146,
            323,
            386,
            336
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "# models.py from django.db import models class Test(model.Model): name = models.CharField(max_length=20) ",
        "guess_lang": "python",
        "bbox": [
            146,
            353,
            705,
            384
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "以上的类名代表了数据库表名，且继承了models.Model，类里面的字段代表数据表中的字段(name)，数据类型则由CharField（相当于 varchar）、DateField（相当于 datetime）， max_length 参数限定长度。",
        "bbox": [
            144,
            390,
            830,
            423
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "在命令行中运行：",
        "bbox": [
            146,
            428,
            250,
            439
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "$ python3 manage.py migrate # 创建表结构",
        "guess_lang": "txt",
        "bbox": [
            146,
            457,
            458,
            469
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "$ python3 manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更",
        "guess_lang": "txt",
        "bbox": [
            146,
            486,
            776,
            499
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "$ python3 manage.py migrate TestModel # 创建表结构",
        "guess_lang": "txt",
        "bbox": [
            146,
            516,
            532,
            529
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "看到几行 \"Creating table…\" 的字样，你的数据表就创建好了。",
        "bbox": [
            144,
            543,
            608,
            558
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "Creating tables ... ",
        "bbox": [
            146,
            563,
            275,
            576
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "",
        "bbox": [
            146,
            586,
            176,
            594
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "Creating table TestModel_test #我们自定义的表",
        "bbox": [
            144,
            600,
            505,
            614
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "",
        "bbox": [
            146,
            624,
            176,
            631
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "表名组成结构为：应用名_类名（如：TestModel_test）。",
        "bbox": [
            144,
            636,
            576,
            652
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "注意：尽管我们没有在 models 给表设置主键，但是 Django 会自动添加一个 id 作为主键。",
        "bbox": [
            144,
            653,
            826,
            670
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "添加数据",
        "text_level": 1,
        "bbox": [
            146,
            673,
            221,
            688
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "添加数据需要先创建对象，然后再执行 save 函数，相当于SQL中的INSERT：",
        "bbox": [
            144,
            692,
            727,
            707
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "- - coding: utf-8 - ",
        "guess_lang": "txt",
        "bbox": [
            146,
            709,
            300,
            724
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.http import HttpResponse ",
        "guess_lang": "python",
        "bbox": [
            146,
            747,
            428,
            762
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from TestModel.models import Test ",
        "guess_lang": "python",
        "bbox": [
            146,
            785,
            406,
            799
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "# 数据库操作",
        "text_level": 1,
        "bbox": [
            146,
            822,
            252,
            837
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def testdb(request): ",
        "guess_lang": "txt",
        "bbox": [
            146,
            841,
            292,
            854
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "test1 = Test(name='runoob') ",
        "guess_lang": "txt",
        "bbox": [
            161,
            859,
            366,
            873
        ],
        "page_idx": 21
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "test1.save() ",
        "guess_lang": "txt",
        "bbox": [
            163,
            877,
            248,
            891
        ],
        "page_idx": 21
    },
    {
        "type": "text",
        "text": "return HttpResponse(\"<p>数据添加成功！</p>\")",
        "bbox": [
            164,
            93,
            524,
            109
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "更新数据",
        "text_level": 1,
        "bbox": [
            149,
            112,
            221,
            127
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "修改数据可以使用 save() 或 update():",
        "bbox": [
            147,
            130,
            428,
            145
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "- -coding: utf-8 - ",
        "guess_lang": "txt",
        "bbox": [
            147,
            149,
            300,
            162
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.http import HttpResponse ",
        "guess_lang": "python",
        "bbox": [
            147,
            186,
            428,
            200
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from TestModel.models import Test ",
        "guess_lang": "python",
        "bbox": [
            147,
            223,
            406,
            237
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [
            "# 数据库操作"
        ],
        "code_body": "def testdb(request): ",
        "guess_lang": "txt",
        "bbox": [
            147,
            279,
            292,
            293
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "# 修改其中一个 $\\mathrm { i } { \\mathsf { d } } { = } 1$ 的 name 字段，再 save，相当于 SQL 中的 UPDATE",
        "bbox": [
            163,
            297,
            695,
            312
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "test1 = Test.objects.get(id=1) ",
        "guess_lang": "python",
        "bbox": [
            163,
            316,
            371,
            330
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "test1.name = 'Google' ",
        "guess_lang": "python",
        "bbox": [
            164,
            335,
            322,
            348
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "test1.save() ",
        "guess_lang": "txt",
        "bbox": [
            164,
            354,
            248,
            367
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "# 另外一种方式",
        "guess_lang": "txt",
        "bbox": [
            164,
            390,
            285,
            405
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Test.objects.filter(id=1).update(name='Google') ",
        "guess_lang": "python",
        "bbox": [
            164,
            407,
            512,
            423
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "修改所有的列",
        "guess_lang": "txt",
        "bbox": [
            164,
            445,
            285,
            460
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Test.objects.all().update(name='Google') ",
        "guess_lang": "txt",
        "bbox": [
            164,
            464,
            468,
            479
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return HttpResponse(\"/<p>修改成功</p>\")",
        "guess_lang": "lua",
        "bbox": [
            164,
            501,
            470,
            517
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "删除数据",
        "text_level": 1,
        "bbox": [
            147,
            520,
            221,
            533
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "删除数据库中的对象只需调用该对象的delete()方法即可：",
        "bbox": [
            147,
            538,
            588,
            552
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "Django 表单",
        "text_level": 1,
        "bbox": [
            147,
            588,
            359,
            619
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "HTML表单是网站交互性的经典方式。 本章将介绍如何用Django对用户提交的表单数据进行处理。",
        "bbox": [
            146,
            638,
            729,
            652
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "HTTP 请求",
        "text_level": 1,
        "bbox": [
            147,
            663,
            302,
            687
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "HTTP协议以\"请求－回复\"的方式工作。客户发送请求时，可以在请求中附加数据。服务器通过解析请求，就可以获得客户传来的数据，并根据URL来提供特定的服务。",
        "bbox": [
            146,
            697,
            843,
            728
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "GET 方法",
        "text_level": 1,
        "bbox": [
            147,
            746,
            257,
            765
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "用户提交了表单，服务器解析表单并回复",
        "bbox": [
            147,
            781,
            468,
            796
        ],
        "page_idx": 22
    },
    {
        "type": "text",
        "text": "我们在之前的项目中创建一个 search.py 文件，用于接收用户的请求：",
        "bbox": [
            146,
            801,
            557,
            814
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.http import HttpResponse ",
        "guess_lang": "python",
        "bbox": [
            147,
            822,
            378,
            835
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.shortcuts import render ",
        "guess_lang": "python",
        "bbox": [
            147,
            845,
            364,
            856
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "#表单 ",
        "guess_lang": "txt",
        "bbox": [
            147,
            866,
            189,
            876
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def search_form(request): ",
        "guess_lang": "txt",
        "bbox": [
            147,
            887,
            304,
            898
        ],
        "page_idx": 22
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return render(request, 'search_form.html')  \n#接收请求数据  \ndef search(request):  \n    request_encoding='utf-8'  \n    if 'q' in request.GET and request.GET['q']:  \n        message = '你搜索的内容为: ' + request.GET['q']  \n    else:  \n        message = '你提交了空表单'  \n    return HttpResponse(message)  \n在模板目录templates中添加search_form.html表单：  \n/HelloWorld/template/search_form.html文件代码：  \n<!DOCTYPE html>  \n<html>  \n<head>  \n<meta charset=\"utf-8\">  \n<title>菜鸟教程(runob.com)</title>  \n</head>  \n<body>  \n<form action=\"/search/\" method=\"get\">  \n    <input type=\"text\" name=\"q\">  \n    <input type=\"submit\" value=\"搜索\">  \n</form>  \n</body>  \n</html>  \nurls.py规则修改为如下形式：  \n/HelloWorld/HelloWorld URLs.py文件代码：  \nfrom django.conf.urls import url  \nfrom . import views,testdb,search  \nurlpatterns = [url(r'^hello/$', views.runoob),url(r'^testdb/$', testdb.testdb),url(r'^search-form/$', search.search_form),url(r'^search/$', search.search),]",
        "guess_lang": "python",
        "bbox": [
            147,
            91,
            468,
            858
        ],
        "page_idx": 23
    },
    {
        "type": "text",
        "text": "POST 方法",
        "text_level": 1,
        "bbox": [
            147,
            873,
            263,
            894
        ],
        "page_idx": 23
    },
    {
        "type": "text",
        "text": "上面我们使用了GET 方法，视图显示和请求处理分成两个函数处理。",
        "bbox": [
            146,
            95,
            502,
            105
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "提交数据时更常用POST 方法。我们下面使用该方法，并用一个URL和处理函数，同时显示视图和处理请求。",
        "bbox": [
            144,
            112,
            715,
            124
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "1. GET 方法实现（search.py）",
        "text_level": 1,
        "bbox": [
            147,
            131,
            309,
            143
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "python ",
        "bbox": [
            147,
            151,
            189,
            162
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "# 两个独立的函数： 一个显示表单， 一个处理请求",
        "bbox": [
            146,
            168,
            411,
            181
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "def search_form(request): # 显示表单 ",
        "bbox": [
            147,
            187,
            359,
            199
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "return render(request, 'search_form.html') ",
        "bbox": [
            159,
            206,
            396,
            218
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "def search(request): # 处理请求",
        "bbox": [
            147,
            243,
            327,
            255
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "if 'q' in request.GET and request.GET['q']: ",
        "bbox": [
            159,
            261,
            384,
            273
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "message $=$ '你搜索的内容为: ' $^ +$ request.GET['q'] ",
        "bbox": [
            171,
            280,
            431,
            292
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "else: ",
        "bbox": [
            159,
            299,
            189,
            309
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "message $=$ '你提交了空表单' ",
        "bbox": [
            171,
            317,
            324,
            328
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "return HttpResponse(message)",
        "bbox": [
            159,
            336,
            336,
            347
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "2. POST 方法实现（search2.py）",
        "text_level": 1,
        "bbox": [
            147,
            355,
            322,
            367
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "python ",
        "bbox": [
            147,
            373,
            189,
            384
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "# 单个函数同时处理表单显示和请求处理",
        "bbox": [
            147,
            391,
            364,
            401
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "def search_post(request): ",
        "bbox": [
            147,
            410,
            290,
            420
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "$\\mathsf { c t x } = \\left\\{ \\right\\}$ ",
        "bbox": [
            159,
            429,
            201,
            439
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "if request.POST: # 如果是POST 请求 （表单提交）",
        "bbox": [
            159,
            447,
            423,
            458
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "ctx['rlt'] $=$ request.POST['q'] ",
        "bbox": [
            171,
            466,
            322,
            476
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "return render(request, \"post.html\", ctx) # 总是返回模板",
        "bbox": [
            159,
            483,
            468,
            495
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "何时用 GET？何时用 POST？",
        "text_level": 1,
        "bbox": [
            147,
            502,
            297,
            514
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "适合 GET 的情况：",
        "text_level": 1,
        "bbox": [
            147,
            521,
            243,
            532
        ],
        "page_idx": 24
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "搜索功能（用户可能想收藏或分享搜索结果URL）",
            ". 筛选和排序（参数在URL中便于分享）",
            "幂等操作（不改变服务器状态的操作）"
        ],
        "bbox": [
            176,
            539,
            463,
            588
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "适合 POST 的情况：",
        "text_level": 1,
        "bbox": [
            147,
            595,
            250,
            606
        ],
        "page_idx": 24
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "登录/注册（包含敏感信息）",
            ". 表单提交（创建新数据）",
            ". 任何会改变服务器状态的操作",
            ". 大量数据提交 "
        ],
        "bbox": [
            176,
            614,
            364,
            680
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "最佳实践总结",
        "text_level": 1,
        "bbox": [
            147,
            688,
            221,
            699
        ],
        "page_idx": 24
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1. GET 完全可以用单函数处理显示和处理逻辑",
            "2. 原代码设计是过时的模式，现代 Django 开发不推荐这样分离",
            "3. 选择GET 还是 POST 取决于具体场景，而不是技术限制",
            "4. 单函数模式更简洁、更易维护"
        ],
        "bbox": [
            176,
            706,
            526,
            772
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "Request 对象",
        "text_level": 1,
        "bbox": [
            147,
            808,
            294,
            828
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "每个视图函数的第一个参数是一个HttpRequest 对象，就像下面这个runoob() 函数:",
        "bbox": [
            146,
            841,
            579,
            854
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "from django.http import HttpResponse ",
        "bbox": [
            147,
            860,
            349,
            872
        ],
        "page_idx": 24
    },
    {
        "type": "text",
        "text": "QueryDict 对象",
        "text_level": 1,
        "bbox": [
            147,
            102,
            315,
            122
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "在 HttpRequest 对象中, GET 和 POST 属性是 django.http.QueryDict 类的实例。QueryDict类似字典的自定义类，用来处理单键对应多值的情况。QueryDict实现所有标准的词典方法。还包括一些特有的方法：",
        "bbox": [
            144,
            135,
            554,
            184
        ],
        "page_idx": 25
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def runoob(request): return HttpResponse(\"Hello world\")",
        "guess_lang": "python",
        "bbox": [
            146,
            209,
            347,
            239
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "Django 视图",
        "text_level": 1,
        "bbox": [
            147,
            259,
            386,
            293
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "视图层 ",
        "text_level": 1,
        "bbox": [
            147,
            312,
            240,
            335
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "一个视图函数，简称视图，是一个简单的Python 函数，它接受Web 请求并且返回Web 响应。",
        "bbox": [
            146,
            347,
            635,
            359
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "响应可以是一个HTML 页面、一个404 错误页面、重定向页面、XML 文档、或者一张图片...",
        "bbox": [
            146,
            366,
            626,
            376
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "无论视图本身包含什么逻辑，都要返回响应。代码写在哪里都可以，只要在Python 目录下面，一般放在项目的views.py 文件中。",
        "bbox": [
            146,
            384,
            811,
            395
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "每个视图函数都负责返回一个HttpResponse 对象，对象中包含生成的响应。",
        "bbox": [
            146,
            403,
            542,
            414
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "视图层中有两个重要的对象：请求对象(request)与响应对象(HttpResponse)。",
        "bbox": [
            146,
            420,
            544,
            432
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "请求对象: HttpRequest 对象（简称 request 对象）",
        "bbox": [
            146,
            439,
            413,
            451
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "以下介绍几个常用的request 属性。",
        "bbox": [
            146,
            458,
            329,
            470
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "1、GET",
        "text_level": 1,
        "bbox": [
            147,
            488,
            228,
            504
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "数据类型是QueryDict，一个类似于字典的对象，包含HTTP GET 的所有参数。",
        "bbox": [
            146,
            520,
            556,
            531
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "有相同的键，就把所有的值放到对应的列表里。",
        "bbox": [
            146,
            539,
            391,
            549
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "取值格式：对象.方法。",
        "bbox": [
            146,
            557,
            265,
            568
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "get()：返回字符串，如果该键对应有多个值，取出该键的最后一个值。",
        "bbox": [
            146,
            576,
            509,
            587
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            147,
            595,
            174,
            605
        ],
        "page_idx": 25
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def runoob(request):\n    name = request.GET.get(\"name\")\n    return HttpResponse('姓名：{}''.format(name)) ",
        "guess_lang": "python",
        "bbox": [
            146,
            613,
            405,
            662
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "2、POST",
        "text_level": 1,
        "bbox": [
            146,
            678,
            245,
            695
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "数据类型是QueryDict，一个类似于字典的对象，包含HTTP POST 的所有参数。",
        "bbox": [
            146,
            711,
            563,
            722
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "常用于form 表单，form 表单里的标签name 属性对应参数的键，value 属性对应参数的值。",
        "bbox": [
            146,
            730,
            618,
            741
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "取值格式：对象.方法。",
        "bbox": [
            146,
            747,
            267,
            759
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "get()：返回字符串，如果该键对应有多个值，取出该键的最后一个值。",
        "bbox": [
            146,
            766,
            509,
            778
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            147,
            785,
            174,
            796
        ],
        "page_idx": 25
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def runoob(request):\n    name = request.POST.get(\"name\")\n    return HttpResponse('姓名：{}'.format(name)) ",
        "guess_lang": "python",
        "bbox": [
            146,
            804,
            405,
            853
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "3、body ",
        "text_level": 1,
        "bbox": [
            146,
            870,
            243,
            889
        ],
        "page_idx": 25
    },
    {
        "type": "text",
        "text": "数据类型是二进制字节流，是原生请求体里的参数内容，在HTTP 中用于POST，因为GET 没有请求体。",
        "bbox": [
            144,
            95,
            690,
            107
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "在HTTP 中不常用，而在处理非HTTP 形式的报文时非常有用，例如：二进制图片、XML、Json 等。",
        "bbox": [
            144,
            112,
            670,
            124
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            131,
            176,
            143
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "def runoob(request): name $=$ request.body print(name) return HttpResponse(\"菜鸟教程\") ",
        "bbox": [
            147,
            151,
            339,
            219
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "获取URL 中的路径部分，数据类型是字符串。",
        "bbox": [
            146,
            224,
            384,
            236
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            243,
            176,
            255
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "def runoob(request): name $=$ request.path print(name) return HttpResponse(\"菜鸟教程\") ",
        "bbox": [
            147,
            262,
            336,
            330
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "4、path ",
        "text_level": 1,
        "bbox": [
            146,
            346,
            240,
            365
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "获取URL 中的路径部分，数据类型是字符串。",
        "bbox": [
            146,
            379,
            384,
            391
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            397,
            176,
            409
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "def runoob(request): name $=$ request.path print(name) return HttpResponse(\"菜鸟教程\") ",
        "bbox": [
            147,
            414,
            337,
            483
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "5、method ",
        "text_level": 1,
        "bbox": [
            146,
            519,
            278,
            536
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "获取当前请求的方式，数据类型是字符串，且结果为大写。",
        "bbox": [
            146,
            551,
            450,
            563
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            570,
            176,
            581
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "def runoob(request): name $=$ request.method print(name) return HttpResponse(\"菜鸟教程\") ",
        "bbox": [
            147,
            588,
            339,
            656
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "响应对象：HttpResponse 对象",
        "text_level": 1,
        "bbox": [
            146,
            670,
            584,
            696
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "响应对象主要有三种形式：HttpResponse()、render()、redirect()。",
        "bbox": [
            146,
            705,
            492,
            718
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "HttpResponse(): 返回文本，参数为字符串，字符串中写文本内容。如果参数为字符串里含有html 标签，也可以渲染。",
        "bbox": [
            144,
            724,
            759,
            736
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "ender(): 返回文本，第一个参数为 request，第二个参数为字符串（页面名称），第三个参数为字典（可选参数，向页面传递的参数：",
        "bbox": [
            144,
            741,
            840,
            753
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "键为页面参数名，值为views参数名）。",
        "bbox": [
            146,
            760,
            354,
            772
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            778,
            176,
            791
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def runoob(request):\n    name = \"菜鸟教程\"\n    return render(request, \"runoob.html\", {\"name\": name}) ",
        "guess_lang": "python",
        "bbox": [
            147,
            797,
            438,
            847
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "redirect()：重定向，跳转新页面。参数为字符串，字符串中填写页面路径。一般用于form 表单提交后，跳转到新页面。",
        "bbox": [
            146,
            854,
            766,
            866
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            872,
            176,
            884
        ],
        "page_idx": 26
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def runoob(request): ",
        "guess_lang": "txt",
        "bbox": [
            147,
            891,
            260,
            903
        ],
        "page_idx": 26
    },
    {
        "type": "text",
        "text": "return redirect(\"/index/\") ",
        "bbox": [
            159,
            96,
            290,
            105
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "render 和 redirect 是在 HttpResponse 的基础上进行了封装：",
        "bbox": [
            146,
            112,
            460,
            124
        ],
        "page_idx": 27
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "render：底层返回的也是 HttpResponse 对象",
            "redirect：底层继承的是 HttpResponse 对象"
        ],
        "bbox": [
            176,
            131,
            443,
            162
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "Django 路由",
        "text_level": 1,
        "bbox": [
            149,
            183,
            384,
            217
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "路由简单的来说就是根据用户请求的URL 链接来判断对应的处理程序，并返回处理结果，也就是URL 与Django 的视图建立映射关系。",
        "bbox": [
            144,
            230,
            845,
            243
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "Django 路由在 urls.py 配置，urls.py 中的每一条配置对应相应的处理方法。",
        "bbox": [
            146,
            249,
            534,
            261
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "Path 设置 ",
        "text_level": 1,
        "bbox": [
            147,
            275,
            287,
            298
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "Django 2.2.x 之后的版本",
        "text_level": 1,
        "bbox": [
            146,
            311,
            282,
            322
        ],
        "page_idx": 27
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "path：用于普通路径，不需要自己手动添加正则首位限制符号，底层已经添加。",
            ". re_path：用于正则路径，需要自己手动添加正则首位限制符号。"
        ],
        "bbox": [
            176,
            329,
            616,
            359
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            146,
            366,
            174,
            376
        ],
        "page_idx": 27
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.url import re_path # 用re_path 需要引入\nurlpatterns = [\n    path('admin/', admin.site URLs),\n    path('index/', views.index), # 普通路径\n    re_path(r'^articles/[0-9]{4}]/$', views_articles), # 正则路径 ",
        "guess_lang": "python",
        "bbox": [
            144,
            384,
            465,
            489
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "正则路径中的分组",
        "text_level": 1,
        "bbox": [
            147,
            502,
            394,
            526
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "正则路径中的无名分组",
        "text_level": 1,
        "bbox": [
            147,
            544,
            378,
            563
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "无名分组按位置传参，一一对应。",
        "bbox": [
            146,
            577,
            322,
            590
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "views 中除了request，其他形参的数量要与urls 中的分组数量一致。",
        "bbox": [
            146,
            596,
            502,
            609
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "urls.py ",
        "bbox": [
            146,
            615,
            189,
            627
        ],
        "page_idx": 27
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "urlpatterns $=$ [ path('admin'/,admin.site URLs), re_path(\"^index/([0-9]{4})/$\",views.index), ] views.py from django.shortcuts import HttpResponse ",
        "bbox": [
            146,
            634,
            384,
            739
        ],
        "page_idx": 27
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def index(request, year):\n    print(year) # 一个形参代表路径中一个分组的内容，按顺序匹配\n    return HttpResponse('菜鸟教程')",
        "guess_lang": "python",
        "bbox": [
            146,
            764,
            494,
            813
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "正则路径中的有名分组",
        "text_level": 1,
        "bbox": [
            147,
            828,
            379,
            847
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "语法：",
        "bbox": [
            147,
            862,
            181,
            873
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "(?P<组名>正则表达式) ",
        "bbox": [
            147,
            881,
            270,
            892
        ],
        "page_idx": 27
    },
    {
        "type": "text",
        "text": "有名分组按关键字传参，与位置顺序无关。",
        "bbox": [
            144,
            95,
            366,
            105
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "views 中除了request，其他形参的数量要与urls 中的分组数量一致，并且views 中的形参名称要与urls 中的组名对应。",
        "bbox": [
            144,
            112,
            764,
            124
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "urls.py ",
        "bbox": [
            146,
            131,
            189,
            143
        ],
        "page_idx": 28
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "urlpatterns $=$ [path('admin／',admin.site.urls),re_path(\"^index/(?P[0-9]{4})/(?P[0-9]{2})/\\$\"，views.index),]views.pyfrom django.shortcuts import HttpResponsedef index(request, year, month):print(year,month)#一个形参代表路径中一个分组的内容,return HttpResponse('菜鸟教程') ",
        "bbox": [
            146,
            151,
            463,
            312
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "路由分发(include) ",
        "text_level": 1,
        "bbox": [
            146,
            326,
            339,
            346
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "存在问题：Django 项目里多个app目录共用一个urls 容易造成混淆，后期维护也不方便。",
        "bbox": [
            144,
            360,
            608,
            372
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "解决：使用路由分发（include），让每个app目录都单独拥有自己的urls。",
        "bbox": [
            144,
            379,
            532,
            390
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "步骤：",
        "bbox": [
            146,
            397,
            179,
            407
        ],
        "page_idx": 28
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "1、在每个 app 目录里都创建一个 urls.py 文件。",
            "2、在项目名称目录下的urls 文件里，统一将路径分发给各个app 目录。"
        ],
        "bbox": [
            176,
            416,
            578,
            445
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            146,
            451,
            174,
            464
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            144,
            470,
            329,
            482
        ],
        "page_idx": 28
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.url import path, include # 从 django.url 引入 include  \nurlpatterns = [  \n    path('admin/', admin.site URLs),  \n    path(\"app01/\", include(\"app01 URLs\")),  \n    path(\"app02/\", include(\"app02 URLs\")), ",
        "guess_lang": "python",
        "bbox": [
            144,
            489,
            495,
            575
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "反向解析",
        "text_level": 1,
        "bbox": [
            147,
            608,
            272,
            631
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "随着功能的增加，路由层的url 发生变化，就需要去更改对应的视图层和模板层的url，非常麻烦，不便维护。",
        "bbox": [
            144,
            644,
            707,
            655
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "这时我们可以利用反向解析，当路由层url 发生改变，在视图层和模板层动态反向解析出更改后的url，免去修改的操作。",
        "bbox": [
            144,
            662,
            766,
            673
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "反向解析一般用在模板中的超链接及视图中的重定向。",
        "bbox": [
            146,
            681,
            426,
            692
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "普通路径",
        "text_level": 1,
        "bbox": [
            147,
            709,
            240,
            727
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "在urls.py 中给路由起别名，name=\"路由别名\"。",
        "bbox": [
            144,
            741,
            396,
            753
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "path(\"login1/\", views.login, name=\"login\") ",
        "bbox": [
            146,
            760,
            364,
            772
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "在 views.py 中，从 django.urls 中引入 reverse，利用 reverse(\"路由别名\") 反向解析:",
        "bbox": [
            146,
            778,
            588,
            791
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "return redirect(reverse(\"login\")) ",
        "bbox": [
            146,
            797,
            312,
            809
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "在模板 templates 中的 HTML 文件中，利用 {% url \"路由别名\" %} 反向解析。",
        "bbox": [
            144,
            816,
            544,
            828
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "<form action=\"{% url 'login' %}\" method=\"post\"> ",
        "bbox": [
            146,
            835,
            400,
            846
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "正则路径（无名分组）",
        "text_level": 1,
        "bbox": [
            146,
            863,
            364,
            882
        ],
        "page_idx": 28
    },
    {
        "type": "text",
        "text": "在 urls.py 中给路由起别名，name=\"路由别名\"。",
        "bbox": [
            144,
            95,
            400,
            107
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "re_path(r\"^login/([0-9]{2})/$\", views.login, name=\"login\") ",
        "bbox": [
            144,
            112,
            440,
            124
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在 views.py 中，从 django.urls 中引入 reverse，利用 reverse(\"路由别名\"，args=(符合正则匹配的参数,)) 反向解析。",
        "bbox": [
            144,
            131,
            751,
            143
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "return redirect(reverse(\"login\",args=(10,))) ",
        "bbox": [
            144,
            151,
            369,
            162
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在模板 templates 中的 HTML 文件中利用 {% url \"路由别名\" 符合正则匹配的参数 %} 反向解析。",
        "bbox": [
            144,
            168,
            642,
            181
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "<form action=\"{% url 'login' 10 %}\" method=\"post\"> ",
        "bbox": [
            144,
            187,
            416,
            199
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "正则路径（有名分组）",
        "text_level": 1,
        "bbox": [
            146,
            215,
            366,
            234
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在 urls.py 中给路由起别名，name=\"路由别名\"。",
        "bbox": [
            144,
            249,
            400,
            261
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "re_path(r\"^login/(?P<year>[0-9]{4})/$\", views.login, name=\"login\") ",
        "bbox": [
            144,
            267,
            490,
            279
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在 views.py 中，从 django.urls 中引入 reverse，利用 reverse(\"路由别名\"，kwargs={\"分组名\":符合正则匹配的参数}) 反向解析。",
        "bbox": [
            144,
            286,
            813,
            298
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "return redirect(reverse(\"login\",kwargs={\"year\":3333})) ",
        "bbox": [
            146,
            304,
            428,
            316
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在模板 templates 中的 HTML 文件中，利用{% url \"路由别名\" 分组名=符合正则匹配的参数 %} 反向解析。",
        "bbox": [
            144,
            323,
            695,
            335
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "<form action=\"{% url 'login' year=3333 %}\" method=\"post\"> ",
        "bbox": [
            144,
            342,
            458,
            353
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "命名空间",
        "text_level": 1,
        "bbox": [
            146,
            386,
            273,
            409
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "命名空间（英语：Namespace）是表示标识符的可见范围。",
        "bbox": [
            144,
            420,
            455,
            432
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "一个标识符可在多个命名空间中定义，它在不同命名空间中的含义是互不相干的。",
        "bbox": [
            144,
            439,
            569,
            451
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "一个新的命名空间中可定义任何标识符，它们不会与任何重复的标识符发生冲突，因为重复的定义都处于其它命名空间中。",
        "bbox": [
            144,
            458,
            779,
            469
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "存在问题：路由别名 name 没有作用域，Django 在反向解析 URL 时，会在项目全局顺序搜索，当查找到第一个路由别名 name 指定",
        "bbox": [
            144,
            476,
            848,
            488
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "URL 时，立即返回。当在不同的 app 目录下的 urls 中定义相同的路由别名 name 时，可能会导致 URL 反向解析错误。",
        "bbox": [
            144,
            495,
            756,
            507
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "解决：使用命名空间。",
        "bbox": [
            144,
            514,
            262,
            526
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "普通路径",
        "text_level": 1,
        "bbox": [
            146,
            533,
            200,
            544
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "定义命名空间（include 里面是一个元组）格式如下：",
        "bbox": [
            144,
            551,
            420,
            562
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "include((\"app 名称：urls\"，\"app 名称\"))",
        "bbox": [
            144,
            570,
            354,
            582
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "实例：",
        "bbox": [
            146,
            588,
            179,
            599
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "path(\"app01/\", include((\"app01.urls\",\"app01\"))) ",
        "bbox": [
            144,
            606,
            393,
            618
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "path(\"app02/\", include((\"app02.urls\",\"app02\"))) ",
        "bbox": [
            144,
            625,
            393,
            637
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在 app01/urls.py 中起相同的路由别名。",
        "bbox": [
            144,
            644,
            349,
            655
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "path(\"login/\", views.login, name=\"login\") ",
        "bbox": [
            144,
            662,
            357,
            674
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "在views.py 中使用名称空间，语法格式如下：",
        "bbox": [
            144,
            681,
            383,
            692
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "reverse(\"app 名称：路由别名\")",
        "bbox": [
            144,
            699,
            310,
            711
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "实例：",
        "bbox": [
            146,
            718,
            179,
            728
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "return redirect(reverse(\"app01:login\") ",
        "bbox": [
            144,
            737,
            344,
            747
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "Django Admin 管理工具",
        "text_level": 1,
        "bbox": [
            147,
            768,
            610,
            802
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "Django 提供了基于web 的管理工具。",
        "text_level": 1,
        "bbox": [
            146,
            822,
            532,
            843
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "Django 自动管理工具是 django.contrib 的一部分。你可以在项目的 settings.py 中的 INSTALLED_APPS 看到它：",
        "bbox": [
            144,
            856,
            727,
            869
        ],
        "page_idx": 29
    },
    {
        "type": "text",
        "text": "/HelloWorld/HelloWorld/settings.py 文件代码：",
        "bbox": [
            144,
            875,
            386,
            887
        ],
        "page_idx": 29
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "INSTALLED_APPS = ( ",
        "guess_lang": "txt",
        "bbox": [
            147,
            95,
            270,
            105
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib_admin', ",
        "guess_lang": "txt",
        "bbox": [
            161,
            114,
            280,
            124
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.auth', ",
        "guess_lang": "txt",
        "bbox": [
            161,
            131,
            272,
            143
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.contenttypes', ",
        "guess_lang": "python",
        "bbox": [
            161,
            151,
            315,
            162
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.authenticated', ",
        "guess_lang": "javascript",
        "bbox": [
            161,
            168,
            295,
            180
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.messages', ",
        "guess_lang": "javascript",
        "bbox": [
            161,
            187,
            302,
            199
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib(staticfiles',",
        "guess_lang": "javascript",
        "bbox": [
            161,
            206,
            297,
            217
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": ")django.contrib 是一套庞大的功能集，它是 Django 基本代码的组成部分。",
        "bbox": [
            146,
            224,
            524,
            236
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "激活管理工具",
        "text_level": 1,
        "bbox": [
            147,
            252,
            287,
            271
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "通常我们在生成项目时会在urls.py 中自动设置好，我们只需去掉注释即可。",
        "bbox": [
            146,
            286,
            537,
            298
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "配置项如下所示：",
        "bbox": [
            147,
            304,
            238,
            316
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "/HelloWorld/HelloWorld/url.py文件代码：",
        "guess_lang": "txt",
        "bbox": [
            146,
            323,
            366,
            335
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "urls.py ",
        "guess_lang": "txt",
        "bbox": [
            147,
            342,
            196,
            353
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.conf.urls import url ",
        "guess_lang": "python",
        "bbox": [
            147,
            360,
            312,
            370
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "from django.contrib import admin ",
        "guess_lang": "python",
        "bbox": [
            147,
            378,
            322,
            390
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "urlpatterns $=$ [ ",
        "bbox": [
            147,
            397,
            223,
            409
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "url(r'^admin\"', admin.site.url), ",
        "guess_lang": "javascript",
        "bbox": [
            161,
            414,
            319,
            426
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "] ",
        "guess_lang": "txt",
        "bbox": [
            147,
            435,
            156,
            445
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "当这一切都配置好后，Django 管理工具就可以运行了。",
        "bbox": [
            147,
            451,
            431,
            464
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "你可以通过命令 python manage.py createsuperuser 来创建超级用户，如下所示：",
        "bbox": [
            146,
            470,
            584,
            483
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "python manage.py createsuperuser ",
        "guess_lang": "batch",
        "bbox": [
            147,
            489,
            347,
            502
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Username (leave blank to use 'root'): admin ",
        "guess_lang": "txt",
        "bbox": [
            147,
            508,
            376,
            520
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Email address: admin@runoob.com ",
        "guess_lang": "txt",
        "bbox": [
            147,
            527,
            337,
            538
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Password: ",
        "guess_lang": "txt",
        "bbox": [
            147,
            546,
            205,
            556
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Password (again): ",
        "guess_lang": "txt",
        "bbox": [
            147,
            564,
            245,
            576
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "Superuser created successfully. ",
        "guess_lang": "txt",
        "bbox": [
            147,
            582,
            317,
            594
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "[root@solar HelloWorld]# ",
        "guess_lang": "txt",
        "bbox": [
            147,
            601,
            282,
            613
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "复杂模型",
        "text_level": 1,
        "bbox": [
            147,
            629,
            242,
            646
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "管理页面的功能强大，完全有能力处理更加复杂的数据模型。",
        "bbox": [
            146,
            662,
            462,
            674
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "django-admin.py startproject app01 ",
        "guess_lang": "txt",
        "bbox": [
            147,
            681,
            336,
            693
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "接下来在 settings.py 中找到 INSTALLED_APPS 这一项，如下：",
        "bbox": [
            147,
            699,
            478,
            711
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "INSTALLED_APPS = ( ",
        "guess_lang": "txt",
        "bbox": [
            147,
            718,
            270,
            728
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib_admin', ",
        "guess_lang": "txt",
        "bbox": [
            161,
            737,
            280,
            747
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.auth', ",
        "guess_lang": "txt",
        "bbox": [
            161,
            755,
            272,
            766
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.contenttypes', ",
        "guess_lang": "python",
        "bbox": [
            161,
            772,
            315,
            785
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contribsessions', ",
        "guess_lang": "javascript",
        "bbox": [
            161,
            791,
            295,
            804
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib.messages', ",
        "guess_lang": "javascript",
        "bbox": [
            161,
            810,
            302,
            822
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'django.contrib(staticfiles',",
        "guess_lang": "javascript",
        "bbox": [
            161,
            829,
            297,
            840
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "'testmodel', # 添加此项 ",
        "guess_lang": "txt",
        "bbox": [
            161,
            847,
            329,
            859
        ],
        "page_idx": 30
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "）",
        "guess_lang": "txt",
        "bbox": [
            147,
            866,
            156,
            878
        ],
        "page_idx": 30
    },
    {
        "type": "text",
        "text": "建表",
        "text_level": 1,
        "bbox": [
            147,
            101,
            200,
            120
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "先在 TestModel/models.py 中增加一个更复杂的数据模型：",
        "bbox": [
            147,
            162,
            450,
            175
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "HelloWorld/TestModel/models.py: 文件代码：",
        "bbox": [
            147,
            181,
            381,
            193
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "from django.db import models ",
        "bbox": [
            147,
            199,
            307,
            212
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "# Create your models here. ",
        "bbox": [
            147,
            237,
            295,
            249
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "class Test(models.Model): ",
        "bbox": [
            147,
            255,
            287,
            267
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "name = models.CharField(max_length=20) ",
        "guess_lang": "python",
        "bbox": [
            161,
            273,
            388,
            286
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "class Contact(models.Model): ",
        "bbox": [
            147,
            311,
            307,
            323
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "name $=$ models.CharField(max_length=200) ",
        "bbox": [
            161,
            329,
            401,
            341
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "age $=$ models.IntegerField(default=0) ",
        "bbox": [
            161,
            348,
            366,
            360
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "email = models.EmailField() ",
        "guess_lang": "txt",
        "bbox": [
            161,
            367,
            312,
            378
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def __unicode__(self): ",
        "guess_lang": "python",
        "bbox": [
            161,
            385,
            280,
            397
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return self.name ",
        "guess_lang": "lua",
        "bbox": [
            173,
            404,
            263,
            414
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "class Tag(models.Model): ",
        "bbox": [
            147,
            439,
            285,
            451
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "contact = models.FireignKey(Contact, on_delete=models.CASCADE,) ",
        "guess_lang": "python",
        "bbox": [
            161,
            458,
            531,
            470
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "name $=$ models.CharField(max_length=50) ",
        "bbox": [
            161,
            478,
            396,
            489
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "def __unicode__(self): ",
        "guess_lang": "python",
        "bbox": [
            161,
            495,
            280,
            508
        ],
        "page_idx": 31
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "return self.name ",
        "guess_lang": "lua",
        "bbox": [
            173,
            514,
            263,
            525
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "这里有两个表。Tag 以 Contact 为外部键。一个 Contact 可以对应多个 Tag。",
        "bbox": [
            147,
            533,
            539,
            546
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "我们还可以看到许多在之前没有见过的属性类型，比如 IntegerField 用于存储整数。",
        "bbox": [
            146,
            570,
            578,
            583
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "用 django 创建表",
        "text_level": 1,
        "bbox": [
            147,
            611,
            337,
            633
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "$ python manage.py migrate # 创建表结构",
        "bbox": [
            146,
            674,
            379,
            686
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "python manage.py makemigrations testmodel 让模型知道我们的表有变更",
        "bbox": [
            146,
            692,
            536,
            703
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "python manage.py migrate TestModel 创建表结构",
        "bbox": [
            147,
            709,
            408,
            722
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "接下来我们在 app01 项目里添加 views.py 和 models.py 文件，app01 项目目录结构：",
        "bbox": [
            146,
            728,
            586,
            741
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "app01 ",
        "bbox": [
            147,
            747,
            184,
            759
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "|-- app01 ",
        "bbox": [
            147,
            766,
            196,
            777
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- __init__.py",
        "bbox": [
            147,
            785,
            236,
            797
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- __pycache__",
        "bbox": [
            147,
            803,
            248,
            815
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- asgi.py ",
        "bbox": [
            147,
            822,
            216,
            833
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- migrations ",
        "bbox": [
            147,
            840,
            233,
            852
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- models.py",
        "bbox": [
            147,
            859,
            233,
            870
        ],
        "page_idx": 31
    },
    {
        "type": "text",
        "text": "| |-- settings.py",
        "bbox": [
            147,
            877,
            235,
            889
        ],
        "page_idx": 31
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "| |-- urls.py",
            "| |-- views.py",
            "| `-- wsgi.py"
        ],
        "bbox": [
            146,
            95,
            223,
            143
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "显示模型",
        "text_level": 1,
        "bbox": [
            147,
            192,
            245,
            211
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "在 TestModel/admin.py 注册多个模型并显示：",
        "bbox": [
            146,
            252,
            490,
            268
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "HelloWorld/TestModel/admin.py: 文件代码：",
        "bbox": [
            146,
            290,
            376,
            302
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            146,
            309,
            322,
            319
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "from TestModel.models import Test,Contact,Tag ",
        "bbox": [
            146,
            328,
            400,
            338
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "# Register your models here. ",
        "bbox": [
            146,
            365,
            302,
            376
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "admin.site.register([Test, Contact, Tag]) ",
        "bbox": [
            146,
            382,
            354,
            395
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "每个栏也可以定义自己的格式。",
        "text_level": 1,
        "bbox": [
            146,
            424,
            465,
            444
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "修改 TestModel/admin.py 为：",
        "bbox": [
            146,
            486,
            302,
            498
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "HelloWorld/TestModel/admin.py: 文件代码：",
        "bbox": [
            146,
            523,
            374,
            535
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            146,
            542,
            322,
            552
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "from TestModel.models import Test,Contact,Tag ",
        "bbox": [
            146,
            560,
            400,
            571
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "# Register your models here. ",
        "bbox": [
            146,
            596,
            302,
            608
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "class ContactAdmin(admin.ModelAdmin): ",
        "bbox": [
            146,
            615,
            366,
            627
        ],
        "page_idx": 32
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "fieldsets = (\n    ['Main', {\n        'fields': ('name', 'email'),\n    }],\n    ['Advance', {\n        'classes': ('collapse'), # CSS\n        'fields': ('age'), }\n    }]) ",
        "guess_lang": "python",
        "bbox": [
            146,
            634,
            341,
            793
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "admin.site.register(Contact, ContactAdmin) ",
        "bbox": [
            146,
            820,
            376,
            831
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "admin.site.register([Test, Tag]) ",
        "bbox": [
            146,
            838,
            309,
            848
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "上面的栏目分为了Main 和Advance 两部分。classes 说明它所在的部分的CSS 格式。这里让Advance 部分隐藏：",
        "bbox": [
            146,
            856,
            739,
            868
        ],
        "page_idx": 32
    },
    {
        "type": "text",
        "text": "内联(Inline)显示",
        "text_level": 1,
        "bbox": [
            147,
            101,
            329,
            122
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "上面的Contact 是Tag 的外部键，所以有外部参考的关系。",
        "bbox": [
            146,
            162,
            450,
            174
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "而在默认的页面显示中，将两者分离开来，无法体现出两者的从属关系。我们可以使用内联显示，让 Tag 附加在Contact 的编辑页面上显示。",
        "bbox": [
            144,
            181,
            847,
            210
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "修改 TestModel/admin.py：",
        "bbox": [
            146,
            219,
            285,
            229
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "HelloWorld/TestModel/admin.py: 文件代码：",
        "bbox": [
            146,
            237,
            386,
            248
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            146,
            255,
            322,
            265
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "from TestModel.models import Test,Contact,Tag ",
        "bbox": [
            146,
            274,
            400,
            284
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "# Register your models here. ",
        "bbox": [
            146,
            311,
            300,
            321
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "class TagInline(admin.TabularInline): ",
        "bbox": [
            146,
            329,
            339,
            338
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "model = Tag ",
        "bbox": [
            161,
            349,
            226,
            359
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "class ContactAdmin(admin.ModelAdmin): ",
        "bbox": [
            146,
            385,
            366,
            395
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "inlines $=$ [TagInline] # Inline ",
        "bbox": [
            161,
            404,
            307,
            414
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "fieldsets = ( ",
        "bbox": [
            161,
            422,
            225,
            432
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "['Main',{ ",
        "bbox": [
            174,
            441,
            216,
            451
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "'fields':('name','email'), ",
        "bbox": [
            186,
            460,
            305,
            470
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "}], ",
        "bbox": [
            174,
            479,
            184,
            488
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "['Advance',{ ",
        "bbox": [
            174,
            495,
            236,
            506
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "'classes': ('collapse',), ",
        "bbox": [
            186,
            514,
            300,
            526
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "'fields': ('age',), ",
        "bbox": [
            188,
            533,
            267,
            544
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "}] ",
        "bbox": [
            174,
            552,
            184,
            562
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": ") ",
        "bbox": [
            157,
            589,
            168,
            599
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "admin.site.register(Contact, ContactAdmin) ",
        "bbox": [
            146,
            626,
            374,
            637
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "admin.site.register([Test]) ",
        "bbox": [
            147,
            645,
            282,
            655
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "自定义该页面的显示",
        "text_level": 1,
        "bbox": [
            147,
            686,
            363,
            705
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "比如在列表中显示更多的栏目，只需要在 ContactAdmin 中增加 list_display 属性:",
        "bbox": [
            146,
            747,
            568,
            759
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "HelloWorld/TestModel/admin.py: 文件代码：",
        "bbox": [
            146,
            785,
            374,
            795
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            146,
            804,
            322,
            813
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "from TestModel.models import Test,Contact,Tag ",
        "bbox": [
            146,
            822,
            398,
            832
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "# Register your models here. ",
        "bbox": [
            146,
            859,
            300,
            869
        ],
        "page_idx": 33
    },
    {
        "type": "text",
        "text": "class TagInline(admin.TabularInline): ",
        "bbox": [
            146,
            877,
            339,
            888
        ],
        "page_idx": 33
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "model $=$ Tag   \nclass ContactAdmin(admin.ModelAdmin): list_display $\\equiv$ ('name','age'，'email')#list inlines $=$ [TagInline] # Inline fieldsets $=$ ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes':('collapse'), 'fields':('age'), }]}   \n）   \nadmin.site.register(Contact,ContactAdmin)   \nadmin.site.register([Test]) ",
        "bbox": [
            147,
            93,
            374,
            423
        ],
        "page_idx": 34
    },
    {
        "type": "text",
        "text": "搜索功能",
        "text_level": 1,
        "bbox": [
            147,
            451,
            247,
            470
        ],
        "page_idx": 34
    },
    {
        "type": "text",
        "text": "search_fields 为该列表页增加搜索栏：",
        "bbox": [
            147,
            513,
            347,
            525
        ],
        "page_idx": 34
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "HelloWorld/TestModel/admin.py:文件代码：  \nfrom django.contrib import admin  \nfrom TestModel.models import Test,Contact,Tag",
        "guess_lang": "python",
        "bbox": [
            147,
            550,
            400,
            600
        ],
        "page_idx": 34
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "# Register your models here.   \nclass TagInline(admin.TabularInline): model $=$ Tag   \nclass ContactAdmin(admin.ModelAdmin): list_display $\\equiv$ ('name','age'，'email') #list search_fields $=$ ('name') inlines $=$ [TagInline] # Inline fieldsets $=$ ( ['Main',{ 'fields':('name','email'), }], ['Advance',{ 'classes':('collapse'), 'fields':('age'), ",
        "bbox": [
            147,
            623,
            371,
            895
        ],
        "page_idx": 34
    },
    {
        "type": "text",
        "text": "}] ",
        "bbox": [
            174,
            96,
            184,
            105
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": ") ",
        "bbox": [
            161,
            131,
            168,
            142
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "admin.site.register(Contact, ContactAdmin) ",
        "bbox": [
            147,
            168,
            376,
            180
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "admin.site.register([Test]) ",
        "bbox": [
            147,
            187,
            282,
            198
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "在 django2.0 后，定义外键和一对一关系的时候需要加 on_delete 选项，此参数为了避免两个表里的数据不一致问题，不然会报错：",
        "bbox": [
            146,
            206,
            840,
            218
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "TypeError: __init__() missing 1 required positional argument: 'on_delete'。 ",
        "bbox": [
            147,
            225,
            561,
            236
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "owner=models.ForeignKey(UserProfile,on_delete=models.CASCADE) --在老版本这个参数（models.CASCADE）是默认值参数",
        "bbox": [
            146,
            243,
            815,
            254
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "说明：on_delete 有 CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET() 五个可选择的值。",
        "bbox": [
            146,
            262,
            677,
            273
        ],
        "page_idx": 35
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "CASCADE：此值设置，是级联删除。",
            ". PROTECT：此值设置，是会报完整性错误。",
            ". SET_NULL：此值设置，会把外键设置为null，前提是允许为null。 ",
            ". SET_DEFAULT：此值设置，会把设置为外键的默认值。 ",
            "SET()：此值设置，会调用外面的值，可以是一个函数。一般情况下使用CASCADE 就可以了。"
        ],
        "bbox": [
            176,
            280,
            700,
            366
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "如果你之前还未创建表结构，可使用以下命令创建：",
        "bbox": [
            147,
            373,
            416,
            384
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "$ python manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更",
        "bbox": [
            146,
            391,
            648,
            403
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "$ python manage.py migrate TestModel # 创建表结构",
        "bbox": [
            147,
            410,
            435,
            422
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "数据库添加",
        "text_level": 1,
        "bbox": [
            147,
            451,
            267,
            470
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "规则配置：",
        "bbox": [
            147,
            513,
            203,
            524
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "app01/urls.py: 文件代码：",
        "bbox": [
            146,
            550,
            280,
            561
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from django.contrib import admin ",
        "bbox": [
            147,
            568,
            322,
            579
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from django.urls import path ",
        "bbox": [
            147,
            587,
            297,
            598
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from . import views ",
        "bbox": [
            147,
            606,
            250,
            615
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "urlpatterns = [ ",
        "bbox": [
            147,
            643,
            223,
            653
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "path('add_book/', views.add_book), ",
        "bbox": [
            161,
            661,
            347,
            671
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "] ",
        "bbox": [
            147,
            681,
            156,
            690
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "方式一：模型类实例化对象",
        "bbox": [
            147,
            697,
            292,
            709
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "需从 app 目录引入 models.py 文件：",
        "bbox": [
            147,
            734,
            334,
            747
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from app 目录 import models ",
        "bbox": [
            146,
            772,
            300,
            783
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "并且实例化对象后要执行 对象.save() 才能在数据库中新增成功。",
        "bbox": [
            146,
            791,
            480,
            802
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "app01/views.py: 文件代码：",
        "bbox": [
            146,
            828,
            290,
            839
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from django.shortcuts import render,HttpResponse ",
        "bbox": [
            146,
            846,
            413,
            857
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "from app01 import models ",
        "bbox": [
            147,
            865,
            287,
            876
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            147,
            883,
            272,
            895
        ],
        "page_idx": 35
    },
    {
        "type": "text",
        "text": "book $=$ models.Book(title $! = \"$ 菜鸟教程\",price=300,publish=\"菜鸟出版社\",pub_date=\"2008-8-8\") ",
        "bbox": [
            156,
            93,
            640,
            105
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "book.save() ",
        "bbox": [
            159,
            114,
            225,
            124
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "return HttpResponse( $\" < p >$ 数据添加成功！ $< / { \\mathsf { p } } > \"$ )",
        "bbox": [
            159,
            131,
            415,
            143
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "方式二：通过 ORM 提供的 objects 提供的方法create 来实现（推荐）",
        "bbox": [
            147,
            149,
            505,
            161
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "app01/views.py: 文件代码：",
        "bbox": [
            147,
            187,
            290,
            199
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "from django.shortcuts import render,HttpResponse ",
        "bbox": [
            147,
            206,
            413,
            217
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "from app01 import models ",
        "bbox": [
            147,
            225,
            287,
            235
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            147,
            243,
            272,
            254
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.create(title=\"如来神掌\",price=200,publish=\"功夫出版社\",pub_date=\"2010-10-10\") ",
        "bbox": [
            159,
            262,
            739,
            273
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "print(books, type(books)) # Book object (18) ",
        "bbox": [
            159,
            280,
            393,
            291
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "return HttpResponse(\"<p>数据添加成功！</p>\")",
        "bbox": [
            159,
            299,
            413,
            310
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "查找",
        "text_level": 1,
        "bbox": [
            147,
            338,
            200,
            357
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "使用 all() 方法来查询所有内容。",
        "bbox": [
            144,
            400,
            388,
            414
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据，类似于 list，里面放的是一个个模型类的对象，可用索引下标取出模型类的对象。",
        "bbox": [
            144,
            418,
            847,
            451
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "（for I in books）",
        "bbox": [
            159,
            456,
            268,
            470
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.all() ",
        "bbox": [
            159,
            476,
            339,
            487
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "print(books,type(books)) # QuerySet 类型，类似于 list，访问 url 时数据显示在命令行窗口中。",
        "bbox": [
            159,
            494,
            642,
            505
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "查询符合条件的数据。",
        "text_level": 1,
        "bbox": [
            146,
            535,
            371,
            555
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "filter() 方法",
        "bbox": [
            146,
            598,
            208,
            608
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据，类似于 list，里面放的是满足条件的模型类的对象，可用索引下标取出模型类的对象。",
        "bbox": [
            144,
            615,
            746,
            627
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "pk=3 的意思是主键 primary key=3，相当于 id=3。",
        "bbox": [
            146,
            653,
            403,
            664
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "因为 id 在 pycharm 里有特殊含义，是看内存地址的内置函数 id()，因此用 pk。",
        "bbox": [
            144,
            690,
            549,
            702
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.filter(pk=5) ",
        "bbox": [
            157,
            726,
            376,
            738
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "print(books) ",
        "bbox": [
            159,
            746,
            226,
            756
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "print(\"//////////////////////////////////////\") ",
        "bbox": [
            159,
            764,
            327,
            775
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.filter(publish='菜鸟出版社', price=300) ",
        "bbox": [
            159,
            783,
            515,
            794
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "print(books, type(books)) # QuerySet 类型，类似于 list。",
        "bbox": [
            159,
            802,
            453,
            813
        ],
        "page_idx": 36
    },
    {
        "type": "text",
        "text": "查询不符合条件的数据。",
        "text_level": 1,
        "bbox": [
            147,
            101,
            394,
            120
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "exclude() 方法",
        "bbox": [
            144,
            162,
            226,
            174
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据，类似于 list，里面放的是不满足条件的模型类的对象，可用索引下标取出模型类的对象。",
        "bbox": [
            144,
            181,
            757,
            193
        ],
        "page_idx": 37
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "books $=$ models.Book.objectsexclude(pk=5) ",
        "bbox": [
            159,
            199,
            394,
            211
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "print(books) ",
        "bbox": [
            159,
            218,
            226,
            229
        ],
        "page_idx": 37
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "print(\"|||||||||||||||||||||||||||||||||\") ",
        "guess_lang": "txt",
        "bbox": [
            159,
            237,
            327,
            248
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.exclude(publish='菜鸟出版社', price=300) ",
        "bbox": [
            159,
            255,
            534,
            267
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "print(books, type(books)) # QuerySet 类型，类似于 list。",
        "bbox": [
            159,
            274,
            455,
            286
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "get() 方法",
        "text_level": 1,
        "bbox": [
            146,
            315,
            260,
            336
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "用于查询符合条件的返回模型类的对象符合条件的对象只能为一个，如果符合筛选条件的对象超过了一个或者没有一个都会抛出错误。",
        "bbox": [
            144,
            376,
            840,
            388
        ],
        "page_idx": 37
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "books $=$ models.Book.objects.get(pk=5) ",
        "bbox": [
            159,
            395,
            371,
            407
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.get ${ \\tt p k } { = } 1 8$ ) # 报错，没有符合条件的对象",
        "bbox": [
            159,
            414,
            534,
            425
        ],
        "page_idx": 37
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "books = models.Book.objects.get(price=200) # 报错，符合条件的对象超过一个 ",
        "guess_lang": "txt",
        "bbox": [
            159,
            432,
            576,
            444
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "print(books, type(books)) # 模型类的对象 ",
        "bbox": [
            159,
            451,
            381,
            463
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "排序 ",
        "text_level": 1,
        "bbox": [
            146,
            492,
            198,
            511
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "order_by() ",
        "bbox": [
            144,
            554,
            205,
            565
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet类型数据，类似于list，里面放的是排序后的模型类的对象，可用索引下标取出模型类的对象。",
        "bbox": [
            144,
            571,
            732,
            583
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "注意：",
        "bbox": [
            146,
            609,
            179,
            620
        ],
        "page_idx": 37
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a、参数的字段名要加引号。",
            "b、降序为在字段前面加个负号-。"
        ],
        "bbox": [
            146,
            646,
            322,
            676
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.order_by(\"price\") # 查询所有，按照价格升序排列",
        "bbox": [
            159,
            684,
            576,
            695
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.order_by(\"-price\") # 查询所有，按照价格降序排列",
        "bbox": [
            159,
            702,
            579,
            714
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "对查询结果进行反转。",
        "text_level": 1,
        "bbox": [
            146,
            744,
            369,
            762
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "reverse() 方法",
        "bbox": [
            144,
            804,
            226,
            816
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "返回的是 QuerySe t类型数据，类似于 list，里面放的是反转后的模型类的对象，可用索引下标取出模型类的对象。",
        "bbox": [
            144,
            822,
            736,
            835
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "# 按照价格升序排列：降序再反转 ",
        "bbox": [
            159,
            860,
            339,
            872
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.order_by(\"-price\").reverse()",
        "bbox": [
            159,
            879,
            463,
            890
        ],
        "page_idx": 37
    },
    {
        "type": "text",
        "text": "查询数据的数量",
        "text_level": 1,
        "bbox": [
            147,
            101,
            317,
            120
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "返回的数据是整数。",
        "text_level": 1,
        "bbox": [
            146,
            161,
            302,
            177
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "count() 方法",
        "bbox": [
            147,
            181,
            215,
            193
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.count() # 查询所有数据的数量",
        "bbox": [
            159,
            199,
            478,
            212
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.filter(price=200).count() # 查询符合条件数据的数量",
        "bbox": [
            159,
            218,
            586,
            231
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "first() 方法返回第一条数据返回的数据是模型类的对象也可以用索引下标 [0]。",
        "bbox": [
            146,
            237,
            544,
            249
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.first() # 返回所有数据的第一条数据",
        "bbox": [
            159,
            255,
            502,
            267
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "last() 方法返回最后一条数据返回的数据是模型类的对象不能用索引下标[-1]，ORM 没有逆序索引。",
        "bbox": [
            146,
            274,
            660,
            286
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.last() # 返回所有数据的最后一条数据",
        "bbox": [
            159,
            292,
            514,
            304
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "是否存在数据",
        "text_level": 1,
        "bbox": [
            147,
            334,
            294,
            353
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "exists() 方法用于判断查询的结果 QuerySet 列表里是否有数据。",
        "bbox": [
            146,
            395,
            477,
            407
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "返回的数据类型是布尔，有为 true，没有为 false。",
        "bbox": [
            146,
            432,
            406,
            444
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "注意：判断的数据类型只能为 QuerySet 类型数据，不能为整型和模型类的对象。",
        "bbox": [
            146,
            469,
            564,
            481
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.exists()",
        "bbox": [
            159,
            506,
            359,
            518
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "# 报错，判断的数据类型只能为QuerySet类型数据，不能为整型",
        "bbox": [
            159,
            525,
            500,
            537
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.count().exists()",
        "bbox": [
            159,
            543,
            400,
            555
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "# 报错，判断的数据类型只能为QuerySet类型数据，不能为模型类对象",
        "bbox": [
            159,
            562,
            534,
            574
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.first().exists()",
        "bbox": [
            159,
            581,
            389,
            592
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "查询数据",
        "text_level": 1,
        "bbox": [
            147,
            621,
            247,
            640
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "values() 方法用于查询部分字段的数据。",
        "bbox": [
            146,
            684,
            354,
            695
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据，类似于 list，里面不是模型类的对象，而是一个可迭代的字典序列，字典里的键是字段，值是数据。",
        "bbox": [
            144,
            720,
            816,
            732
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "注意：",
        "bbox": [
            147,
            758,
            179,
            769
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "参数的字段名要加引号",
        "bbox": [
            147,
            795,
            268,
            806
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "想要字段名和数据用 values",
        "bbox": [
            147,
            814,
            294,
            825
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.values(\"pk\",\"price\") ",
        "bbox": [
            159,
            832,
            420,
            843
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "print(books[0][\"price\"],type(books)) # 得到的是第一条记录的 price 字段的数据",
        "bbox": [
            159,
            851,
            564,
            862
        ],
        "page_idx": 38
    },
    {
        "type": "text",
        "text": "查询数据 2",
        "text_level": 1,
        "bbox": [
            147,
            101,
            265,
            120
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "values_list() 方法用于查询部分字段的数据。",
        "bbox": [
            144,
            162,
            381,
            174
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据，类似于 list，里面不是模型类的对象，而是一个个元组，元组里放的是查询字段对应的数据。",
        "bbox": [
            144,
            181,
            781,
            193
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "注意：",
        "bbox": [
            146,
            199,
            181,
            211
        ],
        "page_idx": 39
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "参数的字段名要加引号",
            ". 只想要数据用 values_list"
        ],
        "bbox": [
            176,
            218,
            339,
            247
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.values_list(\"price\",\"publish\") ",
        "bbox": [
            159,
            255,
            465,
            266
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "print(books) ",
        "bbox": [
            159,
            274,
            228,
            285
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "print(books[0][0],type(books)) # 得到的是第一条记录的 price 字段的数据",
        "bbox": [
            159,
            292,
            539,
            304
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "去重",
        "text_level": 1,
        "bbox": [
            147,
            334,
            200,
            351
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "distinct() 方法用于对数据进行去重。",
        "bbox": [
            144,
            395,
            339,
            407
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "返回的是 QuerySet 类型数据。",
        "bbox": [
            146,
            414,
            307,
            425
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "注意：",
        "bbox": [
            146,
            432,
            181,
            444
        ],
        "page_idx": 39
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            ". 对模型类的对象去重没有意义，因为每个对象都是一个不一样的存在。",
            "distinct() 一般是联合 values 或者 values_list 使用。"
        ],
        "bbox": [
            176,
            451,
            569,
            481
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            146,
            506,
            273,
            518
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "# 查询一共有多少个出版社",
        "bbox": [
            159,
            525,
            304,
            536
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.values_list(\"publish\").distinct() # 对模型类的对象去重没有意义，因为每个对象都是一个不一样的存在。",
        "bbox": [
            157,
            543,
            847,
            555
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.distinct() ",
        "bbox": [
            159,
            562,
            366,
            573
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "基于双下划线的模糊查询",
        "text_level": 1,
        "bbox": [
            146,
            602,
            411,
            623
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "filter() 方法（exclude 同理）。",
        "bbox": [
            144,
            665,
            307,
            676
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "注意：filter 中运算符号只能使用等于号 $=$ ，不能使用大于号 $>$ ，小于号 $<$ ，等等其他符号。",
        "bbox": [
            144,
            683,
            618,
            695
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_in 用于读取区间， $=$ 号后面为列表 。",
        "bbox": [
            146,
            702,
            347,
            713
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_gt 大于号 ， $=$ 号后面为数字。__gte 大于等于， $=$ 号后面为数字。__lt,__lte",
        "bbox": [
            146,
            720,
            554,
            733
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_range 在 ... 之间，左闭右闭区间， $=$ 号后面为两个元素的列表。",
        "bbox": [
            146,
            739,
            489,
            751
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_contains 包含， $=$ 号后面为字符串。",
        "bbox": [
            146,
            758,
            347,
            769
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_icontains 不区分大小写的包含， $=$ 号后面为字符串。",
        "bbox": [
            146,
            776,
            433,
            788
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_startswith 以指定字符开头， $=$ 号后面为字符串。",
        "bbox": [
            146,
            795,
            413,
            806
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_endswith 以指定字符结尾， $=$ 号后面为字符串。",
        "bbox": [
            146,
            813,
            410,
            825
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "_year 是 DateField 数据类型的年份， $=$ 号后面为数字。__day/__month ",
        "bbox": [
            146,
            832,
            527,
            844
        ],
        "page_idx": 39
    },
    {
        "type": "text",
        "text": "删除 ",
        "text_level": 1,
        "bbox": [
            147,
            101,
            200,
            120
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "方式一：使用模型类的 对象.delete()。",
        "bbox": [
            144,
            162,
            344,
            174
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "返回值：元组，第一个元素为受影响的行数。",
        "bbox": [
            146,
            181,
            379,
            192
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "books=models.Book.objects.filter(pk=8).first().delete() ",
        "bbox": [
            146,
            199,
            428,
            211
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "方式二：使用 QuerySet 类型数据.delete()(推荐)",
        "bbox": [
            146,
            218,
            401,
            230
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "返回值：元组，第一个元素为受影响的行数。",
        "bbox": [
            146,
            237,
            379,
            248
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "books=models.Book.objects.filter(pk__in=[1,2]).delete() ",
        "bbox": [
            146,
            255,
            438,
            267
        ],
        "page_idx": 40
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. Django 删除数据时，会模仿 SQL约束 ON DELETE CASCADE 的行为，也就是删除一个对象时也会删除与它相关联的外键对象。",
            ". b. delete() 方法是 QuerySet 数据类型的方法，但并不适用于 Manager 本身。也就是想要删除所有数据，不能不写 all。"
        ],
        "bbox": [
            174,
            274,
            845,
            323
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "books=models.Book.objects.delete() # 报错 ",
        "bbox": [
            146,
            330,
            388,
            341
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "books=models.Book.objects.all().delete() # 删除成功 ",
        "bbox": [
            146,
            348,
            447,
            360
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "修改",
        "text_level": 1,
        "bbox": [
            146,
            388,
            200,
            407
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "方式一：",
        "bbox": [
            146,
            451,
            191,
            462
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "模型类的对象.属性 $=$ 更改的属性值",
        "text_level": 1,
        "bbox": [
            146,
            491,
            514,
            512
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "模型类的对象.save()",
        "bbox": [
            146,
            554,
            258,
            565
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "返回值：编辑的模型类的对象。",
        "bbox": [
            146,
            573,
            309,
            583
        ],
        "page_idx": 40
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k s} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r} (\\mathrm {p k} = 7). \\text {f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            590,
            393,
            602
        ],
        "page_idx": 40
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k s . p r i c e} = 4 0 0\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            609,
            243,
            620
        ],
        "page_idx": 40
    },
    {
        "type": "equation",
        "text": "$$\n\\mathsf {b o o k s . s a v e ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            627,
            218,
            639
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "方式二：QuerySet 类型数据.update(字段名 $\\mathbf { \\tau } = \\mathbf { \\dot { \\tau } }$ 更改的数据)（推荐）",
        "bbox": [
            146,
            646,
            485,
            658
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "返回值：整数，受影响的行数",
        "bbox": [
            146,
            665,
            304,
            676
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "实例",
        "bbox": [
            147,
            684,
            174,
            695
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "from django.shortcuts import render,HttpResponse ",
        "bbox": [
            146,
            702,
            420,
            713
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "from app01 import models ",
        "bbox": [
            146,
            720,
            294,
            731
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            146,
            739,
            273,
            750
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.filter(pk__in=[7,8]).update(price=888) ",
        "bbox": [
            159,
            758,
            514,
            769
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "ORM - 添加数据",
        "bbox": [
            146,
            795,
            236,
            806
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "一对多(外键 ForeignKey)",
        "bbox": [
            146,
            813,
            285,
            825
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "方式一: 传对象的形式，返回值的数据类型是对象，书籍对象。",
        "bbox": [
            146,
            832,
            468,
            843
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "步骤：",
        "bbox": [
            146,
            851,
            179,
            860
        ],
        "page_idx": 40
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. 获取出版社对象",
            "b. 给书籍的出版社属性pulish 传出版社对象"
        ],
        "bbox": [
            174,
            869,
            436,
            898
        ],
        "page_idx": 40
    },
    {
        "type": "text",
        "text": "app01/views.py 文件代码：",
        "bbox": [
            146,
            95,
            292,
            105
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            146,
            112,
            273,
            124
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 获取出版社对象",
        "bbox": [
            159,
            131,
            260,
            143
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "pub_ob $=$ models.Publish.objects.filter(pk=1).first() ",
        "bbox": [
            159,
            151,
            428,
            162
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 给书籍的出版社属性publish传出版社对象",
        "bbox": [
            159,
            168,
            396,
            181
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "book $=$ models.Book.objects.create(title=\"菜鸟教程\", price $\\scriptstyle \\sum 0 0$ , pub_date $\\cdot = \"$ 2010-10-10\", publish=pub_obj) ",
        "bbox": [
            159,
            187,
            717,
            200
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "print(book, type(book)) ",
        "bbox": [
            159,
            206,
            285,
            218
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "return HttpResponse(book)",
        "bbox": [
            159,
            225,
            309,
            236
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "方式二: 传对象 id 的形式(由于传过来的数据一般是 id,所以传对象 id 是常用的)。",
        "bbox": [
            146,
            243,
            556,
            255
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "一对多中，设置外键属性的类(多的表)中，MySQL 中显示的字段名是:外键属性名_id。",
        "bbox": [
            146,
            262,
            589,
            273
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "返回值的数据类型是对象，书籍对象。",
        "bbox": [
            146,
            280,
            344,
            291
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "步骤：",
        "text_level": 1,
        "bbox": [
            146,
            299,
            179,
            310
        ],
        "page_idx": 41
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            ". a. 获取出版社对象的 id",
            "b. 给书籍的关联出版社字段pulish_id 传出版社对象的 id"
        ],
        "bbox": [
            176,
            317,
            500,
            347
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "多对多(ManyToManyField)：在第三张关系表中新增数据",
        "text_level": 1,
        "bbox": [
            146,
            354,
            448,
            366
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "方式一: 传对象形式，无返回值。",
        "bbox": [
            146,
            373,
            315,
            384
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "步骤：",
        "text_level": 1,
        "bbox": [
            146,
            392,
            179,
            401
        ],
        "page_idx": 41
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. 获取作者对象 ",
            ". b. 获取书籍对象",
            "c. 给书籍对象的 authors 属性用 add 方法传作者对象"
        ],
        "bbox": [
            176,
            410,
            482,
            458
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "方式二: 传对象id形式，无返回值。",
        "bbox": [
            146,
            464,
            329,
            476
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "步骤：",
        "text_level": 1,
        "bbox": [
            146,
            483,
            179,
            495
        ],
        "page_idx": 41
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. 获取作者对象的 id",
            ". b. 获取书籍对象",
            "c. 给书籍对象的 authors 属性用 add 方法传作者对象的 id"
        ],
        "bbox": [
            176,
            502,
            505,
            551
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "app01/views.py 文件代码：",
        "bbox": [
            146,
            558,
            292,
            570
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "def add_book(request): ",
        "bbox": [
            146,
            577,
            273,
            588
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 获取作者对象",
        "bbox": [
            159,
            595,
            248,
            606
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "chong $=$ models.Author.objects.filter(name=\"令狐冲\").first() ",
        "bbox": [
            159,
            614,
            468,
            626
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 获取作者对象的id ",
        "bbox": [
            159,
            632,
            270,
            643
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "pk $=$ chong.pk ",
        "bbox": [
            159,
            651,
            238,
            661
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 获取书籍对象",
        "bbox": [
            159,
            670,
            248,
            680
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "book $=$ models.Book.objects.filter(titl $\\mathrel { \\mathop : } \\mathbf { \\overline { { \\mathbf { \\Lambda } } } }$ \"冲灵剑法\").first() ",
        "bbox": [
            159,
            688,
            455,
            699
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "# 给书籍对象的 authors 属性用 add 方法传作者对象的 id",
        "bbox": [
            159,
            706,
            458,
            717
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "book.authors.add(pk) ",
        "bbox": [
            159,
            725,
            275,
            736
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "关联管理器(对象调用)",
        "text_level": 1,
        "bbox": [
            146,
            744,
            265,
            755
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "前提：",
        "text_level": 1,
        "bbox": [
            146,
            762,
            179,
            772
        ],
        "page_idx": 41
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "多对多（双向均有关联管理器）",
            "一对多（只有多的那个类的对象有关联管理器，即反向才有）"
        ],
        "bbox": [
            176,
            780,
            522,
            810
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "语法格式：",
        "text_level": 1,
        "bbox": [
            146,
            816,
            203,
            829
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "正向：属性名",
        "bbox": [
            146,
            835,
            221,
            847
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "反向：小写类名加_set",
        "bbox": [
            146,
            854,
            270,
            866
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "注意：一对多只能反向",
        "bbox": [
            146,
            873,
            268,
            884
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "常用方法：",
        "text_level": 1,
        "bbox": [
            146,
            892,
            203,
            903
        ],
        "page_idx": 41
    },
    {
        "type": "text",
        "text": "add()：用于多对多，把指定的模型对象添加到关联对象集（关系表）中。",
        "bbox": [
            144,
            95,
            527,
            105
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "注意：add() 在一对多(即外键)中，只能传对象（ *QuerySet数据类型），不能传 id（*[id表]）。",
        "bbox": [
            144,
            112,
            640,
            124
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "*[ ] 的使用:",
        "bbox": [
            146,
            133,
            208,
            143
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "# 方式一：传对象",
        "bbox": [
            146,
            151,
            243,
            161
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k} \\_ \\text {o b j} = \\text {m o d e l s . B o o k . o b j e c t s . g e t (i d = 1 0)}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            168,
            376,
            180
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {a u t o r} = \\text {m o d e l s . A u t o r . o b j e c t s . f i l t e r (i d \\_ g t = 2)}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            187,
            413,
            199
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "book_obj.authors.add(*author_list) # 将 id 大于 2 的作者对象添加到这本书的作者集合中",
        "bbox": [
            144,
            206,
            608,
            218
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "# 方式二：传对象id",
        "bbox": [
            146,
            225,
            255,
            235
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "book_obj.authors.add(*[1,3]) # 将 id=1 和 id=3 的作者对象添加到这本书的作者集合中",
        "bbox": [
            144,
            243,
            591,
            255
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "return HttpResponse(\"ok\")",
        "bbox": [
            146,
            262,
            292,
            273
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "反向：小写表名_set",
        "bbox": [
            146,
            280,
            258,
            292
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\ny i n g = \\text {m o d e l s . A u t h o r . o b j e c t s . f i l t e r} (\\text {n a m e} = ^ {\\prime \\prime} \\text {任 盈 盈} ^ {\\prime \\prime}). \\text {f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            299,
            445,
            311
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r (t i t l e = ＂ 冲 灵 剑 法 ＂) . f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            317,
            442,
            329
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {y i n g . b o o k \\_ s e t . a d d (b o o k)}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            336,
            280,
            347
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "1. 正向查询（Forward Query）",
        "text_level": 1,
        "bbox": [
            146,
            373,
            312,
            385
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "定义",
        "text_level": 1,
        "bbox": [
            146,
            392,
            176,
            401
        ],
        "page_idx": 42
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "从“持有外键的模型”访问“被关联的模型”。",
            "使用属性名直接访问关联对象。"
        ],
        "bbox": [
            176,
            409,
            458,
            439
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "2. 反向查询（Reverse Query）",
        "text_level": 1,
        "bbox": [
            146,
            447,
            312,
            458
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "定义",
        "text_level": 1,
        "bbox": [
            146,
            466,
            174,
            476
        ],
        "page_idx": 42
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "从“被关联的模型”访问“持有外键的模型”。",
            "默认使用小写类名_set 作为关联管理器名称。"
        ],
        "bbox": [
            176,
            483,
            458,
            514
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "remove()：从关联对象集中移除执行的模型对象。",
        "bbox": [
            146,
            521,
            405,
            533
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "对于 ForeignKey 对象，这个方法仅在 null=True（可以为空）时存在，无返回值。",
        "bbox": [
            144,
            539,
            566,
            551
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            146,
            558,
            174,
            569
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {a u t o r} = \\text {m o d e l s . A u t h o r . o b j e c t s . g e t (i d = 1)}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            577,
            381,
            588
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k} \\_ \\text {o b j} = \\text {m o d e l s . B o o k . o b j e c t s . g e t (i d = 1 1)}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            595,
            376,
            606
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {a t h o r} \\quad \\text {o b j . b o o k} \\quad \\text {s e t . r e m o v e} (\\text {b o o k} \\quad \\text {o b j})\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            614,
            356,
            625
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "clear()：从关联对象集中移除一切对象，删除关联，不会删除对象。",
        "bbox": [
            146,
            632,
            497,
            644
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "对于 ForeignKey 对象，这个方法仅在 null=True（可以为空）时存在。",
        "bbox": [
            146,
            651,
            509,
            662
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "无返回值。",
        "bbox": [
            146,
            670,
            203,
            680
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "# 清空独孤九剑关联的所有作者",
        "bbox": [
            146,
            688,
            317,
            699
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r (t i t l e} = \\text {＂ 菜 鸟 教 程 ＂) . f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            707,
            442,
            718
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k . a u t h o r s . c l e a r ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            725,
            255,
            736
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "ORM 查询",
        "text_level": 1,
        "bbox": [
            146,
            744,
            205,
            753
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "基于对象的跨表查询。",
        "bbox": [
            146,
            762,
            262,
            772
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "正向：属性名称",
        "bbox": [
            146,
            781,
            233,
            791
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "反向：小写类名_set",
        "bbox": [
            146,
            799,
            255,
            810
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "一对多",
        "text_level": 1,
        "bbox": [
            146,
            818,
            186,
            829
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "查询主键为 1 的书籍的出版社所在的城市（正向）。",
        "bbox": [
            146,
            835,
            415,
            847
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            146,
            854,
            174,
            866
        ],
        "page_idx": 42
    },
    {
        "type": "equation",
        "text": "$$\n\\begin{array}{l} \\text {b o o k} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r (p k = 1 0) . f i r s t ()} \\\\ \\mathrm {r e s} = \\text {b o o k . p u b l i s h . c i t y} \\\\ \\end{array}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            873,
            394,
            903
        ],
        "page_idx": 42
    },
    {
        "type": "text",
        "text": "查询明教出版社出版的书籍名（反向）。",
        "bbox": [
            146,
            95,
            354,
            105
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "反向：对象.小写类名_set(pub.book_set) 可以跳转到关联的表(书籍表)。",
        "bbox": [
            146,
            112,
            524,
            124
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "pub.book_set.all()：取出书籍表的所有书籍对象，在一个 QuerySet 里，遍历取出一个个书籍对象。",
        "bbox": [
            146,
            131,
            663,
            143
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            151,
            174,
            161
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\begin{array}{l} \\mathrm {p u b} = \\text {m o d e l s . P u b l i s h . o b j e c t s . f i l t e r (n a m e = \" 明 教 出 版 社\")}. \\text {f i r s t ()} \\\\ \\operatorname {r e s} = \\operatorname {p u b}. \\operatorname {b o o k} _ {\\text {s e t}}. \\operatorname {a l l} () \\\\ f o r i \\text {i n} \\\\ \\mathbf {p r i n t} (i. t i l e) \\\\ \\end{array}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            168,
            470,
            235
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "一对一",
        "text_level": 1,
        "bbox": [
            147,
            244,
            186,
            254
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "查询令狐冲的电话（正向）",
        "bbox": [
            146,
            261,
            284,
            272
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "正向：对象.属性 (author.au_detail) 可以跳转到关联的表(作者详情表)",
        "bbox": [
            146,
            280,
            505,
            292
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            299,
            174,
            309
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\text {a u t h o r} = \\text {m o d e l s . A u t h o r . o b j e c t s . f i l t e r (n a m e = \" 令 狐 冲\") . f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            317,
            455,
            329
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\mathrm {r e s} = \\text {a u t h o r}.\n$$",
        "text_format": "latex",
        "bbox": [
            147,
            336,
            280,
            346
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\operatorname {p r i n t} (\\text {r e s}, \\text {t y p e} (\\text {r e s}))\n$$",
        "text_format": "latex",
        "bbox": [
            147,
            355,
            253,
            366
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "查询所有住址在黑木崖的作者的姓名（反向）。",
        "bbox": [
            146,
            373,
            389,
            384
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "一对一的反向，用对象.小写类名即可，不用加_set。",
        "bbox": [
            146,
            391,
            423,
            403
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "反向：对象.小写类名(addr.author)可以跳转到关联的表(作者表)。",
        "bbox": [
            146,
            410,
            480,
            420
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            429,
            174,
            439
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\begin{array}{l} \\text {a d d r} = \\text {m o d e l s . A u t h o r D e a t i l . o b j e c t s . f i l t e r} (\\text {a d d r} = ^ {\\prime \\prime} \\text {黑 木 崖}) ^ {\\prime \\prime}). \\text {f i r s t} () \\\\ \\text {r e s} = \\text {a d d r . a u t h o r . n a m e} \\\\ \\operatorname {p r i n t} (\\text {r e s}, \\text {t y p e} (\\text {r e s})) \\\\ \\end{array}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            447,
            470,
            495
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "多对多",
        "text_level": 1,
        "bbox": [
            147,
            502,
            186,
            513
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "菜鸟教程所有作者的名字以及手机号（正向）。",
        "bbox": [
            146,
            521,
            389,
            532
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "正向：对象.属性(book.authors)可以跳转到关联的表(作者表)。",
        "bbox": [
            146,
            539,
            472,
            551
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "作者表里没有作者电话，因此再次通过对象.属性(i.au_detail)跳转到关联的表（作者详情表）。",
        "bbox": [
            146,
            558,
            635,
            570
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            577,
            174,
            588
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\text {b o o k} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r (t i t l e = \" 菜 鸟 教 程 ) . f i r s t ()}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            595,
            440,
            607
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\mathrm {r e s} = \\text {b o o k . a u t h o r s . a l l ()}\n$$",
        "text_format": "latex",
        "bbox": [
            147,
            614,
            270,
            625
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\nf o r i n \\text {r e s}:\n$$",
        "text_format": "latex",
        "bbox": [
            147,
            633,
            208,
            643
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\text {p r i n t} (\\mathrm {i . n a m e}, \\mathrm {i . a u} _ {\\text {d e t a l}}. \\text {t e l})\n$$",
        "text_format": "latex",
        "bbox": [
            161,
            651,
            309,
            662
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "查询任我行出过的所有书籍的名字（反向）。",
        "bbox": [
            146,
            669,
            379,
            681
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            688,
            174,
            697
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\begin{array}{l} \\text {a u t h o r} = \\text {m o d e l s . A u t h o r . o b j e c t s . f i l t e r (n a m e} = ^ {\\prime \\prime} \\text {任 我 行}) ^ {\\prime \\prime}. \\text {f i r s t ()} \\\\ \\operatorname {r e s} = \\text {a u t h o r . b o o k \\_ s e t . a l l ()} \\\\ f o r i n \\text {r e s}: \\\\ \\mathbf {p r i n t} (i. t i l e) \\\\ \\end{array}\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            707,
            455,
            772
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "正向：属性名称__跨表的属性名称反向：小写类名__跨表的属性名称",
        "bbox": [
            146,
            780,
            509,
            791
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "一对多",
        "text_level": 1,
        "bbox": [
            147,
            799,
            186,
            809
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "查询菜鸟出版社出版过的所有书籍的名字与价格。",
        "bbox": [
            146,
            816,
            401,
            829
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            837,
            174,
            847
        ],
        "page_idx": 43
    },
    {
        "type": "equation",
        "text": "$$\n\\text {r e s} = \\text {m o d e l s . B o o k . o b j e c t s . f i l t e r (p u b l i s h \\_ n a m e = \" 菜 鸟 出 版 社\") . v a l u e s \\_ l i s t (\"} t h i t e , \" p r i c e \")\n$$",
        "text_format": "latex",
        "bbox": [
            146,
            854,
            608,
            866
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "正向：属性名称__跨表的属性名称反向：小写类名__跨表的属性名称",
        "bbox": [
            146,
            873,
            509,
            884
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "一对多",
        "text_level": 1,
        "bbox": [
            147,
            892,
            186,
            903
        ],
        "page_idx": 43
    },
    {
        "type": "text",
        "text": "查询菜鸟出版社出版过的所有书籍的名字与价格。",
        "bbox": [
            146,
            95,
            403,
            105
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            146,
            112,
            174,
            124
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.Book.objects.filter(publish__name=\"菜鸟出版社\").values_list(\"title\", \"price\") ",
        "bbox": [
            144,
            131,
            608,
            143
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "反向：通过 小写类名__跨表的属性名称（book__title，book__price）跨表获取数据。",
        "bbox": [
            146,
            151,
            600,
            162
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            146,
            168,
            174,
            180
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.Publish.objects.filter(name=\"菜鸟出版社\").values_list(\"book__title\",\"book__price\") ",
        "bbox": [
            144,
            187,
            643,
            199
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "return HttpResponse(\"ok\")",
        "bbox": [
            146,
            206,
            290,
            217
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "多对多",
        "text_level": 1,
        "bbox": [
            146,
            224,
            186,
            235
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "查询任我行出过的所有书籍的名字。",
        "bbox": [
            146,
            243,
            332,
            254
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "正向：通过 属性名称__跨表的属性名称(authors__name) 跨表获取数据：",
        "bbox": [
            146,
            261,
            524,
            273
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.Book.objects.filter(authors__name=\"任我行\").values_list(\"title\") ",
        "bbox": [
            146,
            280,
            547,
            292
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "反向：通过 小写类名__跨表的属性名称（book__title） 跨表获取数据：",
        "bbox": [
            146,
            299,
            514,
            311
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.Author.objects.filter(name=\"任我行\").values_list(\"book__title\") ",
        "bbox": [
            146,
            317,
            541,
            329
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "一对一",
        "text_level": 1,
        "bbox": [
            146,
            336,
            186,
            346
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "查询任我行的手机号。",
        "bbox": [
            146,
            354,
            260,
            365
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "正向：通过属性名称__跨表的属性名称(au_detail__tel) 跨表获取数据。",
        "bbox": [
            146,
            372,
            519,
            384
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.Author.objects.filter(name=\"任我行\").values_list(\"au_detail__tel\") ",
        "bbox": [
            146,
            391,
            557,
            403
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "反向：通过小写类名__跨表的属性名称（author__name）跨表获取数据。",
        "bbox": [
            146,
            410,
            537,
            420
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "res $=$ models.AuthorDetail.objects.filter(author__name=\"任我行\").values_list(\"tel\") ",
        "bbox": [
            146,
            428,
            573,
            439
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "Django ORM – 多表实例（聚合与分组查询）",
        "text_level": 1,
        "bbox": [
            146,
            447,
            381,
            458
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "聚合查询（aggregate）",
        "text_level": 1,
        "bbox": [
            146,
            464,
            270,
            476
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "聚合查询函数是对一组值执行计算，并返回单个值。",
        "bbox": [
            146,
            483,
            413,
            495
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "Django 使用聚合查询前要先从 django.db.models 引入 Avg、Max、Min、Count、Sum（首字母大写）。",
        "bbox": [
            146,
            502,
            685,
            514
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "from django.db.models import Avg,Max,Min,Count,Sum # 引入函数",
        "bbox": [
            146,
            521,
            509,
            532
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "聚合查询返回值的数据类型是字典。",
        "bbox": [
            146,
            539,
            332,
            551
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "聚合函数 aggregate() 是 QuerySet 的一个终止子句， 生成的一个汇总值，相当于 count()。",
        "bbox": [
            146,
            558,
            613,
            570
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "使用 aggregate() 后，数据类型就变为字典，不能再使用 QuerySet 数据类型的一些 API 了。",
        "bbox": [
            146,
            576,
            620,
            588
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "日期数据类型(DateField)可以用 Max 和 Min。",
        "bbox": [
            146,
            595,
            381,
            606
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "返回的字典中：键的名称默认是（属性名称加上__聚合函数名），值是计算出来的聚合值。",
        "bbox": [
            146,
            613,
            616,
            625
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "如果要自定义返回字典的键的名称，可以起别名：",
        "bbox": [
            146,
            632,
            403,
            643
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "aggregate(别名 $=$ 聚合函数名(\"属性名称\"))",
        "bbox": [
            146,
            651,
            369,
            662
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "分组查询（annotate）",
        "text_level": 1,
        "bbox": [
            146,
            688,
            263,
            699
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "分组查询一般会用到聚合函数，所以使用前要先从 django.db.models 引入 Avg,Max,Min,Count,Sum（首字母大写）。",
        "bbox": [
            144,
            706,
            751,
            718
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "from django.db.models import Avg,Max,Min,Count,Sum # 引入函数",
        "bbox": [
            146,
            725,
            509,
            736
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "返回值：",
        "text_level": 1,
        "bbox": [
            146,
            743,
            191,
            753
        ],
        "page_idx": 44
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "分组后，用 values 取值，则返回值是 QuerySet 数据类型里面为一个个字典；",
            "分组后，用 values_list 取值，则返回值是 QuerySet 数据类型里面为一个个元组。"
        ],
        "bbox": [
            174,
            762,
            626,
            791
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片。",
        "bbox": [
            146,
            799,
            478,
            810
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "注意：",
        "text_level": 1,
        "bbox": [
            146,
            816,
            179,
            829
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "annotate 里面放聚合函数。",
        "bbox": [
            146,
            835,
            287,
            847
        ],
        "page_idx": 44
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "values 或者 values_list 放在 annotate 前面：values 或者 values_list 是声明以什么字段分组，annotate 执行分组。前者相当于GROUP BY,后者相当于聚合函数",
            "values 或者 values_list 放在 annotate 后面： annotate 表示直接以当前表的 pk 执行分组，values 或者 values_list 表示查询哪"
        ],
        "bbox": [
            144,
            854,
            847,
            903
        ],
        "page_idx": 44
    },
    {
        "type": "text",
        "text": "些字段， 并且要将 annotate 里的聚合函数起别名，在 values 或者 values_list 里写其别名。",
        "bbox": [
            176,
            95,
            645,
            107
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "annotate 里相当于聚合函数，value 相当于 groupby 了",
        "text_level": 1,
        "bbox": [
            147,
            114,
            468,
            126
        ],
        "page_idx": 45
    },
    {
        "type": "code",
        "sub_type": "algorithm",
        "code_caption": [],
        "code_body": "res $=$ models.Publish.objects.values(\"name\"). annotate(in_price $\\equiv$ Min(\"book_price\"))   \nprint(res)   \nres $=$ models.Book.objects annotate(c $=$ Count(\"authors_name\").values(\"title\",\"c\")   \nprint(res) ",
        "bbox": [
            146,
            131,
            594,
            200
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "F() 查询",
        "text_level": 1,
        "bbox": [
            147,
            206,
            193,
            218
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "F() 的实例可以在查询中引用字段，来比较同一个 model 实例中两个不同字段的值。",
        "bbox": [
            146,
            224,
            576,
            236
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "之前构造的过滤器都只是将字段值与某个常量做比较，如果想要对两个字段的值做比较，就需要用到 F()。",
        "bbox": [
            146,
            243,
            690,
            255
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "使用前要先从 django.db.models 引入 F:",
        "bbox": [
            146,
            262,
            357,
            273
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "from django.db.models import F ",
        "bbox": [
            146,
            280,
            317,
            291
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "用法：",
        "text_level": 1,
        "bbox": [
            147,
            299,
            179,
            310
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "F(\"字段名称\")",
        "bbox": [
            146,
            317,
            221,
            329
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "F 动态获取对象字段的值，可以进行运算。",
        "bbox": [
            146,
            335,
            366,
            347
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。",
        "bbox": [
            146,
            354,
            544,
            366
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "修改操作（update）也可以使用 F() 函数。",
        "bbox": [
            146,
            373,
            366,
            384
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "查询工资大于年龄的人：",
        "bbox": [
            146,
            391,
            273,
            403
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            410,
            174,
            420
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "from django.db.models import F ",
        "bbox": [
            146,
            428,
            322,
            439
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "... ",
        "bbox": [
            147,
            450,
            161,
            456
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "book=models.Emp.objects.filter(salary__gt=F(\"age\")).values(\"name\",\"age\") ",
        "bbox": [
            146,
            464,
            539,
            476
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Q() 查询",
        "bbox": [
            147,
            483,
            194,
            495
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "使用前要先从 django.db.models 引入 Q:",
        "bbox": [
            146,
            502,
            359,
            514
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "from django.db.models import Q ",
        "bbox": [
            147,
            521,
            319,
            533
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "用法：",
        "bbox": [
            147,
            539,
            179,
            551
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Q(条件判断) ",
        "bbox": [
            147,
            577,
            215,
            589
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "例如：",
        "bbox": [
            147,
            595,
            179,
            606
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Q(title__startswith=\"菜\") ",
        "bbox": [
            146,
            633,
            275,
            645
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "之前构造的过滤器里的多个条件的关系都是 and，如果需要执行更复杂的查询（例如 or 语句），就可以使用 Q 。",
        "bbox": [
            146,
            650,
            726,
            662
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Q 对象可以使用 & | ~ （与 或非）操作符进行组合。",
        "bbox": [
            146,
            688,
            415,
            700
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "优先级从高到低：~ & |。",
        "bbox": [
            146,
            725,
            273,
            737
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "可以混合使用 Q 对象和关键字参数，Q 对象和关键字参数是用\"and\"拼在一起的（即将逗号看成 and ），但是 Q 对象必须位于所有关键字参数的前面。",
        "bbox": [
            144,
            762,
            847,
            791
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "查询价格大于 350 或者名称以菜开头的书籍的名称和价格。",
        "bbox": [
            146,
            816,
            450,
            829
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "查询出版日期是 2004 或者1999 年，并且书名中包含有\"菜\"的书籍。",
        "bbox": [
            146,
            835,
            497,
            848
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "Q 对象和关键字混合使用，Q 对象要在所有关键字的前面:",
        "bbox": [
            146,
            854,
            448,
            866
        ],
        "page_idx": 45
    },
    {
        "type": "text",
        "text": "总结",
        "text_level": 1,
        "bbox": [
            147,
            101,
            200,
            120
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books=models.Book.objects ",
        "bbox": [
            159,
            181,
            310,
            193
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books,type(books)) ",
        "bbox": [
            159,
            199,
            292,
            212
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "TestModel.Book.objects <class 'django.db.models.manager.Manager'> ",
        "bbox": [
            147,
            215,
            781,
            233
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.create(title=\"如来神掌\",price=200,publish=\"功夫出版社\",pub_date=\"2010-10-10\") ",
        "bbox": [
            147,
            237,
            727,
            250
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books, type(books)) # Book object (18) ",
        "bbox": [
            161,
            255,
            393,
            267
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "Book object (7) <class 'TestModel.models.Book'> ",
        "bbox": [
            147,
            272,
            517,
            288
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.all() ",
        "bbox": [
            159,
            292,
            339,
            304
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books,type(books)) ",
        "bbox": [
            159,
            311,
            292,
            323
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "<QuerySet [<Book: Book object $\\cdot$ …,<Book: Book object (7)>]> <class 'django.db.models.query.QuerySet'> ",
        "bbox": [
            147,
            329,
            727,
            341
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.first() # 返回所有数据的第一条数据",
        "bbox": [
            159,
            347,
            502,
            360
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books, type(books)) ",
        "bbox": [
            159,
            366,
            295,
            378
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "Book object (1) <class 'TestModel.models.Book'> ",
        "bbox": [
            147,
            385,
            406,
            395
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "等同于",
        "bbox": [
            147,
            404,
            186,
            414
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.filter() # 返回所有数据的第一条数据",
        "bbox": [
            159,
            422,
            505,
            434
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books[0], type(books[0])) ",
        "bbox": [
            159,
            439,
            321,
            451
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "Book object (1) <class 'TestModel.models.Book'> ",
        "bbox": [
            147,
            458,
            406,
            470
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.values(\"pk\",\"price\") ",
        "bbox": [
            147,
            476,
            408,
            489
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books,type(books)) # 得到的是第一条记录的 price 字段的数据",
        "bbox": [
            159,
            495,
            510,
            508
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "<QuerySet [{'pk': 1, 'price': Decimal('300.00')},… , {'pk': 7, 'price': Decimal('200.00')}]> <class 'django.db.models.query.QuerySet'> ",
        "bbox": [
            147,
            514,
            821,
            526
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "books $=$ models.Book.objects.values_list(\"price\",\"publish\") ",
        "bbox": [
            159,
            533,
            467,
            545
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "print(books,type(books)) ",
        "bbox": [
            159,
            552,
            292,
            563
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "<QuerySet [(Decimal('300.00'), ' 菜 鸟 出 版 社 '), …. (Decimal('200.00'), ' 功 夫 出 版 社 '), (Decimal('200.00'), ' 功 夫 出 版 社 ')]> <class",
        "bbox": [
            146,
            570,
            848,
            583
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "'django.db.models.query.QuerySet'> ",
        "bbox": [
            147,
            588,
            339,
            600
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "1. Book 对象（<class 'TestModel.models.Book'>）",
        "bbox": [
            147,
            607,
            423,
            619
        ],
        "page_idx": 46
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "含义：Book 是 Django 模型（Model）的一个实例，对应数据库中的一行记录。",
            "作用：表示单个数据对象，可以访问其字段（如book.title、book.price）。",
            ". 示例：",
            ". 特点："
        ],
        "bbox": [
            176,
            626,
            620,
            692
        ],
        "page_idx": 46
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 是 TestModel.models.Book 类的实例。",
            "o 可以直接修改并保存到数据库（book.save()）。"
        ],
        "bbox": [
            236,
            699,
            517,
            730
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "2. Book.objects（<class 'django.db.models.manager.Manager'>）",
        "bbox": [
            146,
            755,
            510,
            768
        ],
        "page_idx": 46
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "含义：objects 是 Django 模型的默认管理器（Manager），用于执行数据库查询操作。",
            "作用：提供查询方法（如 all(), filter(), get(), create()），用于操作数据库。",
            "示例："
        ],
        "bbox": [
            174,
            774,
            658,
            822
        ],
        "page_idx": 46
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 是 Manager 类的实例。",
            "o 不直接返回数据，而是返回QuerySet（查询集）或单个Book。",
            "o 可以自定义管理器（如 class BookManager(models.Manager)）。"
        ],
        "bbox": [
            236,
            829,
            620,
            879
        ],
        "page_idx": 46
    },
    {
        "type": "text",
        "text": "3. QuerySet（<class 'django.db.models.query.QuerySet'>）",
        "bbox": [
            144,
            95,
            475,
            107
        ],
        "page_idx": 47
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "含义：QuerySet 是 Django 查询数据库返回的结果集，可以包含0 个、1 个或多个Book 对象。",
            "作用：用于链式查询（如过滤、排序、聚合等），但不会立即执行查询（惰性加载）。",
            "示例："
        ],
        "bbox": [
            174,
            112,
            702,
            161
        ],
        "page_idx": 47
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 是惰性的，只有在真正使用时（如遍历、list(queryset)、len(queryset)）才会查询数据库。",
            "o 可以进一步过滤（filter()）、排序（order_by()）、切片（[:5]）等。",
            "o 如果只返回一个对象（如 get()），则返回 Book 对象，而不是 QuerySet。"
        ],
        "bbox": [
            236,
            168,
            741,
            217
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "2. 为什么 QuerySet 类型不变，但数据形式不同？",
        "bbox": [
            144,
            224,
            406,
            236
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "QuerySet 是 Django 的惰性查询集，它只是生成 SQL 查询的容器，具体返回的数据形式由调用的方法决定：",
        "bbox": [
            174,
            243,
            769,
            255
        ],
        "page_idx": 47
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o all() → 返回模型实例。",
            "o values() → 返回字典。",
            "o values_list() → 返回元组或单个值。"
        ],
        "bbox": [
            236,
            261,
            457,
            310
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "但 QuerySet 本身的类型始终是 django.db.models.query.QuerySet，因为它代表的是“查询能力”，而不是具体的数据形式。",
        "bbox": [
            174,
            317,
            847,
            347
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "3. 如何选择合适的方法？",
        "text_level": 1,
        "bbox": [
            146,
            372,
            275,
            384
        ],
        "page_idx": 47
    },
    {
        "type": "table",
        "img_path": "images/325e9eead8dd4eb708f6b6ae0407571b2100f9723cb4103acdda3536fd974f7f.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>方法</td><td>返回的数据形式</td><td>适用场景</td></tr><tr><td>all()</td><td>QuerySet[Model 实例]</td><td>需要完整模型对象（如调用 save()、访问关联字段）</td></tr><tr><td>values(&quot;field1&quot;, &quot;field2&quot;)</td><td>QuerySet[dict]</td><td>只需要部分字段，且希望用字段名访问</td></tr><tr><td>values_list(&quot;field1&quot;, &quot;field2&quot;)</td><td>QuerySet[tuple]</td><td>只需要字段值，不关心字段名</td></tr><tr><td>values_list(&quot;field&quot;, flat=True)</td><td>QuerySet[单值]</td><td>只需要一个字段的列表（如 [1, 2, 3])</td></tr></table>",
        "bbox": [
            144,
            398,
            848,
            592
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "是的！authors $=$ models.ManyToManyField(\"Author\") 这一行代码会自动创建一个中间表（关联表）来存储多对多关系，但不会直接修改Author 表或当前模型表的结构。以下是详细解释：",
        "bbox": [
            144,
            594,
            848,
            626
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "1. 自动创建的中间表",
        "text_level": 1,
        "bbox": [
            146,
            650,
            258,
            661
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "Django 会为你生成一个名为<当前模型表名>_authors 的中间表（例如，如果当前模型是 Book，则表名为book_authors），其结构如下：",
        "bbox": [
            144,
            668,
            848,
            697
        ],
        "page_idx": 47
    },
    {
        "type": "code",
        "sub_type": "code",
        "code_caption": [],
        "code_body": "CREATE TABLE book_authors (\nid INTEGER PRIMARY KEY AUTOINCREMENT, -- SQLite 示例\nbook_id INTEGER NOT NULL, -- 外键指向Book表\nauthor_id INTEGER NOT NULL, -- 外键指向Author表\nFOREIGN KEY (book_id) REFERENCES book(id),\nFOREIGN KEY (author_id) REFERENCES author(id)",
        "guess_lang": "sql",
        "bbox": [
            146,
            706,
            502,
            810
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "); ",
        "bbox": [
            146,
            818,
            159,
            828
        ],
        "page_idx": 47
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "一 这个表只有 3 个字段：id（主键）、book_id（外键）、author_id（外键）。",
            "你不需要手动定义这个表，Django 在运行makemigrations 和migrate 时会自动创建。"
        ],
        "bbox": [
            174,
            835,
            662,
            866
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "5. 注意事项",
        "text_level": 1,
        "bbox": [
            146,
            872,
            213,
            884
        ],
        "page_idx": 47
    },
    {
        "type": "text",
        "text": "1. 表名规则：",
        "bbox": [
            176,
            891,
            263,
            902
        ],
        "page_idx": 47
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o 自动生成的中间表名格式：<当前模型表名>_<字段名 $>$ （如 book_authors）。",
            "o 如果当前模型表名是 app_book，则中间表名为 app_book_authors。"
        ],
        "bbox": [
            236,
            93,
            673,
            124
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "2. 迁移顺序：",
        "text_level": 1,
        "bbox": [
            176,
            131,
            263,
            143
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "o 确保Author 模型已正确定义并先迁移，否则会报错。",
        "bbox": [
            236,
            149,
            544,
            161
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "3. 数据库一致性：",
        "text_level": 1,
        "bbox": [
            176,
            168,
            287,
            180
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "o 不要手动修改中间表，始终通过 Django ORM 操作（如 add()、remove()）。",
        "bbox": [
            236,
            187,
            665,
            199
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "总结",
        "text_level": 1,
        "bbox": [
            147,
            224,
            174,
            236
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "行为 结果",
        "bbox": [
            146,
            252,
            327,
            263
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "定义 ManyToManyField Django 自动创建中间表",
        "bbox": [
            146,
            288,
            426,
            300
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "默认中间表字段 id、<当前模型>_id、<关联模型>_id",
        "bbox": [
            146,
            325,
            494,
            338
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "自定义中间表 使用 through= 参数指定模型",
        "bbox": [
            146,
            363,
            455,
            375
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "操作关系 通过 add()、remove()、clear() 等方法",
        "bbox": [
            146,
            399,
            505,
            412
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "在 Django 中，当你定义一个 ForeignKey 字段时，例如：",
        "bbox": [
            146,
            428,
            445,
            439
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "python ",
        "bbox": [
            147,
            447,
            186,
            458
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "复制",
        "bbox": [
            147,
            464,
            174,
            476
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "下载",
        "bbox": [
            147,
            483,
            174,
            494
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "publish $=$ models.ForeignKey(\"Publish\", on_delete=models.CASCADE) ",
        "bbox": [
            146,
            502,
            521,
            513
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "Django 实际上会在数据库中创建一个名为publish_id 的列，而不是publish。这是由 Django 的设计机制决定的，原因如下：",
        "bbox": [
            146,
            520,
            796,
            532
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "1. 为什么字段名是 publish_id 而不是 publish？",
        "text_level": 1,
        "bbox": [
            146,
            557,
            396,
            569
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "(1) 数据库层面的外键存储",
        "text_level": 1,
        "bbox": [
            146,
            576,
            287,
            587
        ],
        "page_idx": 48
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "在关系型数据库（如 MySQL、PostgreSQL、SQLite）中，外键关系是通过存储目标表的主键值来实现的。",
            ". 例如，如果 Publish 表的主键是 id，那么 Book 表会有一个 publish_id 列，存储的是 Publish 表中某条记录的 id 值。",
            "1. 为什么列名不同也能关联？",
            "在关系型数据库（如 MySQL、PostgreSQL、SQLite）中，外键关联的本质是值匹配，而不是列名匹配。只要满足以下条件即可："
        ],
        "bbox": [
            174,
            594,
            847,
            678
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "",
        "bbox": [
            176,
            687,
            189,
            696
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "(2) Django 的 ORM 抽象",
        "text_level": 1,
        "bbox": [
            146,
            706,
            280,
            718
        ],
        "page_idx": 48
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "虽然数据库列名是publish_id，但 Django 在Python 模型层面提供了一个名为publish 的属性，用于方便地访问关联对象。",
            "当你访问 book.publish 时，Django 会自动执行查询，获取对应的 Publish 对象。",
            "而 book.publish_id 则直接返回数据库中存储的原始外键值（即 Publish 表的 id）。"
        ],
        "bbox": [
            174,
            724,
            850,
            772
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "2. Django 如何实现这种关联？",
        "text_level": 1,
        "bbox": [
            146,
            780,
            307,
            791
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "（1）数据库层面",
        "text_level": 1,
        "bbox": [
            154,
            799,
            240,
            810
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "Django 生成的 SQL 会明确指定外键关联的目标列：",
        "bbox": [
            176,
            816,
            473,
            828
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "sql",
        "bbox": [
            147,
            835,
            166,
            846
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "复制",
        "bbox": [
            147,
            854,
            174,
            865
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "下载",
        "bbox": [
            147,
            873,
            174,
            883
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "FOREIGN KEY (publish_id) REFERENCES Publish(id) ",
        "bbox": [
            146,
            891,
            436,
            902
        ],
        "page_idx": 48
    },
    {
        "type": "text",
        "text": "这里显式声明了 publish_id 关联到 Publish.id，与列名无关。",
        "bbox": [
            146,
            95,
            467,
            105
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "（2）ORM 层面",
        "text_level": 1,
        "bbox": [
            154,
            112,
            236,
            124
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "当你访问 book.publish 时，Django 会自动执行类似以下的查询：",
        "bbox": [
            176,
            131,
            547,
            143
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "SELECT * FROM Publish WHERE id $=$ book.publish_id; ",
        "bbox": [
            146,
            149,
            443,
            162
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "通过 publish_id 的值找到对应的 Publish 记录。",
        "bbox": [
            146,
            168,
            398,
            181
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "3. 核心区别对比",
        "text_level": 1,
        "bbox": [
            146,
            190,
            235,
            200
        ],
        "page_idx": 49
    },
    {
        "type": "table",
        "img_path": "images/253ad5b303793ce0d4769c6b4c028e01ed1cc3015c9f13cf55d54a3c3ee543c6.jpg",
        "table_caption": [],
        "table_footnote": [],
        "table_body": "<table><tr><td>特性</td><td>.add()</td><td>.create()</td></tr><tr><td>作用</td><td>关联已存在的对象</td><td>创建新对象并关联</td></tr><tr><td>是否创建新对象</td><td>□否</td><td>□是</td></tr><tr><td>适用关系类型</td><td>ManyToManyField, ForeignKey</td><td>ManyToManyField, ForeignKey的反向查询</td></tr><tr><td>参数类型</td><td>对象或ID</td><td>新对象的字段键值对</td></tr><tr><td>返回值</td><td>无</td><td>新创建的对象</td></tr></table>",
        "bbox": [
            144,
            216,
            702,
            444
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "Django Form 组件",
        "text_level": 1,
        "bbox": [
            147,
            481,
            500,
            514
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "Django Form 组件用于对页面进行初始化，生成 HTML 标签，此外还可以对用户提交的数据进行校验（显示错误信息）。",
        "bbox": [
            146,
            527,
            769,
            539
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "报错信息显示顺序：",
        "bbox": [
            146,
            565,
            250,
            576
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "先显示字段属性中的错误信息，然后再显示局部钩子的错误信息。",
        "bbox": [
            146,
            602,
            485,
            613
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "若显示了字段属性的错误信息，就不会显示局部钩子的错误信息。",
        "bbox": [
            146,
            621,
            485,
            632
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "若有全局钩子，则全局钩子是等所有的数据都校验完，才开始进行校验，并且全局钩子的错误信息一定会显示。",
        "bbox": [
            146,
            639,
            719,
            651
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "Django 用户认证（Auth）组件",
        "text_level": 1,
        "bbox": [
            147,
            671,
            726,
            705
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "Django 用户认证（Auth）组件一般用在用户的登录注册上，用于判断当前的用户是否合法，并跳转到登陆成功或失败页面。",
        "bbox": [
            144,
            719,
            784,
            731
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "Django 用户认证（Auth）组件需要导入 auth 模块:",
        "bbox": [
            146,
            738,
            413,
            749
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "# 认证模块 ",
        "bbox": [
            146,
            757,
            208,
            766
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "from django.contrib import auth ",
        "bbox": [
            146,
            775,
            312,
            785
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "# 对应数据库",
        "bbox": [
            146,
            812,
            220,
            822
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "from django.contrib.auth.models import User ",
        "bbox": [
            146,
            831,
            383,
            841
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "返回值是用户对象。",
        "bbox": [
            146,
            848,
            250,
            860
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "创建用户对象的三种方法：",
        "bbox": [
            146,
            868,
            285,
            879
        ],
        "page_idx": 49
    },
    {
        "type": "text",
        "text": "create()：创建一个普通用户，密码是明文的。",
        "bbox": [
            176,
            885,
            447,
            897
        ],
        "page_idx": 49
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "create_user()：创建一个普通用户，密码是密文的。",
            "create_superuser()：创建一个超级用户，密码是密文的，要多传一个邮箱email 参数。"
        ],
        "bbox": [
            174,
            95,
            660,
            124
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "验证用户的用户名和密码使用 authenticate() 方法，从需要auth_user 表中过滤出用户对象。",
        "bbox": [
            147,
            131,
            621,
            143
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "使用前要导入：",
        "bbox": [
            147,
            168,
            226,
            181
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "from django.contrib import auth ",
        "bbox": [
            146,
            206,
            314,
            218
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "参数：",
        "bbox": [
            147,
            225,
            179,
            235
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "username：用户名",
        "bbox": [
            146,
            262,
            250,
            273
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "password：密码",
        "bbox": [
            147,
            281,
            236,
            291
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "返回值：如果验证成功，就返回用户对象，反之，返回 None。",
        "bbox": [
            146,
            299,
            468,
            310
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "给验证成功的用户加 session，将 request.user 赋值为用户对象。",
        "bbox": [
            146,
            317,
            480,
            328
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "登陆使用 login() 方法。",
        "bbox": [
            147,
            336,
            265,
            347
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "使用前要导入：",
        "bbox": [
            147,
            355,
            226,
            365
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "from django.contrib import auth ",
        "bbox": [
            146,
            373,
            312,
            384
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "参数：",
        "bbox": [
            147,
            392,
            179,
            401
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "request：用户对象",
        "bbox": [
            176,
            410,
            310,
            420
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "注销用户使用 logout() 方法，需要清空 session 信息，将 request.user 赋值为匿名用户。",
        "bbox": [
            146,
            428,
            600,
            439
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "使用前要导入：",
        "bbox": [
            147,
            447,
            226,
            458
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "from django.contrib import auth ",
        "bbox": [
            146,
            466,
            314,
            476
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "参数：",
        "bbox": [
            147,
            485,
            179,
            494
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "request：用户对象",
        "bbox": [
            176,
            502,
            310,
            514
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "返回值：None ",
        "bbox": [
            147,
            521,
            226,
            532
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "设置装饰器，给需要登录成功后才能访问的页面统一加装饰器。",
        "bbox": [
            146,
            539,
            472,
            550
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "使用前要导入：",
        "bbox": [
            147,
            558,
            226,
            569
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "from django.contrib.auth.decorators import login_required ",
        "bbox": [
            146,
            577,
            450,
            588
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "实例",
        "text_level": 1,
        "bbox": [
            147,
            596,
            174,
            606
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "from django.contrib.auth.decorators import login_required $@$ login_required ",
        "bbox": [
            146,
            614,
            547,
            625
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "def index(request): ",
        "bbox": [
            147,
            633,
            250,
            643
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "return HttpResponse(\"index 页面。。。\")",
        "bbox": [
            152,
            652,
            374,
            662
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "设置从哪个页面访问，登录成功后就返回哪个页面。",
        "bbox": [
            147,
            670,
            413,
            680
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "解析：",
        "text_level": 1,
        "bbox": [
            147,
            688,
            179,
            699
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "django 在用户访问页面时，如果用户是未登录的状态，就给用户返回登录页面。",
        "bbox": [
            146,
            707,
            559,
            717
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "此时，该登录页面的 URL 后面有参数：next=用户访问的页面的 URL。",
        "bbox": [
            146,
            725,
            510,
            736
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "因此，设置在用户登录成功后重定向的 URL 为 next 参数的值。",
        "bbox": [
            146,
            744,
            472,
            755
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "但是，若用户一开始就输入登录页面 logi，request.GET.get(\"next\") 就取不到值，所以在后面加or，可以设置自定义返回的页面。",
        "bbox": [
            146,
            762,
            810,
            772
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "Django cookie 与 session",
        "text_level": 1,
        "bbox": [
            147,
            795,
            645,
            828
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "Cookie 是存储在客户端计算机上的文本文件，并保留了各种跟踪信息。",
        "bbox": [
            146,
            841,
            514,
            853
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "识别返回用户包括三个步骤：",
        "bbox": [
            147,
            860,
            297,
            872
        ],
        "page_idx": 50
    },
    {
        "type": "text",
        "text": "服务器脚本向浏览器发送一组Cookie。例如：姓名、年龄或识别号码等。",
        "bbox": [
            176,
            879,
            584,
            890
        ],
        "page_idx": 50
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "浏览器将这些信息存储在本地计算机上，以备将来使用。",
            "当下一次浏览器向 Web 服务器发送任何请求时，浏览器会把这些 Cookie 信息发送到服务器，服务器将使用这些信息来识别用户。"
        ],
        "bbox": [
            174,
            95,
            847,
            142
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "HTTP 是一种\"无状态\"协议，这意味着每次客户端检索网页时，客户端打开一个单独的连接到 Web 服务器，服务器会自动不保留之前客户端请求的任何记录。",
        "bbox": [
            144,
            149,
            847,
            180
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "但是仍然有以下三种方式来维持 Web 客户端和 Web 服务器之间的 session 会话：",
        "bbox": [
            146,
            187,
            566,
            199
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "Cookies ",
        "text_level": 1,
        "bbox": [
            146,
            206,
            196,
            216
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie，对于客户端的后续请求可以使用接收到的cookie 来识别。",
        "bbox": [
            144,
            224,
            845,
            254
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "在 Web 开发中，使用 session 来完成会话跟踪，session 底层依赖 Cookie 技术。",
        "bbox": [
            146,
            261,
            564,
            273
        ],
        "page_idx": 51
    },
    {
        "type": "image",
        "img_path": "images/9a48075248f18fba9020fddb11e4edfdde6582563e4eadd68f5b3ee2169d2f8d.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            147,
            275,
            655,
            456
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "Session(保存在服务端的键值对)",
        "text_level": 1,
        "bbox": [
            144,
            464,
            381,
            479
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "服务器在运行时可以为每一个用户的浏览器创建一个其独享的 session 对象，由于 session 为用户浏览器独享，所以用户在访问服务器的 web 资源时，可以把各自的数据放在各自的 session 中，当用户再去访问该服务器中的其它web 资源时，其它web 资源再从用户各自的 session 中取出数据为用户服务。",
        "bbox": [
            144,
            482,
            855,
            554
        ],
        "page_idx": 51
    },
    {
        "type": "image",
        "img_path": "images/13016cbec949f6ef478321a5634a2c0188ff9b392a433e44d44ca4da2df9e845.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            152,
            570,
            801,
            875
        ],
        "page_idx": 51
    },
    {
        "type": "text",
        "text": "工作原理",
        "text_level": 1,
        "bbox": [
            147,
            890,
            218,
            904
        ],
        "page_idx": 51
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. 浏览器第一次请求获取登录页面 login。",
            " b. 浏览器输入账号密码第二次请求，若输入正确，服务器响应浏览器一个 index 页面和一个键为 sessionid，值为随机字符串的 cookie，即 set_cookie (\"sessionid\",随机字符串)。",
            " c. 服务器内部在 django.session 表中记录一条数据。"
        ],
        "bbox": [
            174,
            93,
            843,
            164
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "django.session 表中有三个字段。",
        "bbox": [
            144,
            168,
            379,
            183
        ],
        "page_idx": 52
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "o session_key：存的是随机字符串，即响应给浏览器的 cookie 的 sessionid 键对应的值。",
            "o session_data：存的是用户的信息，即多个 request.session[\"key\"]=value，且是密文。",
            "o expire_date：存的是该条记录的过期时间（默认 14 天）"
        ],
        "bbox": [
            235,
            186,
            843,
            275
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": " d. 浏览器第三次请求其他资源时，携带cookie :{sessionid:随机字符串}，服务器从django.session 表中根据该随机字符串取出该用户的数据，供其使用（即保存状态）。",
        "bbox": [
            174,
            279,
            826,
            313
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "注意: django.session 表中保存的是浏览器的信息，而不是每一个用户的信息。 因此， 同一浏览器多个用户请求只保存一条记录（后面覆盖前面）,多个浏览器请求才保存多条记录。",
        "bbox": [
            144,
            316,
            842,
            349
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "cookie 弥补了 http 无状态的不足，让服务器知道来的人是\"谁\"，但是 cookie 以文本的形式保存在浏览器端，安全性较差，且最大只支持 4096 字节，所以只通过 cookie 识别不同的用户，然后，在对应的 session 里保存私密的信息以及超过 4096 字节的文本。",
        "bbox": [
            144,
            353,
            836,
            405
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "session 设置：",
        "bbox": [
            144,
            409,
            243,
            423
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "request.session[\"key\"] $=$ value ",
        "bbox": [
            144,
            426,
            356,
            442
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "执行步骤：",
        "bbox": [
            144,
            445,
            226,
            460
        ],
        "page_idx": 52
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            "a. 生成随机字符串 ",
            " b. 把随机字符串和设置的键值对保存到 django_session 表的 session_key 和 session_data里",
            " c. 设置 cookie：set_cookie(\"sessionid\",随机字符串) 响应给浏览器"
        ],
        "bbox": [
            174,
            464,
            836,
            535
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "session 获取：",
        "bbox": [
            144,
            539,
            243,
            552
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "request.session.get('key') ",
        "bbox": [
            144,
            557,
            319,
            571
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "执行步骤：",
        "bbox": [
            144,
            575,
            226,
            590
        ],
        "page_idx": 52
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            " a. 从 cookie 中获取 sessionid 键的值，即随机字符串。",
            " b. 根据随机字符串从 django_session 表过滤出记录。",
            " c. 取出 session_data 字段的数据。"
        ],
        "bbox": [
            174,
            594,
            591,
            646
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "session 删除，删除整条记录（包括 session_key、session_data、expire_date 三个字段）：",
        "bbox": [
            144,
            649,
            781,
            665
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "request.session.flush() ",
        "bbox": [
            144,
            668,
            300,
            683
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "删除 session_data 里的其中一组键值对：",
        "bbox": [
            144,
            686,
            435,
            702
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "del request.session[\"key\"] ",
        "bbox": [
            144,
            705,
            326,
            720
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "执行步骤：",
        "bbox": [
            144,
            722,
            226,
            738
        ],
        "page_idx": 52
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            " a. 从 cookie 中获取 sessionid 键的值，即随机字符串",
            " b. 根据随机字符串从 django_session 表过滤出记录",
            " c. 删除过滤出来的记录"
        ],
        "bbox": [
            174,
            741,
            584,
            793
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "Django 中间件",
        "text_level": 1,
        "bbox": [
            144,
            797,
            258,
            813
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "Django 中间件是修改 Django request 或者 response 对象的钩子，可以理解为是介于 HttpRequest与 HttpResponse 处理之间的一道处理过程。",
        "bbox": [
            144,
            816,
            838,
            850
        ],
        "page_idx": 52
    },
    {
        "type": "text",
        "text": "浏览器从请求到响应的过程中，Django 需要通过很多中间件来处理，可以看如下图所示：",
        "bbox": [
            144,
            853,
            800,
            869
        ],
        "page_idx": 52
    },
    {
        "type": "image",
        "img_path": "images/eff2575789a078e5215193fc6d91fb3d590196cf4b9a4bc9c76cfb08807856a7.jpg",
        "image_caption": [],
        "image_footnote": [],
        "bbox": [
            147,
            95,
            815,
            363
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "Django 中间件作用：",
        "bbox": [
            146,
            390,
            295,
            405
        ],
        "page_idx": 53
    },
    {
        "type": "list",
        "sub_type": "text",
        "list_items": [
            " 修改请求，即传送到 view 中的 HttpRequest 对象。",
            " 修改响应，即 view 返回的 HttpResponse 对象。"
        ],
        "bbox": [
            176,
            409,
            569,
            443
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "中间件组件配置在 settings.py 文件的 MIDDLEWARE 选项列表中。",
        "bbox": [
            144,
            445,
            630,
            461
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "配置中的每个字符串选项都是一个类，也就是一个中间件。",
        "bbox": [
            146,
            464,
            576,
            479
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "自定义中间件",
        "bbox": [
            147,
            482,
            253,
            497
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "中间件可以定义四个方法，分别是：",
        "bbox": [
            146,
            501,
            410,
            516
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "process_request(self,request) ",
        "bbox": [
            144,
            539,
            347,
            552
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "process_view(self, request, view_func, view_args, view_kwargs) ",
        "bbox": [
            146,
            557,
            589,
            571
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "process_exception(self, request, exception) ",
        "bbox": [
            147,
            576,
            440,
            590
        ],
        "page_idx": 53
    },
    {
        "type": "text",
        "text": "process_response(self, request, response) ",
        "bbox": [
            147,
            595,
            428,
            609
        ],
        "page_idx": 53
    }
]