Django入门与实践-第26章:个性化工具(完结)
我觉得只添加内置的个性化(humanize)包就会很不错。
它包含一组为数据添加“人性化(human touch)”的工具集。
例如,我们可以使用它来更自然地显示日期和时间字段。
我们可以简单地显示:“2分钟前”,而不是显示整个日期。
首先,添加 django.contrib.humanize 到配置文件的INSTALLED_APPS 中。 #myproject/settings.py INSTALLED_APPS = [ 'django.contrib.humanize', # <- 这里 ]
#现在我们就可以在模板中使用它了。首先来编辑 topics.html 模板: #templates/topics.html {% extends 'base.html' %} {% load humanize %} {% block content %} <!-- 代码被压缩 --> <td>{{ topic.last_updated|naturaltime }}</td> <!-- 代码被压缩 --> {% endblock %} 我们所要做的就是在模板中加载 {%load humanize%} 这个模板标签, 然后在模板中使用过滤器: {{ topic.last_updated|naturaltime }}
Gravatar(添加头像用的库)
给用户个人信息添加图像的一种非常简单的方法就是使用 Gravatar。
在 boards/templatetags 文件夹内,创建一个名为 gravatar.py 的新文件:
#boards/templatetags/gravatar.py
import hashlib
from urllib.parse import urlencode
from django import template
from django.conf import settings
register = template.Library()
@register.filter
def gravatar(user):
email = user.email.lower().encode('utf-8')
default = 'mm'
size = 256
url = 'https://www.gravatar.com/avatar/{md5}?{params}'.format(
md5=hashlib.md5(email).hexdigest(),
params=urlencode({'d': default, 's': str(size)})
)
return url
基本上我们可以使用官方提供的代码片段。
我只是做了一下适配,使得它可以在python 3环境中运行。
很好,现在我们可以将它加载到我们的模板中,就像之前我们使用人性化模板过滤器一样: #templates/topic_posts.html {% extends 'base.html' %} {% load gravatar %} {% block content %} <!-- code suppressed --> <img src="{{ post.created_by|gravatar }}" alt="{{ post.created_by.username }}" class="w-100 rounded"> <!-- code suppressed --> {% endblock %}
最后调整 也许你已经注意到了,如果有人回复帖子时有一个小问题。 我们没有更新last_update 字段,因此主题的排序被打乱顺序了。 boards/views.py @login_required 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() topic.last_updated = timezone.now() # <- 这里 topic.save() # <- 这里 return redirect('topic_posts', pk=pk, topic_pk=topic_pk) else: form = PostForm() return render(request, 'reply_topic.html', {'topic': topic, 'form': form})
接下来我们要做的事是需要控制一下页面访问统计系统。 我们不希望相同的用户再次刷新页面的时候被统计为多次访问。 为此,我们可以使用会话(sessions): #boards/views.py class PostListView(ListView): model = Post context_object_name = 'posts' template_name = 'topic_posts.html' paginate_by = 20 def get_context_data(self, **kwargs): session_key = 'viewed_topic_{}'.format(self.topic.pk)# <--这里 if not self.request.session.get(session_key, False): self.topic.views += 1 self.topic.save() self.request.session[session_key] = True# <--直到这里 kwargs['topic'] = self.topic return super().get_context_data(**kwargs) def get_queryset(self): self.topic = get_object_or_404(Topic, board__pk=self. kwargs.get('pk'), pk=self.kwargs.get('topic_pk')) queryset = self.topic.posts.order_by('created_at') return queryset
现在我们可以在主题列表中提供一个更好一点的导航。 目前唯一的选择是用户点击主题标题并转到第一页。我们可以实践一下这么做: #boards/models.py import math from django.db import models class Topic(models.Model): # ... def __str__(self): return self.subject def get_page_count(self): count = self.posts.count() pages = count / 20 return math.ceil(pages) def has_many_pages(self, count=None): if count is None: count = self.get_page_count() return count > 6 def get_page_range(self): t = self.get_page_count() if self.has_many_pages(count): return range(1, 5) return range(1, count + 1) #然后,在 topics.html 模板中,我们可以这样实现: #templates/topics.html <table class="table table-striped mb-4"> <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 %} {% url 'topic_posts' board.pk topic.pk as topic_url %} <tr> <td> <p class="mb-0"><a href="{{ topic_url }}">{{ topic.subject }}</a></p> <small class="text-muted"> Pages: {% for i in topic.get_page_range %} <a href="{{ topic_url }}?page={{ i }}">{{ i }}</a> {% endfor %} {% if topic.has_many_pages %} ... <a href="{{ topic_url }}?page={{ topic.get_page_count }}">Last Page</a> {% endif %} </small> </td> <td class="align-middle">{{ topic.starter.username}}</td> <td class="align-middle">{{ topic.replies }}</td> <td class="align-middle">{{ topic.views }}</td> <td class="align-middle">{{ topic.last_updated|naturaltime }}</td> </tr> {% endfor %} </tbody> </table> 就像每个主题的小分页一样。 请注意,我在 table 标签里还添加了 tablestriped 类,使得表格有一个更好的样式。
在回复页面中,我们现在是列出了所有的回复。 我们可以将它限制在最近的十个回复。 #boards/models.py class Topic(models.Model): # ... def get_last_ten_posts(self): return self.posts.order_by('-created_at')[:10] #templates/reply_topic.html {% block content %} <form method="post" class="mb-4" novalidate> {% csrf_token %} {% include 'includes/form.html' %} <button type="submit" class="btn btn-success">Post a reply</button> </form> {% for post in topic.get_last_ten_posts %} <!-- here! --> <div class="card mb-2"> <!-- code suppressed --> </div> {% endfor %} {% endblock %}
另一件事是,当用户回复帖子时,我们现在是会再次将用户重定向到第一页。 我们可以通过将用户送回到最后一页来改善这个问题。 我们可以在帖字上添加一个ID: #templates/topic_posts.html {% block content %} <div class="mb-4"> <a href="{% url 'reply_topic' topic.board.pk topic.pk %}"class="btn btn-primary" role="button">Reply</a> </div> {% for post in posts %} <div id="{{ post.pk }}" class="card {% if forloop.last %}mb-4{% else %}mb-2{% endif %} {% if forloop.first %}border-dark{% endif %}"> <!-- code suppressed --> </div> {% endfor %} {% include 'includes/pagination.html' %} {% endblock %} 这里的重要点是 <div id="{{ post.pk }}" ...> 。 然后我们可以在视图中像这样使用它: #boards/views.py @login_required 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() topic.last_updated = timezone.now() topic.save() topic_url = reverse('topic_posts', kwargs={'pk': pk, 'topic_pk': topic_pk}) topic_post_url = '{url}?page={page}#{id}'.format( url=topic_url, id=post.pk, page=topic.get_page_count() ) return redirect(topic_post_url) else: form = PostForm() return render(request, 'reply_topic.html', {'topic': topic, 'form': form}) 在 topic_post_url 中,我们使用最后一个来构建一个url,添加一个锚点id等于帖子id的元素。
#下一个问题,正如你在前面的截图中看到的,要解决分页时页数太多的问题。 #最简单的方法是调整 pagination.html 模板: #templates/includes/pagination.html {% if is_paginated %} <nav aria-label="Topics pagination" class="mb-4"> <ul class="pagination"> {% if page_obj.number > 1 %} <li class="page-item"> <a class="page-link" href="?page=1">First</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">First</span> </li> {% endif %} {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Previous</span> </li> {% endif %} {% for page_num in paginator.page_range %} {% if page_obj.number == page_num %} <li class="page-item active"> <span class="page-link"> {{ page_num }} <span class="sr-only">(current)</span> </span> </li> {% elif page_num > page_obj.number|add:'-3' and page_num < page_obj.number|add:'3' %} <li class="page-item"> <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a> </li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Next</span> </li> {% endif %} {% if page_obj.number != paginator.num_pages %} <li class="page-item"> <a class="page-link" href="?page={{ paginator.num_pages }}">Last</a> </li> {% else %} <li class="page-item disabled"> <span class="page-link">Last</span> </li> {% endif %} </ul> </nav> {% endif %}