Django企业开发实战-blog项目(9)

github链接 https://github.com/yt-xy/Django-blog

增加搜索功能

blogs/views.py

class SearchView(IndexView):
    def get_context_data(self, **kwargs):
        context = super().get_context_data()
        context.update({
            'keyword': self.request.GET.get('keyword', '')
        })
        return context
    def get_queryset(self):
        queryset = super().get_queryset()
        keyword = self.request.GET.get('keyword')
        if not keyword:
            return queryset
        # SELECT * FROM post WHERE title ILIKE '%<keyword>%' or title ILIKE '%<keyword>%'
        return queryset.filter(Q(title__icontains=keyword) | Q(desc__icontains=keyword))

urls.py

from blogs.views import (
	...
    SearchView,
)
urlpatterns = [
    ...
    url(r'search/$', SearchView.as_view(), name='search'),
]

templates/blogs/base.html

<form class="form-inline" action="/search/" action="GET">
	<input class="form-control" type="search" placeholder="Search" name="keyword" placeholder="Search" aria-label="Search" value="{{ keyword }}">
	<button class="btn btn-outline-success" type="submit">搜索</button>
</form>
增加作者页面

blogs/views.py

class AuthorView(IndexView):
    def get_queryset(self):
        queryset = super().get_queryset()
        author_id = self.kwargs.get('owner_id')
        return queryset.filter(owner_id=author_id)

urls.py

from blogs.views import (
    AuthorView,
    ...
)

urlpatterns = [
    ...
    url(r'author/(?P<owner_id>\d+)/$', AuthorView.as_view(), name='author'),
]
增加友链页面

config/views.py

from django.views.generic import ListView
from blogs.views import CommonViewMixin
from config.models import Link
class LinkListView(CommonViewMixin, ListView):
    queryset = Link.objects.filter(status=Link.STATUS_NORMAL)
    template_name = 'config/links.html'
    context_object_name = 'link_list'

urls.py

urlpatterns = [
    ...
    url(r'^links/$', LinkListView.as_view(), name='links'),
]

config/links.html

{% extends 'blogs/base.html' %}
{% block title %}友情链接{% endblock %}
{% block main %}
    <table class="table">
        <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">名称</th>
                <th scope="col">网址</th>
            </tr>
        </thead>
        <tbody>
            {% for link in link_list %}
                <tr>
                    <th scope="row">{{ forloop.counter }}</th>
                    <td>{{ link.title }}</td>
                    <td><a href="{{ link.href }}">{{ link.href }}</a></td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}
增加评论页面

为了增加评论的范围,比如通过评论来完成友链的申请,即在友情链接页面下增加评论内容的展示和提交。因为我们的模型设计是针对Post对象的,因此需要稍作调整
*把Comment中的target改为CharField,里面存放着被评论内容的网址。就像很多其他社交评论所做的那样,只需要有一个能够唯一标识当前页面地址的标记即可。但这种方法存在的问题是,在admin后台无法处理权限,因为是多用户系统,理论上只有文章的作者才能删除当前文章下的评论
comment/models.py

# target = models.ForeignKey(Post, verbose_name='评论目标', on_delete=models.PROTECT, db_constraint=False)
target = models.CharField(max_length=100, verbose_name='评论目标')
# 记得数据迁移

comment/forms.py

from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
    nickname = forms.CharField(
        label='昵称',
        max_length=50,
        widget=forms.widgets.Input(
            attrs={'class': 'form-control', 'style': 'width:60%;'}
        )
    )
    email = forms.CharField(
        label='Email',
        max_length=50,
        widget=forms.widgets.EmailInput(
            attrs={'class': 'form-control', 'style': 'width: 60%;'}
        )
    )
    website = forms.CharField(
        label='网站',
        max_length=100,
        widget=forms.widgets.URLInput(
            attrs={'class': 'form-control', 'style': 'width: 60%;'}
        )
    )
    content = forms.CharField(
        label='内容',
        max_length=500,
        widget=forms.widgets.Textarea(
            attrs={'rows': 6, 'cols': 60, 'class': 'form-control'}
        )
    )
    def clean_content(self):
        content = self.cleaned_data.get('content')
        if len(content) < 10:
            raise forms.ValidationError('内容长度怎么能这么短呢!!!')
        return content
    class Meta:
        model = Comment
        fields = ['nickname', 'email', 'website', 'content']

comment/models.py

# 增加一个 返回某篇文章下的所有有效评论 接口
@classmethod
def get_by_target(cls, target):
    return cls.objects.filter(target=target, status=cls.STATUS_NORMAL)

blogs/views.py

class PostDetailView(CommonViewMixin, DetailView):
    queryset = Post.latest_posts()
    model = Post
    template_name = 'blogs/detail.html'
    context_object_name = 'post'
    pk_url_kwarg = 'post_id'
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context.update({
            'comment_form': CommentForm,
            'comment_list': Comment.get_by_target(self.request.path),
        })
        return context

