一个完整的Django入门指南(三)

时间:2022-05-08
本文章向大家介绍一个完整的Django入门指南(三),主要内容包括第五部分、5.2.帖子视图、5.3.帖子回复、5.5.修改主页视图、5.6.修改topic列表视图、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

第五部分

 Introduction

Welcome to the 5th part of the tutorial series! In this tutorial, we are going to learn more about protecting views against unauthorized users and how to access the authenticated user in the views and forms. We are also going to implement the topic posts listing view and the reply view. Finally, we are going to explore some features of Django ORM and have a brief introduction to migrations.

5.1. 权限

我们必须开始保护我们对非授权用户的视图。到目前为止,用户没有登录,他们也可以看到页面和表单。

Django有一个内置的视图装饰器来避免这个问题:

from django.contrib.auth.decorators import login_required


@login_required
def new_topic(request,pk):
'
'
'
@login_required
def new_topic(request,pk):
    board = get_object_or_404(Board,pk=pk)
    #获取当前登录的用户
    user = User.objects.first()
    if request.method == 'POST':
        #实例一个表单实例
        form = NewTopicForm(request.POST)
        if form.is_valid():
            topic = form.save(commit=False)
            topic.board = board
            topic.starter = request.user
            topic.save()
            post = Post.objects.create(
                message = form.cleaned_data.get('message'),
                topic = topic,
                created_by = request.user
            )
            return redirect('board_topics',pk=board.pk)     #重定向到已创建的主题页

    else:
        form = NewTopicForm()
    return render(request,'new_topic.html',{'board':board,'form':form})

5.2.帖子视图

先在让我们花点时间来实现帖子列表页面,相应的线框如下:

(1) 首先创建一个url

url(r'^boards/(?P<pk>d+)/topics/(?P<topic_pk>d+)/$', views.topic_posts, name='topic_posts'),

 pk:用于标识boards

topic_pk:用来检测是哪个主题

(2)匹配视图如下:

def topic_posts(request, pk, topic_pk):
    topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
    return render(request, 'topic_posts.html', {'topic': topic})

(3)templates/topic_posts.html

{% extends 'base.html' %}

{% block title %}{{ topic.subject }}{% endblock %}

{% block breadcrumb %}
  <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
  <li class="breadcrumb-item"><a href="{% url 'board_topics' topic.board.pk %}">{{ topic.board.name }}</a></li>
  <li class="breadcrumb-item active">{{ topic.subject }}</li>
{% endblock %}

{% block content %}

{% endblock %}

 (4)显示主题里面所有的帖子

topic_posts.html内部,我们可以创建一个迭代主题帖子的for循环:

模板/ topic_posts.html

{% extends 'base.html' %}

{% load static %}

{% block title %}{{ topic.subject }}{% endblock %}

{% block breadcrumb %}
  <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
  <li class="breadcrumb-item"><a href="{% url 'board_topics' topic.board.pk %}">{{ topic.board.name }}</a></li>
  <li class="breadcrumb-item active">{{ topic.subject }}</li>
{% endblock %}

{% block content %}

  <div class="mb-4">
    <a href="#" class="btn btn-primary" role="button">Reply</a>
  </div>

  {% for post in topic.posts.all %}
    <div class="card mb-2">
      <div class="card-body p-3">
        <div class="row">
          <div class="col-2">
            <img src="{% static 'image/people.png' %}" alt="{{ post.created_by.username }}" class="w-100">
            <small>Posts: {{ post.created_by.posts.count }}</small>
          </div>
          <div class="col-10">
            <div class="row mb-3">
              <div class="col-6">
                <strong class="text-muted">{{ post.created_by.username }}</strong>
              </div>
              <div class="col-6 text-right">
                <small class="text-muted">{{ post.created_at }}</small>
              </div>
            </div>
            {{ post.message }}
            {% if post.created_by == user %}
              <div class="mt-3">
                <a href="#" class="btn btn-primary btn-sm" role="button">Edit</a>
              </div>
            {% endif %}
          </div>
        </div>
      </div>
    </div>
  {% endfor %}

{% endblock %}

 由于现在我们没有办法上传用户图片,因此我们只需要有一张空白图片图像下载地址

 把图像图片放到静态文件image下

