Django快速入门

投票项目案例

一.虚拟环境搭建

pip intall virtualenv  # 安装virtualenv工具
virtualenv -p ‪E:\python3.9\python.exe DjangoEnv  # 创建虚拟环境

# 切换到DjangoEnv环境
pip install django==4.1  # 以django4.1版本为例

二.创建项目和创建应用

  • 创建项目
    django-admin startproject mysite  # mysite为项目名称
    
  • 启动Django项目
    # 切换到manage.py统计目录下执行
    python manage.py runserver 0.0.0.0:5000  # 默认使用127.0.0.1:8000
    
    """
    (env) E:\django_test\test2\mysite>python manage.py runserver 0.0.0.0:5000
    Watching for file changes with StatReloader
    Performing system checks...
    
    System check identified no issues (0 silenced).
    
    You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
    Run 'python manage.py migrate' to apply them.
    November 10, 2022 - 13:46:45
    Django version 4.1, using settings 'mysite.settings'
    Starting development server at http://0.0.0.0:5000/
    Quit the server with CTRL-BREAK.
    
    """
    
  • 创建应用
    # 同样在manage.py目录下执行
    python manage.py startapp polls  # 创建名为polls的应用
    
    • 编写视图
      # polls/views.py
      from django.http import HttpResponse
      
      def index(request):
          return HttpResponse("这是index页面")
      
    • 注册url
      在polls应用下新建urls.py文件,并写入下边代码
      # polls/urls.py
      from django.urls import path
      from . import views
      
      urlpatterns = [
          path('', views.index, name='index')
      ]
      
      在根URLconf文件中插入应用中新建的polls.urls模块(在mysite/urls.py中插入新的include()),函数 include() 允许引用其它 URLconfs。每当 Django 遇到 include() 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理.
      from django.contrib import admin
      from django.urls import path, include
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          path('polls/', include('polls.urls'))
      ]
      
    • 查看视图
      # 重新起动django项目并访问http://127.0.0.1:5000/polls/
      python manage.py runserver 5000
      
  • URLconf中path参数解析
    • route
      route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。
      这些准则不会匹配 GET 和 POST 参数或域名。例如,URLconf 在处理请求 https://www.example.com/myapp/ 时,它会尝试匹配 myapp/ 。处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/。
    • view
      当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。
    • name
      为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。

三.模型配置

  • settings.py
    通常,这个配置文件使用 SQLite 作为默认数据库,如果你想使用其他数据库,你需要安装合适的 database bindings ,然后改变设置文件中 DATABASES 'default' 项目中的一些键值,添加一些额外设置,比如 USER 、 PASSWORD 、 HOST 等等。
    # 使用sql server数据库
    # pip install mssql-django
    DATABASES = {
        'default': {
            'ENGINE': 'mssql',
            'NAME': 'Choice',
            'HOST': '192.168.1.51',
            'USER': 'sa',
            'PASSWORD': '123456',
            'OPTIONS': {
                'driver': 'SQL Server Native Client 11.0'  # 驱动程序(win输入odbc查看)
            }
        }
    }
    
  • 使用已存在的数据库
    创建应用,修改settings数据库配置后执行以下代码:
    python manage.py inspectdb > app/models.py
    python manage.py migrate
    
  • 创建模型
    在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。在这个投票应用中,需要创建两个模型:问题 Question 和选项 Choice。Question 模型包括问题描述和发布时间。Choice 模型有两个字段,选项描述和当前得票数。每个选项属于一个问题。
    # polls.models.py
    from django.db import models
    
    
    class Question(models.Model):
        question_text = models.CharField(max_length=200)
        pub_date = models.DateTimeField('date published')
    
    
    class Choice(models.Model):
        question = models.ForeignKey(Question, on_delete=models.CASCADE)
        choice_text = models.CharField(max_length=200)
        votes = models.IntegerField(default=0)
    
  • 注册应用
    为了在我们的工程中包含这个应用,我们需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 'polls.apps.PollsConfig'。在文件 mysite/settings.py 中 INSTALLED_APPS 子项添加点式路径后,它看起来像这样:
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'polls.apps.PollsConfig'
    ]   
    
  • 模型迁移
    通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移。
    迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式 - 它们其实也只是一些你磁盘上的文件。如果你想的话,你可以阅读一下你模型的迁移数据,它被储存在 polls/migrations/0001_initial.py 里。别担心,你不需要每次都阅读迁移文件,但是它们被设计成人类可读的形式,这是为了便于你手动调整 Django 的修改方式。
    python manage.py makemigrations polls
    
  • sqlmigrate查看迁移后即将执行的sql语句
    python manage.py sqlmigrate polls 0001
    
    """
        BEGIN TRANSACTION
        --
        -- Create model Question
        --
        CREATE TABLE [polls_question] ([id] bigint NOT NULL PRIMARY KEY IDENTITY (1, 1), [question_text] nvarchar(200) NOT NULL, [pub_date] datetime2 NOT NULL);
        --
        -- Create model Choice
        --
        CREATE TABLE [polls_choice] ([id] bigint NOT NULL PRIMARY KEY IDENTITY (1, 1), [choice_text] nvarchar(200) NOT NULL, [votes] int NOT NULL, [question_id] bigint NOT NULL);
        CREATE INDEX [polls_choice_question_id_c5b4b260] ON [polls_choice] ([question_id]);
        ALTER TABLE [polls_choice] ADD CONSTRAINT [polls_choice_question_id_c5b4b260_fk_polls_question_id] FOREIGN KEY ([question_id]) REFERENCES [polls_question] ([id]);
        COMMIT;
    """
    
  • 模型同步
    运行 migrate 命令,在数据库里创建新定义的模型的数据表(在数据库中创建表):
    python manage.py migrate
    
  • 流程梳理
    • 修改settings.py数据库配置
    • 新建应用
    • 注册应用
    • 编辑 models.py 文件,改变模型
    • 运行 python manage.py makemigrations 为模型的改变生成迁移文件
    • 运行 python manage.py migrate 来应用数据库迁移
  • Django API
     python manage.py shell
    
    我们使用这个命令而不是简单的使用“python”是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。

