美文网首页
Django入门学习Day19:查询结果集

Django入门学习Day19:查询结果集

作者: 冰321 | 来源:发表于2018-05-18 14:04 被阅读184次

    现在我们花点时间来探索关于模型的 API。首先,我们来改进主页:

    有3个任务:

    • 显示每个板块的总主题数
    • 显示每个板块的总回复数
    • 显示每个板块的最后发布者和日期

    在实现这些功能前,我们先使用Python终端

    因为我们要在Python终端尝试,所以,把所有的 models 定义一个 __str__ 方法是个好主意

    boards/models.py

    from django.db import models
    from django.utils.text import Truncator
    
    class Board(models.Model):
        # ...
        def __str__(self):
            return self.name
    
    class Topic(models.Model):
        # ...
        def __str__(self):
            return self.subject
    
    class Post(models.Model):
        # ...
        def __str__(self):
            truncated_message = Truncator(self.message)
            return truncated_message.chars(30)
    

    在 Post 模型中,使用了 Truncator 工具类,这是将一个长字符串截取为任意长度字符的简便方法(这里我们使用30个字符)

    现在打开 Python shell

    python manage.py shell
    from boards.models import Board
    
    # 首先从数据库中取一个板实例
    board = Board.objects.get(name='Django')
    

    这三个任务中最简单的一个就是获取当前版块的总主题数,因为 Topic 和 Baoard 是直接关联的。

    board.topics.all()
    
    <QuerySet [<Topic: hello!>, <Topic: hello!>, <Topic: 测试>]>
    
    board.topics.count()
    3
    

    就这样子。

    现在统计一个版块下面的回复数量有点麻烦,因为回复并没有和 Board 直接关联

    from boards.models import Post
    
    Post.objects.all()
    
    <QuerySet [<Post: this is my first topic>, <Post: this is my first topic>, <Post: test>, <Post: test new reply>]>
    
    Post.objects.count()
    4
    

    这里一共4个回复,但是它并不全部属于 "Django" 这个版块的。

    我们可以这样来过滤

    from boards.models import Board, Post
    
    board = Board.objects.get(name='Django')
    
    Post.objects.filter(topic__board=board)
    
    <QuerySet [<Post: this is my first topic>, <Post: this is my first topic>, <Post: test>, <Post: test new reply>]>
    
    Post.objects.filter(topic__board=board).count()
    4
    

    双下划线的topic__board用于通过模型关系来定位,在内部,Django 在 Board-Topic-Post之间构建了桥梁,构建SQL查询来获取属于指定版块下面的帖子回复。

    最后一个任务是标识版块下面的最后一条回复

    # 使用 `created_at`字段来排序, 获得最新的第一个
    Post.objects.filter(topic__board=board).order_by('-created_at')
    
    <QuerySet [<Post: test new reply>, <Post: test>, <Post: this is my first topic>, <Post: this is my first topic>]>
    
    # 我们可以使用 `first()` 方法去只要抓住我们感兴趣的结果
    Post.objects.filter(topic__board=board).order_by('-created_at').first()
    <Post: test new reply >
    

    太棒了,现在我们来实现它

    boards/models.py

    from django.db import models
    
    class Board(models.Model):
        name = models.CharField(max_length=30, unique=True)
        description = models.CharField(max_length=100)
    
        def __str__(self):
            return self.name
    
        def get_posts_count(self):
            return Post.objects.filter(topic__board=self).count()
    
        def get_last_post(self):
            return Post.objects.filter(topic__board=self).order_by('-created_at').first()
    

    注意,我们使用的是self,因为这是Board的一个实例方法,所以我们就用这个Board实例来过滤这个 QuerySet

    现在我们可以改进主页的HTML模板来显示这些新的信息

    templates/home.html

    {% extends 'base.html' %}
    
    {% block breadcrumb %}
      <li class="breadcrumb-item active">Boards</li>
    {% endblock %}
    
    {% block content %}
      <table class="table">
        <thead class="thead-inverse">
          <tr>
            <th>Board</th>
            <th>Posts</th>
            <th>Topics</th>
            <th>Last Post</th>
          </tr>
        </thead>
        <tbody>
          {% for board in boards %}
            <tr>
              <td>
                <a href="{% url 'board_topics' board.pk %}">{{ board.name }}</a>
                <small class="text-muted d-block">{{ board.description }}</small>
              </td>
              <td class="align-middle">
                {{ board.get_posts_count }}
              </td>
              <td class="align-middle">
                {{ board.topics.count }}
              </td>
              <td class="align-middle">
                {% with post=board.get_last_post %}
                  <small>
                    <a href="{% url 'topic_posts' board.pk post.topic.pk %}">
                      By {{ post.created_by.username }} at {{ post.created_at }}
                    </a>
                  </small>
                {% endwith %}
              </td>
            </tr>
          {% endfor %}
        </tbody>
      </table>
    {% endblock %}
    

    运行测试:

    python manage.py test
    
    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    ......................................................EE............
    ======================================================================
    ERROR: test_home_view_contains_link_to_topics_page (boards.tests.test_view_home.HomeTests)
    ----------------------------------------------------------------------
    
    django.urls.exceptions.NoReverseMatch: Reverse for 'topic_posts' with arguments '(1, '')' not found. 1 pattern(s) tried: ['boards/(?P<pk>\\d+)/topics/(?P<topic_pk>\\d+)/$']
    
    ======================================================================
    ERROR: test_home_view_status_code (boards.tests.test_view_home.HomeTests)
    ----------------------------------------------------------------------
    
    django.urls.exceptions.NoReverseMatch: Reverse for 'topic_posts' with arguments '(1, '')' not found. 1 pattern(s) tried: ['boards/(?P<pk>\\d+)/topics/(?P<topic_pk>\\d+)/$']
    
    ----------------------------------------------------------------------
    Ran 68 tests in 2.855s
    
    FAILED (errors=2)
    Destroying test database for alias 'default'...
    

    看起来好像有问题,如果没有回复的时候程序会崩溃

    templates/home.html

    {% with post=board.get_last_post %}
      {% if post %}
        <small>
          <a href="{% url 'topic_posts' board.pk post.topic.pk %}">
            By {{ post.created_by.username }} at {{ post.created_at }}
          </a>
        </small>
      {% else %}
        <small class="text-muted">
          <em>没有文章。</em>
        </small>
      {% endif %}
    {% endwith %}
    

    再次运行测试:

    python manage.py test
    
    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    ................................................................................
    ----------------------------------------------------------------------
    Ran 68 tests in 2.63s
    
    OK
    Destroying test database for alias 'default'...
    

    看效果如下:


    现在是时候来改进回复列表页面了。

    现在,我将告诉你另外一种方法来统计回复的数量,用一种更高效的方式

    和之前一样,首先在Python shell 中尝试

    python manage.py shell
    
    from django.db.models import Count
    from boards.models import Board
    
    board = Board.objects.get(name='Django')
    
    topics = board.topics.order_by('-last_updated').annotate(replies=Count('posts'))
    
    for topic in topics:
        print(topic.replies)
    
    2
    1
    1
    

    这里我们使用annotate ,QuerySet将即时生成一个新的列,这个新的列,将被翻译成一个属性,可通过 topic.replies来访问,它包含了指定主题下的回复数。

    我们来做一个小小的修复,因为回复里面不应该包括发起者的帖子

    topics = board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1)
    
    for topic in topics:
        print(topic.replies)
    
    1
    0
    0
    

    很酷,对不对?

    boards/views.py

    from django.db.models import Count
    from django.shortcuts import get_object_or_404, render
    from .models import Board
    
    def board_topics(request, pk):
        board = get_object_or_404(Board, pk=pk)
        topics = board.topics.order_by('-last_updated').annotate(replies=Count('posts') - 1)
        return render(request, 'topics.html', {'board': board, 'topics': topics})
    

    templates/topics.html

    {% for topic in topics %}
      <tr>
        <td><a href="{% url 'topic_posts' board.pk topic.pk %}">{{ topic.subject }}</a></td>
        <td>{{ topic.starter.username }}</td>
        <td>{{ topic.replies }}</td>
        <td>0</td>
        <td>{{ topic.last_updated }}</td>
      </tr>
    {% endfor %}
    

    下一步是修复主题的查看次数,但是,现在我们需要添加一个新的字段

    原文:https://github.com/pythonzhichan/django-beginners-guide/blob/master/DjangoORM3.md

    相关文章

      网友评论

          本文标题:Django入门学习Day19:查询结果集

          本文链接:https://www.haomeiwen.com/subject/ryhgdftx.html