(5)更新topics.html模板

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

点主题,跳到对应的帖子

5.3.帖子回复

我们现在实现回复帖子视图

(1)添加url

url(r'^boards/(?P<pk>d+)/topics/(?P<topic_pk>d+)/reply/$', views.reply_topic, name='reply_topic'),

(2)为帖子回复添加一个新表单

boards/forms.py

from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['message', ]

(3)帖子回复的视图函数

boards/views.py

def reply_topic(request, pk, topic_pk):
    topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.topic = topic
            post.created_by = request.user
            post.save()
            return redirect('topic_posts', pk=pk, topic_pk=topic_pk)
    else:
        form = PostForm()
    return render(request, 'reply_topic.html', {'topic': topic, 'form': form})

更新new_topic的视图函数

return redirect('topic_posts', pk=pk, topic_pk=topic.pk)  
def new_topic(request,pk):
    board = get_object_or_404(Board,pk=pk)
    #获取当前登录的用户
    user = User.objects.first()
    if request.method == 'POST':
        #实例一个表单实例
        form = NewTopicForm(request.POST)
        if form.is_valid():
            topic = form.save(commit=False)
            topic.board = board
            topic.starter = request.user
            topic.save()
            post = Post.objects.create(
                message = form.cleaned_data.get('message'),
                topic = topic,
                created_by = request.user
            )
            return redirect('topic_posts', pk=pk, topic_pk=topic.pk)

    else:
        form = NewTopicForm()
    return render(request,'new_topic.html',{'board':board,'form':form})

(4)reply_topic.html

{% extends 'base.html' %}

{% load static %}

{% block title %}Post a reply{% endblock %}

{% block breadcrumb %}
  <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
  <li class="breadcrumb-item"><a href="{% url 'board_topics' topic.board.pk %}">{{ topic.board.name }}</a></li>
  <li class="breadcrumb-item"><a href="{% url 'topic_posts' topic.board.pk topic.pk %}">{{ topic.subject }}</a></li>
  <li class="breadcrumb-item active">Post a reply</li>
{% endblock %}

{% block content %}

  <form method="post" class="mb-4">
    {% csrf_token %}
    {% include 'includes/form.html' %}
    <button type="submit" class="btn btn-success">Post a reply</button>
  </form>

  {% for post in topic.posts.all %}
    <div class="card mb-2">
      <div class="card-body p-3">
        <div class="row mb-3">
          <div class="col-6">
            <strong class="text-muted">{{ post.created_by.username }}</strong>
          </div>
          <div class="col-6 text-right">
            <small class="text-muted">{{ post.created_at }}</small>
          </div>
        </div>
        {{ post.message }}
      </div>
    </div>
  {% endfor %}

{% endblock %}

访问帖子回复的url

然后,在发布回复之后,用户将重定向回主题帖子

我们现在可以更改首发帖子,以便在页面中更加强调它:

 templates / topic_posts.html

{% for post in topic.posts.all %}
        <div class="card mb-2 {% if forloop.first %}border-dark{% endif %}">
            {% if forloop.first %}
                <div class="card-header text-white bg-dark py-2 px-3">{{ topic.subject }}</div>
            {% endif %}
    
            <div class="card-body p-3">
{% extends 'base.html' %}

{% load static %}

{% block title %}{{ topic.subject }}{% endblock %}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
    <li class="breadcrumb-item"><a href="{% url 'board_topics' topic.board.pk %}">{{ topic.board.name }}</a></li>
    <li class="breadcrumb-item active">{{ topic.subject }}</li>
{% endblock %}

