A complete Django entry guide (3)

A complete Django entry guide (3)

the fifth part

 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. Permission

We must start to protect our view of unauthorized users. So far, users have not logged in, and they can also see pages and forms.

Django has a built-in view decorator to avoid this problem:

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)
    #Get the currently logged-in user
    user = User.objects.first()
    if request.method =='POST':
        #InstanceA form instance
        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) #Redirect to the created topic page

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

5.2. Post view

Let us take a moment to implement the post list page. The corresponding wireframe is as follows:

(1) First create a url

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

 pk: used to identify boards

topic_pk: used to detect which topic

(2) The matching view is as follows:

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) Display all posts in the topic

Inside topic_posts.html , we can create a for loop that iterates over topic posts:

Template/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 %}

 Since there is no way to upload user pictures now, we only need to have a blank picture image download address

 Put the image picture under the static file image

(5) Update topics.html template

 {% 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 %}

Click the topic, jump to the corresponding post

5.3. Post reply

We now implement the reply post view

(1) Add url

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

(2) Add a new form for post reply

boards/forms.py

from django import forms
from .models import Post

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

(3) View function of post reply

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

Update the view function of 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)
    #Get the currently logged-in user
    user = User.objects.first()
    if request.method =='POST':
        #InstanceA form instance
        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 %}

Visit the url of the post reply

Then, after posting the reply, the user will be redirected back to the topic post

We can now change the initial post to emphasize it more on the page:

 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. Modify the home page view

 1. let's improve the main view:

There are three tasks here:

  • Display the number of posts on the board;
  • Display the number of topics on the board;
  • Shows the last user who posted the content and the date and time.

 (1) Change models.py

Add __str__ method

# 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):
    '''Pattern'''
    name = models.CharField(max_length=30,unique=True)
    # Used to explain what this section is for
    description = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Topic(models.Model):
    '''topic'''
    # main content
    subject = models.CharField(max_length=255)
    # Define order
    last_updated = models.DateTimeField(auto_now_add=True)
    # Specify which section this topic belongs to
    board = models.ForeignKey(Board,related_name='topics',on_delete=models.CASCADE)
    # Used to identify who initiated the topic
    starter = models.ForeignKey(User,related_name='topics')

    def __str__(self):
        return self.subject


class Post(models.Model):
    '''Posts'''

    # Store the content of the reply
    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)
    # Who created
    created_by = models.ForeignKey(User,related_name='posts',on_delete=models.CASCADE)
    # Who updated
    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) Modify Board

Add two methods

class Board(models.Model):
    '''Pattern'''
    name = models.CharField(max_length=30,unique=True)
    # Used to explain what this section is for
    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) Modify 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 %}

Can now be displayed

 5.6. Modify the topic list view

(1) Modify 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) Modify 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) Add a views field to Topic

class Topic(models.Model):
    '''topic'''
    # main content
    subject = models.CharField(max_length=255)
    # Define order
    last_updated = models.DateTimeField(auto_now_add=True)
    # Specify which section this topic belongs to
    board = models.ForeignKey(Board,related_name='topics',on_delete=models.CASCADE)
    # Used to identify who initiated the topic
    starter = models.ForeignKey(User,related_name='topics')
    views = models.PositiveIntegerField(default=0)

    def __str__(self):
        return self.subject

(4) Modify the topics_posts function

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

Open a topic and refresh the page several times to see if its views increase

Reference: https://cloud.tencent.com/developer/article/1091551 A complete Django introductory guide (3)-Cloud + Community-Tencent Cloud