zoukankan      html  css  js  c++  java
  • 第一个 Django 应用

    1. 创建项目

    1.1 新建项目

    首先新建一个项目,名为 mysite,命令如下:

    django-admin startproject mysite		# 或用 django-admin.py
    

    运行成功,生成一些目录:

    mysite/
        manage.py			# 管理 Django 项目的命令行工具
        mysite/				# 包,包含项目
            __init__.py
            settings.py		# 配置文件
            urls.py			# 路由文件
            wsgi.py			# WSGI 接口,web 服务器进入点,提供底层网络通信功能,无需关心
    

    1.2 启动服务器

    python manage.py runserver			# 默认以 8000 端口开启
    python manage.py runserver 8080		# 指定端口
    

    执行成功,看到输出如下信息:

    在浏览器中访问 http://127.0.0.1:8000/,看到以下信息,表示开启成功(Django2.x 以下版本不一样):

    1.3 新建应用

    现在我们新建一个应用(app),名为 polls,命令如下:

    cd mysite			# 切好到项目里面
    python manage.py startapp polls
    

    执行成功后,可以看到 mysite 中多了一个 polls文件夹,打开 polls,里面包含以下文件:

    polls/
        __init__.py
        admin.py			# Django 提供的后台管理程序
        apps.py	
        migrations/			# 数据库表生成记录
            __init__.py
        models.py			# 模型(与数据库相关)
        tests.py			# 测试文件
        views.py			# 视图(一个视图函数表示一个页面)
    

    项目与应用的区别

    • 一个项目可以有一个或多个应用
    • 一个应用往往是用来实现某个功能,如:博客、日程管理系统等
    • 一个应用可以属于多个项目

    1.4 第一个视图

    一个视图函数表示一个 Web 页面,在 polls/views.py 中编写:

    from django.shortcuts import render, HttpResponse
    
    def index(request):
        """首页"""
        return HttpResponse('Is Ok!')
    

    要调用视图,我们需要先配置 urlconf,让 Django 找到我们的视图函数,在此之前我们先把 app 添加到 settings.py 中:

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
    
        'polls',		# 最好空一行,以示区分
    ]
    

    配置 urlconf

    编写 mysite/urls.py

    from django.contrib import admin
    from django.urls import path, include
    from polls import views			# 导入视图函数
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index, name='index'),
    ]
    

    访问 http://127.0.0.1:8000/index/,如果不出意外的话,会看到 Is Ok! 的字样~

    多级路由

    上面我们只创建了一个 app,因此 url 路径配置在项目 mysite/urls.py中毫无影响,但是当有多个应用且有多个相同的名字的视图时,为了避免冲突,就需要用到多级路由了。

    1. 配置 mysite/urls.py
    from django.contrib import admin
    from django.urls import path, include		# 引入 include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('polls/', include('polls.urls')),		# include 就相当于多级路由,它会将去掉 url 前面的正则,将剩余字符串传递给下一级路由,即 polls/urls.py 来判断
    ]
    
    1. 在应用 polls 目录下新建一个 urls.py 文件,配置如下:
    from django.urls import path
    from polls import views			# 导入视图函数
    
    urlpatterns = [
        path('index/', views.index, name='index'),
        # url(r'^index/', views.index, name='index'),		# django2.x 以前版本
    ]
    

    那么访问地址将变成 http://127.0.0.1:8000/polls/index/

    2. 模型和后台管理

    2.1 数据库配置

    在 Django 中模型即指数据库,Django 内置 SQLite 数据库,可以直接使用它。但是 SQLite 一般仅用来测试使用,实际开发中一般很少不会使用。如果要使用其他数据库,需要配置 settings,并安装相应驱动,下面我们以 MySQL 为例。

    常用数据库配置:

    'django.db.backends.sqlite3',
    'django.db.backends.postgresql',
    'django.db.backends.mysql',
    'django.db.backends.oracle',
    
    1. 设置 settings.py
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'test',     # 数据库名字,需要事先创建
            'USER': 'root',     # 用户名
            'PASSWORD': '',		# 密码
            'HOST': '',      # 留空默认为 localhost,数据库主机名
            'PORT': '3306',
        }
    }
    
    1. 安装 pymysql 模块
    pip install pymysql
    
    1. 激活 MySQL,打开项目mysite/__init__.py 文件,配置如下:
    import pymysql
    pymysql.install_as_MySQLdb()
    

    时区和语言

    Django 默认使用 UTC 时区,以及英文,我们可以将其修改为东八区和中文:

    LANGUAGE_CODE = 'zh-hans'
    TIME_ZONE = 'Asia/Shanghai'
    

    2.2 创建模型 Model

    Django 通过 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操作数据库,即使不懂 SQL 语句也可以操作数据库。

    我们只需在模型中创建相应的 以及字段即可,然后再执行命令,Django会自动帮我们生成数据表:

    • 类:对应数据表名
    • 字段:对应数据表的列

    在此之前我们创建了一个投票应用 polls,现在我们将创建两个数据表:问题表 Question(用来存储问题以及发布事件)、以及选择人们的选择表Choice

    下面我们编写 polls/models.py

    from django.db import models
    
    class Question(models.Model):		# 每个类必须继承 models.Model
        """数据表:问题表"""
        question_text = models.CharField(max_length=2000)       # 问题内容
        pub_date = models.DateTimeField('date published')       # 发布日期
    
    
    class Choice(models.Model):
        """数据表:选择表"""
        choice_text = models.CharField(max_length=200)      # 选择
        votes = models.IntegerField(default=0)      # 是否已经投票
        question = models.ForeignKey(Question, on_delete=models.CASCADE)  # 外键关联
    
    • 在上面有些字段我们指定了最长宽度 max_length,这将限制其输入范围,非必须但是最好有所限制

    • 另外我们通过外键(数据库内容) ForeignKey将两个表关联起来,也就是这两张表是一对多关系。

    • 一个问题可以有多个选择,除此之外数据表间关联还有 一对一、以及多对多关系,后面讲详细介绍。


    模型创建和数据迁徙

    接下来就是创建模型,执行 python manage.py makemigrations polls,会看到以下提示:

    这表示在 pollsmigrations001_initial.py 文件中创建相关模型记录,当我们对数据表操作时,会在上面有相应记录,保存在我们的电脑磁盘上面。

    接着我们要将数据迁徙到真正的数据库中去,执行 python manage.py migrate

    在 Pycharm 中打开 SQLite ,可以看到创建很多数据表:


    Tips

    • 创建模型时,我们不需要创建 id,Django 会自动帮我们创建
    • 外键字段,Django会在其名字之上加上一个 _id,表示与主表的 ID 进行关联
    • Django 运行随时修改模型,只需按照以下三步走,即可不丢失数据
      • 修改 models.py
      • 执行 python manage.py makemigrations app_name 为改动创建迁徙记录
      • 执行 python manage.py migrate ,将操作同步至数据库

    2.3 操作模型

    上面我们通过相应命令创建了模型,那么我们该如何操作数据表中内容呢?Django为我们提供了一系列的 API,可以很方便地就能操作数据。

    1. 进入 Django 提供的 shell 交互环境 python manage.py shell
    >>> from polls.models import Question, Choice	# 导入模型类
    >>> Question.objects.all()		# 获取所有 question 对象
    <QuerySet []>					# 因为里面还没数据,所有是空的
    
    >>> from django.utils import timezone	# 导入 Django 内置的 timezone 模块,获取时间,来自于依赖库 pytz
    >>> q = Question(question_text="What's new?", pub_date=timezone.now())		# 创建 question 对象
    >>> q.save()				# 保存到数据库
    >>> q.id					# 通过对象属性调用方式,访问模型中字段的值
    1
    >>> q.question_text
    "What's new?"
    >>> q.pub_date
    datetime.datetime(2019, 2, 28, 8, 10, 18, 766500, tzinfo=<UTC>)
    
    # 修改字段的值,再保存
    >>> q.question_text = "What's up?"
    >>> q.save()
    
    # .all() 方式查询数据库中所有对象,这里是 question 对象
    >>> Question.objects.all()
    <QuerySet [<Question: Question object (1)>]>
    

    在上面我们访问 Question 中所有对象时,得到是一个 object 对象,这样显示很不友好,为此我们可以为模型添加一个 __str()__ 方法,使其能够更具有可读性:

    from django.db import models
    import datetime
    from django.utils import timezone
    
    class Question(models.Model):
    	...
        def __str__(self):
            return self.question_text		# 返回的是 question_text,而不是 object
    
    class Choice(models.Model):
    	...
        def __str__(self):
            return self.choice_text
    
    1. 重新打开一个 shell,来看看其他 API
    >>> from polls.models import Question, Choice
    >>> Question.objects.all()
    <QuerySet [<Question: What's up?>]>
               
    # 关键字查询 filter() 方法过滤 id=1
    >>> Question.objects.filter(id=1)		
    <QuerySet [<Question: What's up?>]>
    
    # 查询 question_text 以 What 开头的 question
    >>> Question.objects.filter(question_text__startswith="What")
    <QuerySet [<Question: What's up?>]>
    
    # 导入 timezone
    # 查询今年发布的问题
    >>> from django.utils import timezone
    >>> current_year = timezone.now().year		# 获取今年时间:2019
    >>> Question.objects.get(pub_date__year=current_year)		#  __year=2019
    <Question: What's up?>
           
    # 查询不存在的 ID,出现异常
    >>> Question.objects.get(id=2)
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "E:Python_virtualenvsfor_djangolibsite-packagesdjangodbmodelsmanager.py", line 82, in manager_method
        return getattr(self.get_queryset(), name)(*args, **kwargs)
      File "E:Python_virtualenvsfor_djangolibsite-packagesdjangodbmodelsquery.py", line 399, in get
        self.model._meta.object_name
    polls.models.Question.DoesNotExist: Question matching query does not exist.
    
    # pk 即 primary key 缩写,与 id 等同
    >>> Question.objects.get(pk=1)
    <Question: What's up?>
               
    >>> q = Question.objects.get(pk=1)		# 创建 Question 对象
    >>> q.choice_set.all()			# 通过 数据表名_set.all() 方式获得与其关联的数据表的所有对象
    <QuerySet []>
               
    # 创建三个 choices
    >>> q.choice_set.create(choice_text='Not much', votes=0)
    <Choice: Not much>
    >>> q.choice_set.create(choice_text='The sky', votes=0)
    <Choice: The sky>
    >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
    >>> c.question
    <Question: What's up?>
               
    >>> q.choice_set.all()
    <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
    >>> q.choice_set.count()
    3
    
    >>> Choice.objects.filter(question__pub_date__year=current_year)
    <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
    >>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
    >>> c.delete()		# delete() 删除对象
    (1, {'polls.Choice': 1})
    

    上面是官方文档提供的一些例子,还有更多的有关 API 的操作,我们将在后面学习到。

    总结

    1、创建对象
    
    q = Question.objects.all()		# QuerySet 对象集合
    q = Question.objects.filter()	# QuerySet 对象集合
    q = Question.objects.get()		# QuerySet 对象,一个
    
    
    2、插入数据
    
    q = Question(question_text="What's up?", pub_date=timezone.now())	# 方法一
    q.save()
    
    	访问数据:
    		q.id
    		q.pub_date
    
    Question.objects.create(question_text="What's up?", pub_date=timezone.now())	# 方法二
    
    3、查询数据
    
    q = Question.objects.get(id=1)		# 通过 q.数据表名_set.all()  方式获得与其关联的数据表对象
    q.choice_set.all()		# <QuerySet [<Choice: Not much>, <Choice: The sky>]>
    
    
    4、删除数据
    q.delete()
    

    2.4 后台管理 Admin

    Django 为我们提供了一个后台管理工具 Admin,可以对数据进行简单的增删改查等,简单易用,并支持拓展。

    创建管理员用户

    python manage.py createsuperuser		# 运行命令,新建用户名、邮箱和密码
    # username: xxx
    # email:xxx@qq.com
    # password:xxx
    

    注册应用

    将模型中的类注册到 polls/admin.py 中,接收站点的管理:

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

    访问 Admin

    访问 http://127.0.0.1:8000/admin/,输入刚才创建的用户名和密码:

    样式定制

    修改 polls/admin.py

    from django.contrib import admin
    from polls.models import Question, Choice
    
    # 定制样式,更多样式见官方文档
    class QuestionAdmin(admin.ModelAdmin):
        list_display = ('id', 'question_text', 'pub_date')		# 要显示的字段
        list_editable = ('question_text', 'pub_date')		# 可编辑的
    
    admin.site.register(Question, QuestionAdmin)
    admin.site.register(Choice)
    

    3. 模板和视图

    3.1 编写视图函数

    Django 中每一个网页都是通过视图函数来处理的,在 polls 应用中,我们将创建以下四个视图:

    URL 视图函数 模板 说明
    /index/ index() index.html 主页,显示最新问题
    /results/ results() results.html 投票结果
    /detail/ detail() detail.html 问题详细描述
    /vote/ vote() vote.html 投票动作,是否投票
    1. 首先我们配置好 mysite/urlconf,以便能够找到相应视图函数:
    from django.contrib import admin
    from django.urls import path, include
    from polls import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index, name='index'),
        path('detail/', views.detail, name='detail'),
        path('results/', views.results, name='results'),
        path('vote/', views.vote, name='vote'),
    ]
    
    1. 编写视图 polls/views.py
    from django.shortcuts import render, HttpResponse
    
    def index(request):
        """首页"""
        return HttpResponse('Is Ok!')
    
    def detail(request):
        """问题详细描述"""
        return HttpResponse('问题详细描述')
    
    def results(request):
        """投票结果"""
        return HttpResponse('投票结果')
    
    def vote(request):
        """是否投票"""
        return HttpResponse('是否已经投票')
    

    现在视图函数已经创建好了,我们可以访问相应视图看看 http://127.0.0.1:8000/detail/ 返回的是什么。

    3.2 使用模板

    3.2.1 创建模板

    在上面的视图函数中,我们使用了 HttpResponse 对象返回了一个字符串,而实际开发中,我们得到的都是一个 HTML页面。这就需要用到我们的模板系统了。

    在 polls 目录下创建一个 templates 目录,再在 templates 目录下创建一个新的 polls目录。然后在 polls 中创建相应的模板文件(其路径polls/templates/polls/),如:index.html/detail.html 等。


    为什么要再多创建一个 polls 目录

    当有另一个 app 也有 index.html 时,可以避免 Django 匹配错误。

    配置 templates

    要想 Django 能找到 templates 中的模板文件,那么还要配置下 settings

    # 当 templates 在 mysite/templates 下,不要添加 polls
    TEMPLATE_DIRS = (os.path.join(BASE_DIR, 'polls', 'templates'),)		
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')],		# 添加这行
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    

    3.2.2 渲染模板

    1. 渲染模板,Django 为我们提供了一个 render() 函数,用于渲染模板文件,render()语法格式:
    render(request, template_name, context=None)	# 三个参数,第一个固定为请求对象request,第二个是要渲染的模板文件,第三个是个可选参数,即要传递的数据,是个字典格式
    

    编辑 polls/views.py

    from django.shortcuts import render, HttpResponse
    from .models import Question
    
    def index(request):
        """首页"""
        question_list = Question.objects.all()		# 取出 Question 中所有 question 
    
        return render(request, 'polls/index.html', {'question_list': question_list})
    
    def detail(request, question_id):
        """问题详细描述"""
        question = Question.objects.get(id=question_id)
    
        return render(request, 'polls/detail.html', {'question': question})
    

    当我们访问 http://127.0.0.1:8000/index/ 时,index() 函数会处理我们的视图。它从 Question 取出所有的问题对象,并渲染到模板中。

    1. 创建模板文件 polls/templates/polls/index.html
    <!--index.html-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        {% for question in question_list %}
        <!-- 相当于访问 <a href='detail/1/'></a>-->
            <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a> </li>
        {% endfor %}	
    </body>
    </html>
    

    在模板文件 index.html 中,我们使用 for 循环将所有问题循环,当我们点击其中的 a 标签的链接时,将会被定位到 http://127.0.0.1:8000/detail/1 中。

    1. 模板文件 polls/templates/polls/detail.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Detail</title>
    </head>
    <body>
        <h1>{{ question.question_text }}</h1>
        <ul>
            {% for choice in question.choice_set.all %}
                <li>{{ choice.choice_text }}</li>
            {% endfor %}
        </ul>
    </body>
    </html>
    
    1. 配置 mysite/urls.py
    from django.contrib import admin
    from django.urls import path, include
    from polls import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index, name='index'),
        
        # 我们将 'detail/' 修改为: 'detail/<int:question_id>',以可匹配 http://127.0.0.1:8000/detail/1 这样的路径
        path('detail/<int:question_id>', views.detail, name='detail'),
    ]	
    

    在这里我们将 'detail/' 修改为: 'detail/<int:question_id>',以可匹配 http://127.0.0.1:8000/detail/1 这样的路径。其中 <int: question_id> 将匹配到一个正整数,另外不要忘了在视图函数中也要接收相应 question_id

    def detail(request, question_id):
        """问题详细描述"""
        question = Question.objects.get(id=question_id)
    
        return render(request, 'polls/detail.html', {'question': question})
    

    这里我们用的是 Django 提供的模板语言,将数据库中的数据显示在页面上,后面将详细介绍。

    3.3 返回 404 错误

    当我们访问不存在的路径时,会返回一个 Http404,我们可以定制下让其返回我们想要的内容,编辑 polls/views.py

    from django.http import 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 不存在")
        return render(request, 'polls/detail.html', {'question': question})
    

    另外 Django 也为我们提供了一个快捷函数 get_object_or_404(),只需一行即可替代上面多行:

    from django.shortcuts import get_object_or_404, render
    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_object_or_404():替代的是 get() 方法
    • get_list_or_404():替代的是 filter() 方法

    3.4 URL 命名空间

    什么是 URL 的命名空间呢?就是给每一个 URL 路径,添加一个 别名,它有如下几点好处:

    • 当有多个 app 时,可以更好地区分是哪个 app 的路径
    • 避免硬编码,在上面 index.html 中,我们使用的就是 URL 命名空间,而不是 <a href='/detail/{{question.id}}' 这样的硬编码。这样在我们修改匹配方法时,不需要做大量的修改。

    添加命名空间

    from django.contrib import admin
    from django.urls import path, include
    from polls import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index, name='index'),			# 其中 name='index' 即为 URL的 命名空间
        path('detail/<int:question_id>', views.detail, name='detail'),	
    ]
    

    当有多个应用时

    当有多个应用时,我们只需在 urls.py 中添加一个 app_name ,并在使用时带上它即可:

    ...
    app_name = 'polls'		# 添加这行
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index, name='index'),			# 其中 name='index' 即为 URL的 命名空间
        path('detail/<int:question_id>', views.detail, name='detail'),	
    ]
    

    使用时,一定要记得带上 app_name

    <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
    

    4. 表单和通用视图

    在创建表单之前,我们先来分析下程序的整体运行流程:

    • 访问首页 index,将所有问题都显示出来
    • 点击问题,跳转到 detail,显示详细问题,并显示投票选项
    • 当用户投票后,跳转到 results 结果页面,并询问是否还要继续投票。

    从流程中可以看出,我们要在问题详细页面提供单选框,以供用户选择,下面我们来创建第一个表单:

    4.1 Form 表单

    1. 编写 polls/detail.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Detail</title>
    </head>
    <body>
    <!--问题-->
    <h1>{{ question.question_text }}</h1>
    
    <!-- 错误信息 -->
    {% if error_message %}
        <p>{{ error_message }}</p>
    {% endif %}
    
    
    <form action="{% url 'vote' question.id %}" method="post">
        {% csrf_token %}        <!--csrf 攻击,表单提交必须带上这个-->
    
        <!-- 通过 question.choice_set.all 获得所有 Choice 选项 -->
        {% for choice in question.choice_set.all %}             <!--choice1、choice2-->
            <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
            <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>
        {% endfor %}
    
        <!-- 提交 -->
        <input type="submit" value="vote">  
    </form>
    </body>
    </html>
    
    • 在上面 detail.html 模板文件中,我们创建了一个表单,当用户点击提交时,会被提交到 action 对应的 URL 中去。
    • 在表单中,我们通过 question.choice_set.all 获得所有 Choice 选项,并循环它。
    • 再定义了一个单选框 radio,提交到服务器的键为 choice,值为选项的 id。
    • 另外要注意的是 form 表单发送 post 请求时,务必带上 {% csrf_token %},否则将被禁止提交。
    1. 配置 mysite/urls.py
    # /index/
    path('index/', views.index, name='index'),
    # /detail/1/
    path('detail/<int:question_id>', views.detail, name='detail'),
    # /results/1/
    path('results/<int:question_id>', views.results, name='results'),
    # /vote/1/
    path('vote/<int:question_id>', views.vote, name='vote'),
    
    1. 编写 polls/views.py
    from django.shortcuts import render, HttpResponse, get_object_or_404, redirect
    from .models import Question, Choice
    from django.http import HttpResponseRedirect
    from django.urls import reverse
    
    def vote(request, question_id):
        """处理投票"""
        print(question_id)
        question = get_object_or_404(Question, id=question_id)
        try:
            choice_id = request.POST.get('choice', None)
            print(choice_id)
            selected_choice = question.choice_set.get(id=choice_id)
        except (KeyError, Choice.DoesNotExist):
            # choice 没找到,重新返回表单页面,并给出提示信息
            return render(request, 'polls/detail.html', {'question': question, 'error_message': '你没用选择选项!'})
        else:
            selected_choice.votes += 1
            selected_choice.save()
            ret = reverse('results', args=(question.id,))       # /results/1
            return HttpResponseRedirect(ret)
    
    • question_id 为问题所对应的 id
    • detail.html 模板中,我们将选项的 id 提交到了后台,通过 request.POST.get('choice') 我们可以获得用户选择的选项 id
    • 当没有对应的 choice_id 时,重新返回表单页面,并给出错误信息
    • 当有相应 choice_id 时,对应 vote 则加 1,最后重定向到投票结果页面 results
    1. 编写 polls/results.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>结果</title>
    </head>
    <body>
        <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 'detail' question.id %}">Vote again?</a>
    </body>
    </html>
    

    至此一个简单的公共投票系统已大致编写完成,以下为演示:

    4.2 通用视图

    在视图 polls/views中,我们写了大量的类似于 index() 的重复代码,存在冗余问题。

    Django 为我们提供了一种 通用视图系统,将常见的模式抽象画,可以删去很多冗余代码。为此我们需要以下三个步骤:

    • 转换 urlconf
    • 删除一些旧的、不需要的视图
    • 基于通用视图,引入新的视图

    转换 URLconf

    编辑 mysite/urls.py

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.IndexView.as_view(), name='index'),
        path('detail/<int:pk>', views.DetailView.as_view(), name='detail'),
        path('results/<int:pk>', views.ResultsView.as_view(), name='results'),
        path('vote/<int:question_id>', views.vote, name='vote'),
    ]
    

    在这里我们将 question_id 修改为 pk,这是因为通用视图从 url 中匹配的将是主键 pk。

    修改视图

    from django.views import generic
    
    
    class IndexView(generic.ListView):
        template_name = 'polls/index.html'  # 模板名称
        context_object_name = 'question_list'   # 返回给模板的变量
    
        def get_queryset(self):
            return Question.objects.all()
    
    
    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):
        pass
    
    • ListView:显示对象的列表
    • DetaiView:显示特定类型对象详细页面
    • context_object_name:返回给模板的变量{'question_list':question_list} 中的 question_list
    • DetaiView:匹配的是 URL 中的 pk 主键
    • template_name:返回的模板文件,格式为 <app_name>/<model name>_list.html

    更多有关通用视图:https://docs.djangoproject.com/zh-hans/2.1/topics/class-based-views/

    5. 测试

    测试是实际开发中不可或缺的一部分,它可以:

    • 检验程序是否符合预期
    • 及时发现问题,节省开发时间
    • 更有利团队合作等

    测试分为手动测试和自动测试,手动测试往往费时费力,效率低下。我们可以借助一些测试模块,如:TestCase,自动帮我们完成测试工作,Django也有自动测试程序,它也是基于 TestCase 模块来实现的。

    在模型 models.py 中,我们给 Question 定义了一个 was_published_recently() 方法,用于返回问题是否是最近发布的,当 Question 在最近一天发布时返回 True

    class Question(models.Model):
        """数据表:问题表"""
        question_text = models.CharField(max_length=2000)       # 问题内容
        pub_date = models.DateTimeField('date published')       # 发布日期
    
        def __str__(self):
            return self.question_text
    
        def was_published_recently(self):
            # 当前时间减去前一天,与问题发布时间比较
            return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
    

    5.1 验证 bug

    进入 Django shell 环境:

    >>> import datetime
    >>> from django.utils import timezone
    >>> from polls.models import Question
    
    # 创建一个在发布日期 30 天后的问题对象
    >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
    
    # 测试返回值,发现也是 True
    >>> future_question.was_published_recently()
    True
    

    我们创建了一个在发布日期 30 天后的问题,测试发现还是返回 True,也就是说这里被允许在未来时间发布问题,这就是个 bug。

    5.2 测试 bug

    编写 polls/tests.py

    from django.test import TestCase
    import datetime
    from django.utils import timezone
    from .models import Question
    
    class QuestionModelTests(TestCase):
    
        def test_was_published_recently_with_future_question(self):
        # 创建一个 pub_date 是未来30天后的 Question 示例,然后检查 was_published_recently() 的返回值,它应该是 False
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertIs(future_question.was_published_recently(), False)
    

    执行 python manage.py test polls,会看到结果:

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    F
    ======================================================================
    FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "E:Python_virtualenvsfor_djangoProjectsmysitepolls	ests.py", line 11, in test_was_published_recently_with_future_qu
    estion
        self.assertIs(future_question.was_published_recently(), False)
    AssertionError: True is not False
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.016s
    
    FAILED (failures=1)
    Destroying test database for alias 'default'...
    

    我们创建了一个 pub_dae 值为 30 天后的 Question 实例,用 assertls() 方法判断是否返回 False,结果发现返回 True。

    5.3 修改 bug

    我们要让 pub_date 是未来某天时, Question.was_published_recently() 返回 False,修改 polls/models.py

    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    

    再进行测试,发现测试通过。测试在项目开发中很重要,也很常用,在这里我们只是做个大概的了解,到后面再详细的探讨。

    6. 静态文件

    静态文件即 Web 应用程序所要用到的一些必要文件,如:图片、JS 脚本、CSS 样式等。一个完整的 Web 应用应该有自己独立静态文件、模板文件,也就是说需要和项目本身区分开。

    在应用 polls 下新建一个 static 的目录,再新建一个以应用名字为名的文件夹,最后再分类存储各种静态文件,其目录结构是这样的:

    配置静态文件

    与模板 templates 一样,再使用前,需要先配置好静态文件,这样 Django 才能找到,编辑 settings.py

    STATIC_URL = '/static/'
    STATICFILES_DIRS = (
        os.path.join(BASE_DIR, 'polls', 'static'),
    )       # 一定不要忘记最后的逗号
    

    使用静态文件

    polls/static/polls/ 下创建一个 images 目录用来存储图片,再创建一个 css 目录用来存储 CSS 文件。然后在新建一个 style.css 的文件。

    下面我们来给首页 index.html 添加背景图片,编写以下代码:

    li a{
        color: red;
    }
    
    body {
        background: white url("images/2.png") no-repeat;
    }
    

    然后在 index.html 中来加载 style.css 文件:

    {% load static %}       <!--引入 static-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <!--再把 style.css 加载进来 -->
        <link rel="stylesheet" href="{% static 'polls/css/style.css' %}">       
    </head>
    <body>
        {% for question in question_list %}
            <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a> </li>
        {% endfor %}
    </body>
    </html>
    

    我们再刷新下,发现已经给首页添加好了背景图片。除此之外我们还可以在模板文件中直接使用静态文件,如:在模板中使用 jQuery

    # 同样地,也要先引入 static
    {% load static %}
    <script src="{% static 'polls/js/jquery-3.1.1.js' %}"></script>
    
  • 相关阅读:
    4Sum
    3Sum Closest
    3Sum
    Longest Common Prefix
    Roman to Integer
    thinkphp3.2自定义配置文件
    centos7下git的使用和配置
    git 报错
    Git服务器安装详解及安装遇到问题解决方案
    centos GIT安装
  • 原文地址:https://www.cnblogs.com/midworld/p/10467998.html
Copyright © 2011-2022 走看看