四.Django管理页面

  • 创建管理员账号

    python manage.py createsuperuser
    """
        Username (leave blank to use 'administrator'): admin
        Email address:
        Password:
        Password (again):
        This password is too short. It must contain at least 8 characters.
        This password is too common.
        This password is entirely numeric.
        Bypass password validation and create user anyway? [y/N]: y
        Superuser created successfully.
    """
    

    启动django服务访问: http://127.0.0.1:5000/admin/ 即可用刚刚创建的账号登录.

  • 将创建的polls应用注册到管理页面
    只需要再做一件事:我们得告诉管理,问题 Question 对象需要一个后台接口。打开 polls/admin.py 文件,把它编辑成下面这样:

    # polls/admin.py
    from django.contrib import admin
    from .models import Question,Choice
    
    admin.site.register(Question)
    admin.site.register(Choice)
    

    注册完成后,管理页面即可显示添加的类:

    点击进入可进行下边操作:

    保存(Save) - 保存改变,然后返回对象列表。
    保存并继续编辑(Save and continue editing) - 保存改变,然后重新载入当前对象的修改界面。
    保存并新增(Save and add another) - 保存改变,然后添加一个新的空对象并载入修改界面。
    删除(Delete) - 显示一个确认删除页面。

  • 时间问题
    如果显示的 “发布日期(Date Published)” 和你创建它们的时间不一致,这意味着你可能没有正确的设置 TIME_ZONE 。改变设置,然后重新载入页面看看是否显示了正确的值。

    LANGUAGE_CODE = 'zh-hans'  # 设置中文
    TIME_ZONE = 'Asia/Shanghai'  # 设置国内时间
    USE_TZ = True
    """
    USE_TZ = True
    作用一:前端传递过来的DateFiled类型,经过模型类,存到数据库中,全部是0时区时间。
    作用二: 后端自己写的代码,只要是使用timezone获取的时间(已知的时间)(带时区的时间),django会自己转换,无需我们手动转换。
    作用三:模板显示问题:django模板,会将数据库中0时区的时间转换成当地时间,无需我们自己转换。
    """
    

五.Django视图和模板