templates/blogs/detail.html

{% block main %}
	...
    <hr />
    <div class="comment">
        <form class="form-group" action="/comment/" method="POST">
            {% csrf_token %}
            <input name="target" type="hidden" value="{{ request.path }}" />
            {{ comment_form }}
            <input type="submit" value="写好了!" />
        </form>

        <!-- 评论列表 -->
        <ul class="list-group">
            {% for comment in comment_list %}
                <li class="list-group-item">
                    <div class="nickname">
                        <a href="{{ comment.website }}">{{ comment.nickname }}</a>
                        <span>{{ comment.created_time }}</span>
                    </div>
                    <div class="comment-content">
                        {{ comment.content }}
                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
{% endblock %}

comment/views.py

from django.shortcuts import redirect
from django.views.generic import TemplateView
from comment.forms import CommentForm
class CommentView(TemplateView):
    http_method_name = ['post']
    template_name = 'comment/result.html'
    def post(self, request, *args, **kwargs):
        comment_form = CommentForm(request.POST)
        target = request.POST.get('target')
        if comment_form.is_valid():
            instance = comment_form.save(commit=False)
            instance.target = target
            instance.save()
            succeed = True
            return redirect(target)
        else:
            succeed = False
        context = {
            'succeed': succeed,
            'form':comment_form,
            'target': target,
        }
        return self.render_to_response(context)

comment/result.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>评论结果页</title>
    <style>
        body {TEXT-ALIGN: center;}
        .result {
            text-align: center;
            width: 40%;
            margin: auto;
        }
        .errorlist {color: red;}
        ul li {
            list-style-type: None;
        }
    </style>
</head>
<body>
    <div class="result">
        {% if succeed %}
            评论成功!
            <a href="{{ target }}">返回</a>
        {% else %}
            <ul class="errorlist">
                {% for field, message in form.errors.items %}
                    <li>{{ message }}</li>
                {% endfor %}
            </ul>
            <a href="javascript:window.history.back()">返回</a>
        {% endif %}
    </div>
</body>
</html>

urls.py

from comment.views import CommentView
urlpatterns = [
    ...
    url(r'^comment/$', CommentView.as_view(), name='comment'),
]
抽象出评论模块组件和Mixin

上面的实现满足了基本功能,但是结构上不太合理,因为我们还需要在blogs/views.py中来操作comment的数据。这意味着,如果要在友链页面上增加评论,也得去修改View层的代码,需要把评论弄成一个即插即用的组件。要完成这个需求,就要用到Django的template tag(自定义标签)这部分接口
在任何需要添加评论的地方,只需要使用{% comment_block request.path %}即可。之所以叫comment_block。是因为comment是Django内置的tag,用来作大块代码的注释
comment/templatetags/comment_block.py

from django import template
from comment.forms import CommentForm
from comment.models import Comment
register = template.Library()
@register.inclusion_tag('comment/block.html')
def comment_block(target):
    return {
        'target': target,
        'comment_form': CommentForm(),
        'comment_list': Comment.get_by_target(target),
    }

上面的代码编写完之后,就可以把PostDetailView中新增的那个get_context_data去掉了,同时也可以去掉评论相关的引用了
接着编写模板,也就是上面用到的comment/block.html,这个模板里面的代码直接从blogs/detail.html中剪切粘贴过来即可。唯一需要处理的是target部分,因为是自定义标签,默认是没有request对象的
comment/block.html

<hr />
<div class="comment">
    <form class="form-group" action="/comment/" method="POST">
        {% csrf_token %}
        <input name="target" type="hidden" value="{{ target }}" />
        {{ comment_form }}
        <input type="submit" value="写好了!" />
    </form>
    <!-- 评论列表 -->
    <ul class="list-group">
        {% for comment in comment_list %}
            <li class="list-group-item">
                <div class="nickname">
                    <a href="{{ comment.website }}">{{ comment.nickname }}</a>
                    <span>{{ comment.created_time }}</span>
                </div>
                <div class="comment-content">
                    {{ comment.content }}
                </div>
            </li>
        {% endfor %}
    </ul>
</div>

因为是自定义的tag,所以需要在模板的最上面(但是需要在extends下面)增加{% load comment_block %},用来加载我们自定义的标签文件,然后在需要展示评论的地方增加{% comment_block request.path %}即可

修改最新评论模板

之前写过最新评论的模板,是基于外键关联Post的方式,现在修改为通用的方法
config/blocks/sidebar_comments.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        {% for comment in comments %}
            <li>
                <a href="{{ comment.target }}">{{ comment.target.title }}</a> | {{ comment.nickname }} : {{ comment.content }}
            </li>
        {% endfor %}
    </ul>
</body>
</html>

版权声明:本文为yt_xy原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。