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.
我们必须开始保护我们对非授权用户的视图。到目前为止,用户没有登录,他们也可以看到页面和表单。
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})
先在让我们花点时间来实现帖子列表页面,相应的线框如下:
(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 %}
点主题,跳到对应的帖子
我们现在实现回复帖子视图
(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 %}
首先,让我们改进主视图:
这里有三项任务:
(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 %}
现在可以显示了
(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 %}
打开一个主题并刷新页面几次,看看它浏览量是不是增加