在 Django 中,网页和其他内容都是从视图派生而来。每一个视图表现为一个 Python 函数(或者说方法,如果是在基于类的视图里的话)。Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据 URL 中域名之后的部分)。为了将 URL 和视图关联起来,Django 使用了 'URLconfs' 来配置。URLconf 将 URL 模式映射到视图。

  • 添加视图

    # polls/views.py
    from django.http import HttpResponse
    
    
    def index(request):
        return HttpResponse("这是index页面")
    
    
    def detail(request, question_id):
        return HttpResponse("You're looking at question %s." % question_id)
    
    
    def results(request, question_id):
        response = "You're looking at the results of question %s."
        return HttpResponse(response % question_id)
    
    
    def vote(request, question_id):
        return HttpResponse("You're voting on question %s." % question_id)
    

    把这些新视图添加进 polls.urls 模块里,只要添加几个 url() 函数调用就行:

    # polls/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        # ex: /polls/
        path('', views.index, name='index'),
      
        # ex: /polls/5/
        path('<int:question_id>/', views.detail, name='detail'),
      
        # ex: /polls/5/results/
        path('<int:question_id>/results/', views.results, name='results'),
      
        # ex: /polls/5/vote/
        path('<int:question_id>/vote/', views.vote, name='vote'),
    ]
    

    当某人请求你网站的某一页面时——比如说, "/polls/34/" ,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF 中设置了。然后 Django 寻找名为 urlpatterns 变量并且按序匹配正则表达式。在找到匹配项 'polls/',它切掉了匹配的文本("polls/"),将剩余文本——"34/",发送至 'polls.urls' URLconf 做进一步处理。在这里剩余文本匹配了 'int:question_id/',使得我们 Django 以如下形式调用 detail():

  • 编写有用的视图
    上边例子只是暂时用于占位的结果和投票页,接下来编写一个有用的视图,我们在 index() 函数里插入了一些新内容,让它能展示数据库里以发布日期排序的最近 5 个投票问题,以空格分割:
    我们在 index() 函数里插入了一些新内容,让它能展示数据库里以发布日期排序的最近 5 个投票问题,以空格分割:

    from django.http import HttpResponse
    from .models import Question
    
    def index(request):
        latest_question_list = Question.objects.order_by('-pub_date')[:5]
        output = ', '.join([q.question_text for q in latest_question_list])
        return HttpResponse(output)
    
    • 模板系统
      这里有个问题:页面的设计写死在视图函数的代码里的。如果你想改变页面的样子,你需要编辑 Python 代码。所以让我们使用 Django 的模板系统,只要创建一个视图,就可以将页面的设计从代码中分离出来。

      首先,在你的 polls 目录里创建一个 templates 目录。Django 将会在这个目录里查找模板文件。
      你项目的 TEMPLATES 配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 "templates" 子目录。

      在你刚刚创建的 templates 目录里,再创建一个目录 polls,然后在其中新建一个文件 index.html 。换句话说,你的模板文件的路径应该是 polls/templates/polls/index.html 。因为app_directories 模板加载器是通过上述描述的方法运行的,所以 Django 可以引用到 polls/index.html 这一模板了。

      模板命名空间
      虽然我们现在可以将模板文件直接放在 polls/templates 文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最好的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。

      <!--polls\templates\polls\index.html-->
      {% if latest_question_list %}
          <ul>
          {% for question in latest_question_list %}
              <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
          {% endfor %}
          </ul>
      {% else %}
          <p>No polls are available.</p>
      {% endif %}
      
    • 引用模板
      然后,让我们更新一下 polls/views.py 里的 index 视图来使用模板:

      from django.http import HttpResponse
      from django.template import loader
      from .models import Question
      
      def index(request):
          latest_question_list = Question.objects.order_by('-pub_date')[:5]
          template = loader.get_template('polls/index.html')
          context = {
              'latest_question_list': latest_question_list,
          }
          return HttpResponse(template.render(context, request))
      

      上述代码的作用是,载入 polls/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。用你的浏览器访问 "/polls/" ,你将会看见一个无序列表,列出了我们添加的 “What's up” 投票问题,链接指向这个投票的详情页。

    • 快捷函数: render()
      「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数,我们用它来重写 index() 视图:

      # polls/views.py
      from django.shortcuts import render
      from .models import Question
      
      def index(request):
          latest_question_list = Question.objects.order_by('-pub_date')[:5]
          context = {
              'latest_question_list': latest_question_list,
          }
          return render(request, 'polls/index.html', context)
      
    • 404异常
      现在,我们来处理投票详情视图——它会显示指定投票的问题标题。下面是这个视图的代码:

      # polls/views.py
      from django.http import HttpResponse, Http404
      from django.shortcuts import render
      from .models import Question
      
      # ...
      def detail(request, question_id):
          try:
              question = Question.objects.get(pk=question_id)
          except Question.DoesNotExist:
              raise Http404("Question does not exist")
          return render(request, 'polls/detail.html', {'question': question})
      

      这里有个新原则。如果指定问题 ID 所对应的问题不存在,这个视图就会抛出一个 Http404 异常。

      我们稍后再讨论你需要在 polls/detail.html 里输入什么,但是如果你想试试上面这段代码是否正常工作的话,你可以暂时把下面这段输进去:

      <!--polls/templates/polls/detail.html-->
      {{ question }}
      

      测试结果:

    • 快捷函数: get_object_or_404()
      尝试用 get() 函数获取一个对象,如果不存在就抛出 Http404 错误也是一个普遍的流程。Django 也提供了一个快捷函数,下面是修改后的详情 detail() 视图代码:

      # polls/views.py
      from django.shortcuts import render, get_object_or_404
      from .models import Question
      
      def detail(request, question_id):
          question = get_object_or_404(Question, pk=question_id)
          return render(request, 'polls/detail.html', {'question': question})
      

      也有 get_list_or_404() 函数,工作原理和 get_object_or_404() 一样,除了 get() 函数被换成了 filter() 函数。如果列表为空的话会抛出 Http404 异常。

  • 使用模板系统
    回过头去看看我们的 detail() 视图。它向模板传递了上下文变量 question 。下面是 polls/detail.html 模板里正式的代码:

    <!--polls/templates.polls/detail.html-->
    <h1>{{ question.question_text }}</h1>
    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
    {% endfor %}
    </ul>
    

    模板系统统一使用点符号来访问变量的属性。在示例 {{ question.question_text }} 中,首先 Django 尝试对 question 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)。

    在 {% for %} 循环中发生的函数调用:question.choice_set.all 被解释为 Python 代码 question.choice_set.all() ,将会返回一个可迭代的 Choice 对象,这一对象可以在 {% for %} 标签内部使用。

    • Django模板语言
      • 变量
        变量从上下文中输出一个值,上下文是一个类似于字典的对象,将键映射到值。变量被 {{ 和 }} 包围,如下所示:

        My first name is {{ first_name }}. My last name is {{ last_name }}.

        字典查找,属性查找和列表索引查找均以点符号实现:

        {{ my_dict.key }}
        {{ my_object.attribute }}
        {{ my_list.0 }}

      • 标签
        标签在渲染过程中提供了任意逻辑。这个定义是故意含糊的。例如,标签可以输出内容,或用作控制结构如 “if” 语句和 “for” 循环,或从数据库中抓取内容,甚至可以访问其他模板标签。标签被 {% 和 %} 包围,如下所示:

        {% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %}

      • 过滤器
        过滤器转换变量和标签参数的值。

        {{ django|title }}
        {{ my_date|date:"Y-m-d" }}

      • 注释

        {# this won't be rendered #}

    • 去除模板中的硬编码 URL
      还记得吗,我们在 polls/index.html 里编写投票链接时,链接是硬编码的:
      <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
      
      问题在于,硬编码和强耦合的链接,对于一个包含很多应用的项目来说,修改起来是十分困难的。然而,因为你在 polls.urls 的 url() 函数中通过 name 参数为 URL 定义了名字,你可以使用 {% url %} 标签代替它:
      <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
      
      如果你想改变投票详情视图的 URL,比如想改成 polls/specifics/12/ ,你不用在模板里修改任何东西(包括其它模板),只要在 polls/urls.py 里稍微修改一下就行:
      # polls/urls.py
      # ...
      path('specifics/<int:question_id>/', views.detail, name='detail'),
      # ...
      
    • 为URL名称添加命名空间
      举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?答案是:在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍作修改,加上 app_name 设置命名空间:
      from django.urls import path
      from . import views
      
      app_name = 'polls'
      urlpatterns = [
          path('', views.index, name='index'),
          path('<int:question_id>/', views.detail, name='detail'),
          path('<int:question_id>/results/', views.results, name='results'),
          path('<int:question_id>/vote/', views.vote, name='vote'),
      ]
      
      修改为指向具有命名空间的详细视图:
      <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
      

六.表单和通用视图

  • 表单

    • 实现简单表单
      更新上边教程中编写的投票详细页面的模板 ("polls/detail.html") ,让它包含一个 HTML

      元素:

      <!--polls/templates/polls/detail.html-->
      <form action="{% url 'polls:vote' question.id %}" method="post">
          {% csrf_token %}
          <fieldset>
              <legend><h1>{{ question.question_text }}</h1></legend>
              {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
              {% for choice in question.choice_set.all %}
              <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
              <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
              {% endfor %}
          </fieldset>
          <input type="submit" value="Vote">
      </form>
      

      表单说明:

      • 上面的模板在 Question 的每个 Choice 前添加一个单选按钮。 每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name 是 "choice" 。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念。
      • 我们将表单的 action 设置为 {% url 'polls:vote' question.id %},并设置 method="post"。使用 method="post" (而不是 method="get" )是非常重要的,因为提交这个表单的行为将改变服务器端的数据。当你创建一个改变服务器端数据的表单时,使用 method="post"。这不是 Django 的特定技巧。
      • forloop.counter 指示 for 标签已经循环多少次。
      • 由于我们创建一个 POST 表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 谢天谢地,你不必太过担心,因为 Django 自带了一个非常有用的防御系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。
    • vote()函数真实实现

      # polls/views.py
      from django.http import HttpResponse, HttpResponseRedirect
      from django.shortcuts import render, get_object_or_404, reverse
      from .models import Question, Choice
      
      def vote(request, question_id):
          question = get_object_or_404(Question, pk=question_id)
          try:
              selected_choice = question.choice_set.get(pk=request.POST['choice'])
          except (KeyError, Choice.DoesNotExist):
              # Redisplay the question voting form.
              return render(request, 'polls/detail.html', {
                  'question': question,
                  'error_message': "You didn't select a choice.",
              })
          else:
              selected_choice.votes += 1
              selected_choice.save()
              # Always return an HttpResponseRedirect after successfully dealing
              # with POST data. This prevents data from being posted twice if a
              # user hits the Back button.
              return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
      

      vote()函数说明:

      • request.POST 是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中, request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串。
        注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动。
      • 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。
      • 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponse 、 HttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL。
        正如上面的 Python 注释指出的,在成功处理 POST 数据后,你应该总是返回一个 HttpResponseRedirect。
      • 在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:'/polls/3/results/'
    • results()函数真实实现
      当对 Question 进行投票后, vote() 视图将请求重定向到 Question 的结果界面。让我们来编写这个视图:

          # polls/views.py
          from django.shortcuts import render, get_object_or_404
          from .models import Question
          
          def results(request, question_id):
              question = get_object_or_404(Question, pk=question_id)
              return render(request, 'polls/results.html', {'question': question})
      
    • results模板

      <!--polls/templates/polls/results.html-->
      <h1>{{ question.question_text }}</h1>
      
      <ul>
          {% for choice in question.choice_set.all %}
              <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
          {% endfor %}
      </ul>
      
      <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
      

      在你的浏览器中访问 /polls/1/ 然后为 Question 投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。 如果你提交时没有选择任何 Choice,你应该看到错误信息。

  • 竞争条件和F()
    我们的 vote() 视图代码有一个小问题。代码首先从数据库中获取了 selected_choice 对象,接着计算 vote 的新值,最后把值存回数据库。如果网站有两个方可同时投票在 同一时间 ,可能会导致问题。同样的值,42,会被 votes 返回。然后,对于两个用户,新值43计算完毕,并被保存,但是期望值是44,这个问题被称为竞争条件,我们可以用F()来处理这个问题。
    vote()函数修改:

    # polls/views.py
    from django.db.models import F
    
    def vote():
      # ...
      selected_choice.votes = F('votes') + 1
      # ...
    

    当 Django 遇到 F() 的实例时,它会覆盖标准的 Python 运算符来创建一个封装的 SQL 表达式;在本例中,它指示数据库递增由 selected_choice.votes 表示的数据库字段。
    无论 selected_choice.votes 上的值是多少,Python 永远不会知道它——它完全由数据库处理。通过 Django 的 F() 类,Python 所做的就是创建 SQL 语法来引用这个字段并描述操作。

  • 通用视图
    根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做 “通用视图” 系统。
    将上文的投票应用转换成使用通用视图系统,这样我们可以删除许多我们的代码。我们仅仅需要做以下几步来完成转换,我们将:

    • 转换 URLconf。
    • 删除一些旧的、不再需要的视图。
    • 基于 Django 的通用视图引入新的视图。
    • 改良URLconf
    from django.urls import path
    from . import views
    
    app_name = 'polls'
    urlpatterns = [
        path('', views.IndexView.as_view(), name='index'),
        path('<int:pk>/', views.DetailView.as_view(), name='detail'),
        path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
        path('<int:question_id>/vote/', views.vote, name='vote'),
    ]
    
    • 改良视图
      删除旧的 index, detail, 和 results 视图,并用 Django 的通用视图代替。打开 polls/views.py 文件,并将它修改成:
    from django.http import HttpResponseRedirect
    from django.shortcuts import get_object_or_404, render
    from django.urls import reverse
    from django.views import generic
    from .models import Choice, Question
    from django.db.models import F
    
    
    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]
    
    
    class DetailView(generic.DetailView):
        model = Question
        template_name = 'polls/detail.html'
    
    
    class ResultsView(generic.DetailView):
        model = Question
        template_name = 'polls/results.html'
            
    def vote(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        try:
            selected_choice = question.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the question voting form.
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "You didn't select a choice.",
            })
        else:
            selected_choice.votes = F('votes') + 1
            selected_choice.save()
            # Always return an HttpResponseRedirect after successfully dealing
            # with POST data. This prevents data from being posted twice if a
            # user hits the Back button.
            return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
    

    详解:

    • 我们在这里使用两个通用视图: ListView 和 DetailView 。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。
    • 每个通用视图需要知道它将作用于哪个模型。 这由 model 属性提供。
    • DetailView 期望从 URL 中捕获名为 "pk" 的主键值,所以我们为通用视图把 question_id 改成 pk 。
    • 默认情况下,通用视图 DetailView 使用一个叫做 /_detail.html 的模板。在我们的例子中,它将使用 "polls/question_detail.html" 模板。template_name 属性是用来告诉 Django 使用一个指定的模板名字,而不是自动生成的默认名字。 我们也为 results 列表视图指定了 template_name —— 这确保 results 视图和 detail 视图在渲染时具有不同的外观,即使它们在后台都是同一个 DetailView 。
    • 类似地,ListView 使用一个叫做 /_list.html 的默认模板;我们使用 template_name 来告诉 ListView 使用我们创建的已经存在的 "polls/index.html" 模板。
    • 在之前的教程中,提供模板文件时都带有一个包含 question 和 latest_question_list 变量的 context。对于 DetailView , question 变量会自动提供—— 因为我们使用 Django 的模型(Question), Django 能够为 context 变量决定一个合适的名字。然而对于 ListView, 自动生成的 context 变量是 question_list。为了覆盖这个行为,我们提供 context_object_name 属性,表示我们想使用 latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的 context 变量 —— 这是一种更便捷的方法,告诉 Django 使用你想使用的变量名。

    启动服务器,使用一下基于通用视图的新投票应用。

