《Python编程:从入门到实践》 (3)Django

《Python编程 从入门到实践》第二部分阅读笔记:Django

项目3 Web应用程序——Django

第18章 Django入门

  • 以编写一个“学习笔记”的Web应用程序为例
18.1 建立项目
  • 以规范的形式对项目进行描述,建立虚拟环境,创建项目
  • 创建与激活虚拟环境
  • pip命令安装Django,并用Django创建项目——包含四个文件:__init__.py settings.py urls.py wsgi.py
    • settings.py:指定Django如何与系统交互以及如何管理项目
    • urls.py:应创建哪些网页来响应浏览器请求
    • wsgi.py :帮助Django提供它创建的文件
  • 为项目创建数据库
    • python manage.py migrate
    • 首次执行命令migrate时,将让Django确保数据库与项目的当前状态匹配。会表明将创建必要的数据库表
  • 项目查看:python manage.py runserver
18.2 创建应用程序
  • python manage.py startapp 项目名:使Django建立创建应用所需要的基础设置。生成文件admin.py __init__.py migrations models.py tests.py views.py

  • 定义模型(models.py

    • 模型告诉Django如何处理应用程序中存储的数据。模型是一个类。

    • 举例:类Topic继承了类Model(一个定义了模型基本功能的类)

      1
      2
      3
      4
      5
      6
      7
      8
      class Topic(models.Model): 
      """用户学习的主题"""
      text = models.CharField(max_length=200)
      date_added = models.DateTimeField(auto_now_add=True)

      def __str__(self):
      """返回模型的字符串表示"""
      return self.text
      • 属性text是一个由字符或文本组成的数据,类型为CharField,必须告诉Django需要预留的空间大小,即max_length
      • 属性date_added是一个记录日期和时间的数据,类型为DateTimeField,用户创建新主题时,自动将属性设置为当前日期和时间,即设定参数auto_now_add
      • 函数__str__()定义了默认使用什么属性来显示有关的模型信息
    • 激活模型

      • 将应用程序包含到项目中(通过settings.py

      • 在元组INSTALLED_APPS中添加应用程序的名字(之前startapp创建的项目名)
        image-20201031140718469

      • 命令python manage.py makemigrations 项目名:修改数据库,使其能存储与模型相关的信息

        • 再次执行命令python manage.py migrate迁移
        • 每当需要修改“学习笔记”管理的数据时,都采取如下三个步骤:修改models.py;对learning_logs(项目名)调用makemigrations;让Django迁移项目。
      • Django管理网站

        • 创建超级用户:

          • 命令:python manage.py createsuperuser
          • 之后在命令行窗口填充超级用户的信息
        • 向管理网站注册模型

          • Django自动在管理网站中添加了一些模型UserGroup,但自己创建的模型必须手工注册

          • admin.py中:

            1
            2
            3
            from django.contrib import admin
            from learning_logs.models import Topic
            admin.site.register(Topic)
          • 访问网站http://localhost:8000/admin/并输入超级用户的用户名和密码,可添加和修改用户和用户组,并管理先前注册的模型
            image-20201031141911660

        • 添加主题:

          • 在管理网站的模型中创建新的主题(单击Add,填写对应表单即可)
      • 定义模型Entry

        • 在本例中,需要定义模型——用户在学习笔记中添加的条目。其中,多个条目可以关联到一个主题

        • 模型同样继承了基类Model

        • 代码:属性topic为外键,引用了数据库的另一条记录。类Meta用于管理模型的额外信息

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          from django.db import models 

          class Topic(models.Model):
          --snip--
          class Entry(models.Model):
          """学到的有关某个主题的具体知识"""
          topic = models.ForeignKey(Topic)
          text = models.TextField()
          date_added = models.DateTimeField(auto_now_add=True)

          class Meta:
          verbose_name_plural = 'entries'

          def __str__(self):
          """返回模型的字符串表示"""
          return self.text[:50] + "..."
      • 迁移模型Entry:和之前迁移类似

      • 管理网站注册Entry:注册过程类似

      • Django Shell

        • 通过交互式终端会话查看数据。交互式环境称Django shell

        • 代码:

          1
          2
          3
          4
          (ll_env)learning_log$ python manage.py shell
          >>> from learning_logs.models import Topic
          >>> Topic.objects.all() # 获取模型Topic的所有实例,返回一个称为查询集的列表。前者为topic.id,后者为topic
          [<Topic: Chess>, <Topic: Rock Climbing>]
        • 获取对象:

          1
          t = Topic.objects.get(id=1) 
        • 查看与主题相关联的条目(利用外键)

          1
          t.entry_set.all()
18.3 创建网页:学习笔记主页
  • 网页创建阶段:定义URL,编写视图,编写模板

    • 视图函数获取并处理网页需要的数据
    • 视图函数通常调用一个模板,以生成浏览器理解的网页
  • 映射URL

    • 代码:项目主文件夹learning_log中的urls.py

      1
      2
      3
      4
      5
      6
      from django.conf.urls import include, url  # 导入帮助项目和管理网站管理URL的函数和模块
      from django.contrib import admin
      urlpatterns = [
      url(r'^admin/', include(admin.site.urls)), # 管理网站中请求的所有URL
      url(r'', include('learning_logs.urls', namespace='learning_logs')),
      ] # 实参 namespace 将项目的URL同其他URL区分开
    • 代码:项目文件夹(通过startapp创建)learning_logs中的urls.py

      1
      2
      3
      4
      5
      6
      7
       """定义learning_logs的URL模式""" 
      from django.conf.urls import url # 用于将URL映射到视图
      from . import views
      urlpatterns = [
      # 主页
      url(r'^$', views.index, name='index'),
      ] # 包含能够在应用程序learning_logs中请求的网页。参数1是正则表达式,定义Django可查找的模式,如果请求的URL不与任何URL模式匹配,则返回一个错误页面;参数2指定了要调用的视图函数;参数3将这个URL模式的名称定义为Index
  • 编写视图

    • learning_logs中的文件views.py是执行命令startapp时自动生成的

    • 代码:

      1
      2
      3
      4
      from django.shortcuts import render 
      def index(request):
      """学习笔记的主页"""
      return render(request, 'learning_logs/index.html') # 根据视图提供的数据渲染
    • 当URL请求与先前定义的模式匹配时,Django在文件views.py中查找函数index(),再将请求对象传递给这个视图函数。此函数只包含调用render()的代码。实参为:原始请求对象以及可用于创建网页的模板

  • 编写模板

    • 模板定义网页结构。网页被请求时,Django将填入相关数据

    • learning_logs新建文件夹templates,在templates建立文件夹learning_logs并新建文件index.html

    • 代码:

      1
      2
      3
      <!--标签<p>标识段落,指明开头位置/结尾位置。第一个段落为标题,第二个也是标题-->
      <p>Learning Log</p>
      <p>Learning Log helps you keep track of your learning, for any topic you're learning about.</p>
18.4 创建其他网页
  • 模板继承:有一些所有网页都包含的元素——需要一个包含通用元素的父模板,让所有网页都继承它

    • 父模板:

      • 创建base.html并放在模板所在的目录

      • 将这个标题设置为到主页的链接

      • 代码:

        1
        2
        3
        4
        <p>
        <a href="{% url 'learning_logs:index' %}">Learning Log</a>
        </p>
        {% block content %}{% endblock content %} <!--模板标签,是一小段代码,生成要在网页显示的信息-->
    • 子模板:

      • 重新编写index.html,使其继承base.html

      • 代码:

        1
        2
        3
        4
        5
        {% extends "learning_logs/base.html" %} <!--表明继承的父模板-->
        {% block content %} <!--所有不是从父模板继承的内容都包含到这里-->
        <p>Learning Log helps you keep track of your learning, for any topic you're
        learning about.</p>
        {% endblock content %}
  • 显示所有主题的页面

    • URL模式:定义显示所有主题的页面的URL

      • learning_logs/urls.py中修改

      • 代码:

        1
        2
        3
        4
        5
        6
        7
        8
        """为learning_logs定义URL模式""" 
        --snip--
        urlpatterns = [
        # 主页
        url(r'^$', views.index, name='index'),
        # 显示所有的主题
        url(r'^topics/$', views.topics, name='topics'),
        ]
    • 视图:函数topics()需要从数据库中获得数据并发送给模板

      • 代码:

        1
        2
        3
        4
        5
        def topics(request): 
        """显示所有的主题"""
        topics = Topic.objects.order_by('date_added') # 查询数据库,请求提供Topic对象,并按属性排序,查询集存储在topics中
        context = {'topics': topics} # 定义一个发送给模板的上下文
        return render(request, 'learning_logs/topics.html', context)
    • 模板

  • 显示特定主题的页面

    • URL模式
    • 视图
    • 模板

第19章 用户账户

19.1 让用户能输入数据
  • 添加新主题
  • 添加新条目
  • 编辑条目
19.2 创建用户账户
  • 建立一个用户注册和身份验证系统

    • 通过startapp建立应用程序users

    • 将新的应用程序添加到settings.py中的INSTALLED_APPS

    • 修改根目录中的urls.py,使其包含应用程序定义的URL(创建命名空间users

    • 设置登录页面

      • 新建learning_log/users/urls.py

        1
        2
        3
        4
        5
        6
        7
        from django.conf.urls import url 
        from django.contrib.auth.views import login
        from . import views
        urlpatterns = [
        # 登录页面
        url(r'^login/$', login, {'template_name': 'users/login.html'}, name='login'),
        ]
      • 新建learning_log/users/templates/users/login.html

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        {% extends "learning_logs/base.html" %} 
        {% block content %}
        {% if form.errors %} <!-- 表单的errors属性被设置,我们就显示一条错误消息 -->
        <p>Your username and password didn't match. Please try again.</p>
        {% endif %}
        <form method="post" action="{% url 'users:login' %}"> <!--要让登录视图处理表单,因此将实参action设置为登录页面的URL-->
        {% csrf_token %}
        {{ form.as_p }} <!--显示表单-->
        <button name="submit">log in</button> <!--提供按钮-->
        <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" /> <!--隐藏的表单元素next,实参value告知Django在用户登录后重定向的位置-->
        </form>
        {% endblock content %}
      • base.html中添加到登录界面的链接,让所有页面都包含。而用户登录后,则不显示这个链接,因此嵌套在一个{%if%}的标签中

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <p> 
        <a href="{% url 'learning_logs:index' %}">Learning Log</a> -
        <a href="{% url 'learning_logs:topics' %}">Topics</a> -
        {% if user.is_authenticated %}
        Hello, {{ user.username }}.
        {% else %}
        <a href="{% url 'users:login' %}">log in</a>
        {% endif %}
        </p>
        {% block content %}{% endblock content %}
    • 注销

      • users/urls.py设置注销URL

      • 设置视图函数logout_view(),导入Django的函数logout()并调用,重定向到主页

      • views.py

        1
        2
        3
        4
        5
        6
        7
        from django.http import HttpResponseRedirect 
        from django.core.urlresolvers import reverse
        from django.contrib.auth import logout
        def logout_view(request):
        """注销用户"""
        logout(request)
        return HttpResponseRedirect(reverse('learning_logs:index'))
      • base.html添加注销链接,让页面都包含它。放入{% if user.is_authenticated %},用户仅在登录后才能看到

        1
        2
        3
        4
        5
        6
        7
        8
        --snip— 
        {% if user.is_authenticated %}
        Hello, {{ user.username }}.
        <a href="{% url 'users:logout' %}">log out</a>
        {% else %}
        <a href="{% url 'users:login' %}">log in</a>
        {% endif %}
        --snip--
    • 注册

      • users/urls.py添加注册页面的URL

      • 设置视图函数register()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        from django.shortcuts import render 
        from django.http import HttpResponseRedirect
        from django.core.urlresolvers import reverse
        from django.contrib.auth import login, logout, authenticate
        from django.contrib.auth.forms import UserCreationForm
        def logout_view(request):
        --snip--
        def register(request):
        """注册新用户"""
        if request.method != 'POST':
        # 显示空的注册表单
        form = UserCreationForm()
        else:
        # 处理填写好的表单
        form = UserCreationForm(data=request.POST)

        if form.is_valid():
        new_user = form.save()
        # 让用户自动登录,再重定向到主页
        authenticated_user = authenticate(username=new_user.username,
        password=request.POST['password1'])
        login(request, authenticated_user)
        return HttpResponseRedirect(reverse('learning_logs:index'))
        context = {'form': form}
        return render(request, 'users/register.html', context)
      • 设置注册模板register.html

      • base.html中链接到注册页面

19.3 限制用户的访问
  • 创建一个系统,确定各项数据所属的用户,再限制对页面的访问,让用户只能使用自己的数据

  • 限制访问

    • 装饰器login_required

      1
      2
      3
      4
      5
      6
      7
      from django.contrib.auth.decorators import login_required 
      from .models import Topic, Entry
      --snip--
      # 只允许已登录的用户请求topics页面,Python在运行topics()的代码前先运行login_required()的代码
      @login_required
      def topics(request):
      """显示所有的主题"""
    • 修改settings.py,以使用户未登录时能重定向到登录页面

      1
      LOGIN_URL = '/users/login/'
    • 其他页面的登录访问控制同上

  • 数据关联到用户

    • 为数据的model添加外键,并迁移数据库
  • 只允许用户访问自己的主题

    • 修改views.py

      1
      2
      3
      4
      5
      6
      @login_required 
      def topics(request):
      """显示所有的主题"""
      topics = Topic.objects.filter(owner=request.user).order_by('date_added') # Django只从数据库中获取owner属性为当前用户的Topic对象
      context = {'topics': topics}
      return render(request, 'learning_logs/topics.html', context)
  • 保护页面(禁止用户通过输入类似的URL访问其他用户的内容)

    • 在视图函数中,增加代码

      1
      2
      if topic.owner != request.user: 
      raise Http404

第20章 设置应用程序样式与部署

20.1 设置项目样式
  • 使用Bootstrap库和应用程序django-bootstrap3赋予应用程序简单而专业的外观
20.2 部署
  • 将项目部署到Heroku的服务器