{% block content %}

    <div class="mb-4">
        <a href="#" class="btn btn-primary" role="button">Reply</a>
    </div>

    {% for post in topic.posts.all %}
        <div class="card mb-2 {% if forloop.first %}border-dark{% endif %}">
            {% if forloop.first %}
                <div class="card-header text-white bg-dark py-2 px-3">{{ topic.subject }}</div>
            {% endif %}

            <div class="card-body p-3">
                <div class="row">
                    <div class="col-2">
                        <img src="{% static 'image/people.png' %}" alt="{{ post.created_by.username }}" class="w-100">
                        <small>Posts: {{ post.created_by.posts.count }}</small>
                    </div>
                    <div class="col-10">
                        <div class="row mb-3">
                            <div class="col-6">
                                <strong class="text-muted">{{ post.created_by.username }}</strong>
                            </div>
                            <div class="col-6 text-right">
                                <small class="text-muted">{{ post.created_at }}</small>
                            </div>
                        </div>
                        {{ post.message }}
                        {% if post.created_by == user %}
                            <div class="mt-3">
                                <a href="#" class="btn btn-primary btn-sm" role="button">Edit</a>
                            </div>
                        {% endif %}
                    </div>
                </div>
            </div>
        </div>
    {% endfor %}

{% endblock %}

 5.5.修改主页视图

 首先,让我们改进主视图:

这里有三项任务:

  • 显示board的帖子数;
  • 显示board的主题数量;
  • 显示发布内容的最后一位用户以及日期和时间。

 (1)更改models.py

添加__str__方法

# boards/models.py

from django.db import models
from django.contrib.auth.models import User
from django.utils.text import Truncator

class Board(models.Model):
    '''板块'''
    name = models.CharField(max_length=30,unique=True)
    # 用于说明这个板块做什么用的
    description = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Topic(models.Model):
    '''话题'''
    # 主题内容
    subject = models.CharField(max_length=255)
    # 定义排序
    last_updated = models.DateTimeField(auto_now_add=True)
    # 指定这个话题属于哪个板块
    board = models.ForeignKey(Board,related_name='topics',on_delete=models.CASCADE)
    # 用来识别谁发起的话题
    starter = models.ForeignKey(User,related_name='topics')

    def __str__(self):
        return self.subject


class Post(models.Model):
    '''帖子'''

    # 存储回复的内容
    message = models.TextField(max_length=4000)
    topic = models.ForeignKey(Topic,related_name='posts',on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(null=True)
    # 谁创建的
    created_by = models.ForeignKey(User,related_name='posts',on_delete=models.CASCADE)
    # 谁更新的
    updated_by = models.ForeignKey(User,null=True,related_name='+',on_delete=models.CASCADE)

    def __str__(self):
        truncated_message = Truncator(self.message)
        return truncated_message.chars(30)   

(2) 修改Board

添加两个方法

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()

(3)修改home.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 %}
                        {% 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>No posts yet.</em>
                            </small>
                        {% endif %}
                    {% endwith %}
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

现在可以显示了

 5.6.修改topic列表视图

(1)修改boards/views.py

from django.db.models import Count

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})

(2)修改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 %}
{#templates/topics.html#}

{% extends 'base.html' %}

{% block title %}
    {{ board.name }} - {{ block.super }}
{% endblock %}

{% block breadcrumb %}
    <li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
    <li class="breadcrumb-item active">{{ board.name }}</li>
{% endblock %}

{% block content %}
    <div class="mb-4">
        <a href="{% url 'new_topic' board.pk %}" class="btn btn-primary">New topic</a>
    </div>
    <table class="table">
        <thead class="thead-inverse">
        <tr>
            <th>Topic</th>
            <th>Starter</th>
            <th>Replies</th>
            <th>Views</th>
            <th>Last Update</th>
        </tr>
        </thead>
        <tbody>
        {% 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 %}
        </tbody>
    </table>
{% endblock %}

 (3)给Topic添加一个views字段

class Topic(models.Model):
    '''话题'''
    # 主题内容
    subject = models.CharField(max_length=255)
    # 定义排序
    last_updated = models.DateTimeField(auto_now_add=True)
    # 指定这个话题属于哪个板块
    board = models.ForeignKey(Board,related_name='topics',on_delete=models.CASCADE)
    # 用来识别谁发起的话题
    starter = models.ForeignKey(User,related_name='topics')
    views = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.subject

(4)修改topics_posts函数

def topic_posts(request, pk, topic_pk):
    topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk)
    topic.views += 1
    topic.save()
    return render(request, 'topic_posts.html', {'topic': topic})

(5)修改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>{{ topic.views }}</td>
                <td>{{ topic.last_updated }}</td>
            </tr>
        {% endfor %}

打开一个主题并刷新页面几次,看看它浏览量是不是增加