larken

勤奋的人生才有价值

导航

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

 

posted on 2018-09-06 17:46  larken  阅读(239)  评论(0编辑  收藏  举报