七.静态文件

除了服务端生成的 HTML 以外,网络应用通常需要一些额外的文件——比如图片,脚本和样式表——来帮助渲染网络页面。在 Django 中,我们把这些文件统称为“静态文件”。

  • 自定义应用的界面和风格
    首先,在你的 polls 目录下创建一个名为 static 的目录。Django 将在该目录下查找静态文件,这种方式和 Diango 在 polls/templates/ 目录下查找 template 的方式类似。

    Django 的 STATICFILES_FINDERS 设置包含了一系列的查找器,它们知道去哪里找到 static 文件。AppDirectoriesFinder 是默认查找器中的一个,它会在每个 INSTALLED_APPS 中指定的应用的子文件中寻找名称为 static 的特定文件夹,就像我们在 polls 中刚创建的那个一样。管理后台采用相同的目录结构管理它的静态文件。

    在你刚创建的 static 文件夹中创建一个名为 polls 的文件夹,再在 polls 文件夹中创建一个名为 style.css 的文件。换句话说,你的样式表路径应是 polls/static/polls/style.css。因为 AppDirectoriesFinder 的存在,你可以在 Django 中以 polls/style.css 的形式引用此文件,类似你引用模板路径的方式。

    静态文件命名空间
    虽然我们可以像管理模板文件一样,把 static 文件直接放入 polls/static (而不是创建另一个名为 polls 的子文件夹),不过这实际上是一个很蠢的做法。Django 只会使用第一个找到的静态文件。如果你在 其它 应用中有一个相同名字的静态文件,Django 将无法区分它们。我们需要指引 Django 选择正确的静态文件,而最好的方式就是把它们放入各自的 命名空间 。也就是把这些静态文件放入 另一个 与应用名相同的目录中。

    • 新建静态文件

      /*polls/static/polls/style.css*/
      li a {
          color: green;
      }
      
    • 使用静态文件

      <!--polls/templates/polls/index.html 开头添加下列代码-->
      {% load static %}
      
      <link rel="stylesheet" href="{% static 'polls/style.css' %}">
      

      {% static %} 模板标签会生成静态文件的绝对路径。
      重启项目:

参考文档

https://docs.djangoproject.com/zh-hans/4.1/

posted @ 2022-11-14 14:41  屁桃  阅读(70)  评论(0编辑  收藏  举报