Django项目: 6.新闻详情页
一、功能需求分析
1.功能
新闻详情
加载评论功能
添加评论功能
二、新闻详情页
1.业务流程分析
业务流程:
判断前端传递新闻id是否为空,是否为整数,是否存在
2.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /news/<int:news_id>/ |
参数格式 | url路径参数 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
news_id | 整数 | 是 | 新闻id |
返回结果:
html页面,直接通过模板渲染的方式实现
3.后端代码
视图
- # 在news/views.py中定义如下视图
- class NewDetailView(View):
- def get(self, request, news_id):
- news = News.objects.select_related('tag', 'author').only(
- 'title', 'content', 'update_time', 'tag__name', 'author__username').filter(
- is_delete=False, id=news_id).first()
- if news:
- return render(request, 'news/news_detail.html', context={
- 'news': news,
- })
- else:
- return HttpResponseNotFound('<h1>Page not found</h1>')
- # 快捷方式
- # 1. 去数据库获取新闻数据
- # news_queryset = News.objects.select_related('tag', 'author').only('title', 'content', 'update_time', 'tag__name', 'author__username')
- # news = get_object_or_404(news_queryset, is_delete=False, id=news_id)
- # 2. 返回渲染页面
- # return render(request, 'news/news_detail.html', context={'news': news})
路由
- # 在news/urls.py中定义如下路由
- urlpatterns = [
- #....
- path('news/<int:news_id>/', views.NewDetailView.as_view(), name='news_detail')
- ]
4.前端代码
html
- <!-- templates/news/news_detail.html -->
- {% extends 'base/base.html' %}
- {% load static %}
- {% block title %}文章详情{% endblock %}
- {% block link %}
- <link rel="stylesheet" href="{% static 'css/news/news-detail.css' %}">
- {% endblock %}
- {% block main_contain %}
- <!-- news-contain start -->
- <div class="news-contain">
- <h1 class="news-title">{{ news.title }}</h1>
- <div class="news-info">
- <div class="news-info-left">
- <span class="news-author">{{ news.author.username }}</span>
- <span class="news-pub-time">{{ news.update_time }}</span>
- <span class="news-type">{{ news.tag.name }}</span>
- </div>
- </div>
- <article class="news-content">
- {{ news.content|safe }}
- </article>
- <div class="comment-contain">
- <div class="comment-pub clearfix">
- <div class="new-comment">
- 文章评论(<span class="comment-count">0</span>)
- </div>
- <div class="comment-control please-login-comment" style="display:none;">
- <input type="text" placeholder="请登录后参加评论">
- </div>
- <div class="comment-control logged-comment">
- <input type="text" placeholder="请填写评论">
- </div>
- <button class="comment-btn">发表评论</button>
- </div>
- <ul class="comment-list">
- <li class="comment-item">
- <div class="comment-info clearfix">
- <img src="../images/avatar.jpeg" alt="avatar" class="comment-avatar">
- <span class="comment-user">评论人</span>
- <span class="comment-pub-time">1小时前</span>
- </div>
- <div class="comment-content">这是一条评论</div>
- </li>
- <li class="comment-item">
- <div class="comment-info clearfix">
- <img src="../images/avatar.jpeg" alt="avatar" class="comment-avatar">
- <span class="comment-user">评论人</span>
- <span class="comment-pub-time">1小时前</span>
- </div>
- <div class="comment-content">这是一条评论</div>
- </li>
- </ul>
- </div>
- </div>
- <!-- news-contain end -->
- {% endblock %}
- {% block script %}
- {% endblock %}
css
- /* 为文章内容添加样式 */
- /* 在static/css/news/news-detail.css文件中需要添加如下内容:*/
- .news-content p {
- font-size: 16px;
- line-height: 26px;
- text-align: justify;
- word-wrap: break-word;
- padding: 3px 0
- }
三、加载新闻评论
1.接口设计
新闻详情页,直接渲染新闻评论
2.后端代码
模型代码
- # 本项目设计二级评论,修改Comments模型,添加一个parent字段
- parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True)
修改模型后一定要及时迁移
导入数据,文件见我的文件中的sql数据包.rar
- # 导入测试数据tb_comments_20181222.sql
- # 一定要保证tb_users中有id为1,,3的三个用户,不然导入测试数据会报错
- mysql -u用户名 -p -D 数据库名< tb_comments_20181222.sql
注意: 如果你想删除有外键关联的表是不能在navicat中用查询-新建查询的命令truncate table_name 来删除表中所有数据的,只能重新弄过
视图代码
- # 修改news/views.py中的NewsDetailView视图
- class NewDetailView(View):
- def get(self, request, news_id):
- news = News.objects.select_related('tag', 'author').only(
- 'title', 'content', 'update_time', 'tag__name', 'author__username').filter(
- is_delete=False, id=news_id).first()
- if news:
- comments = Comments.objects.select_related('author', 'parent').only(
- 'content', 'author__username', 'update_time', 'parent__author__username', 'parent__content',
- 'parent__update_time').filter(is_delete=False, news_id=news_id)
- return render(request, 'news/news_detail.html', context={
- 'news': news,
- 'comments': comments
- })
- else:
- return HttpResponseNotFound('<h1>Page not found</h1>')
3.前端代码
css
- /* 在static/css/news/news-detail.css中添加如下代码: */
- .comment-list .comment-item {
- /*把这条样式注释掉*/
- /*border-bottom: 1px solid #ddd;*/
- margin-bottom: 30px;
- }
- /* ========= 为父评论添加样式 start============ */
- .left_float{
- float:left;
- }
- .right_float{
- float:right;
- }
- .parent_comment_text{
- width:698px;
- padding:8px;
- background: #f4facf;
- margin:10px 0 0 60px;
- }
- .comment_time{
- font-size:12px;
- color:#999;
- margin:10px 0 0 60px;
- }
- .parent_comment_text .parent_username{
- font-size:12px;
- color:#000;
- display:inline-block;
- }
- .parent_comment_text .comment_time{
- display: inline-block;
- float:right;
- }
- .parent_comment_text .parent_content_text{
- color:#666;
- font-size:14px;
- margin-top: 20px;
- }
- .reply_a_tag{
- font-size:12px;
- color:#999;
- text-indent:20px;
- margin:10px 0 0 20px;
- background:url('/static/images/content_icon.png') left center no-repeat;
- }
- .reply_form{
- width:718px;
- overflow:hidden;
- margin:10px 0 0 60px;
- display:none;
- }
- .reply_input{
- float:left;
- width:692px;
- height:30px;
- border-radius:4px;
- padding:10px;
- outline:none;
- border:1px solid #2185ed;
- }
- .reply_btn,.reply_cancel{
- width:40px;
- height:23px;
- background:#76b6f4;
- border:0px;
- border-radius:2px;
- color:#fff;
- margin:10px 5px 0 10px;
- cursor:pointer;
- }
- .reply_cancel{
- background:#fff;
- color: #909090;
- }
- /* ========= 为父评论添加样式 end============ */
将content_icon.png图片放到static/images/中
html
- <!-- 在templates/news/news_detail.html文件中class="comment-contain"里面加入如下代码: -->
- <div class="comment-contain">
- <div class="comment-pub clearfix">
- <div class="new-comment">
- 文章评论(<span class="comment-count">0</span>)
- </div>
- <div class="comment-control please-login-comment" style="display:none;">
- <input type="text" placeholder="请登录后参加评论">
- </div>
- <div class="comment-control logged-comment">
- <input type="text" placeholder="请填写评论">
- </div>
- <button class="comment-btn">发表评论</button>
- </div>
- <ul class="comment-list">
- {% for comment in comments %}
- <li class="comment-item">
- <div class="comment-info clearfix">
- <img src="/static/images/avatar.jpeg" alt="avatar" class="comment-avatar">
- <span class="comment-user">{{ comment.author.username }}</span>
- <span class="comment-pub-time">{{ comment.update_time }}</span>
- </div>
- <div class="comment-content">{{ comment.content }}</div>
- {% if comment.parent %}
- <div class="parent_comment_text">
- <div class="parent_username">{{ comment.parent.author }}</div>
- <div class="comment_time">{{ comment.parent.update_time }}</div>
- <div class="parent_content_text">
- {{ comment.parent.content }}
- </div>
- </div>
- {% endif %}
- <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>
- <form class="reply_form left_float" comment-id="{{ comment.id }}"
- news-id="{{ comment.news_id }}">
- <textarea class="reply_input"></textarea>
- <input type="button" value="回复" class="reply_btn right_float">
- <input type="reset" name="" value="取消" class="reply_cancel right_float">
- </form>
- </li>
- {% endfor comments %}
- </ul>
- </div>
js代码
- // 在static/js/news/news_detail.js中加入如下代码:
- $(function () {
- $('.comment-list').delegate('a,input', 'click', function () {
- //获取回复按钮的class属性
- let sClassValue = $(this).prop('class');
- // 如果点击的是回复按钮,就显示输入框
- if (sClassValue.indexOf('reply_a_tag') >= 0) {
- $(this).next().toggle();
- }
- // 如果点击的是取消按钮,就隐藏输入框
- if (sClassValue.indexOf('reply_cancel') >= 0) {
- $(this).parent().toggle();
- }
- if (sClassValue.indexOf('reply_btn') >= 0) {
- // 评论
- }
- });
- });
四、添加新闻评论功能
1.业务流程分析
业务处理流程:
判断用户是否登录
判断前端传的新闻id是否为空,是否我整数,是否存在
判断评论内容是否为空
判断是否有父评论,父评论id是否与新闻id匹配
保存新闻评论
2.接口设计
接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /news/<int:news_id>/comment/ |
参数格式 | url路径参数,表单参数 |
参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
news_id | 整数 | 是 | 新闻id |
content | 字符串 | 是 | 新闻评论内容 |
parent_id | 整数 | 否 | 父评论id |
注意:post请求需要携带csrftoken
返回结果:
- {
- "errno": "",
- "errmsg": "",
- "data": {
- "news_id": ,
- "content_id": ,
- "content": "评论比较中肯。",
- "author": "admin",
- "update_time": "2019年08月19日 16:00",
- "parent": {
- "news_id": ,
- "content_id": ,
- "content": "行文思路简单肤浅,文章结构平面呆板。",
- "author": "xinlan",
- "update_time": "2018年12月21日 11:17",
- "parent": null
- }
- }
- }
- {
3.后端代码
视图代码
- # 在news/views.py中编写如下视图
- class NewsCommentView(View):
- """
- 添加评论视图
- url: /news/<int:news_id>/comment/
- """
- def post(self, request, news_id):
- # 是否登录
- if not request.user.is_authenticated:
- return json_response(errno=Code.SESSIONERR, errmsg=error_map[Code.SESSIONERR])
- # 新闻是否存在
- if not News.objects.only('id').filter(is_delete=False, id=news_id).exists():
- return json_response(errno=Code.PARAMERR, errmsg='新闻不存在!')
- content = request.POST.get('content')
- # 内容是否为空
- if not content:
- return json_response(errno=Code.PARAMERR, errmsg='评论内容不能为空!')
- # 父id是否正常
- parent_id = request.POST.get('parent_id')
- if parent_id:
- try:
- parent_id = int(parent_id)
- if not Comments.objects.only('id').filter(is_delete=False, id=parent_id, news_id=news_id).exists():
- return json_response(errno=Code.PARAMERR, errmsg=error_map[Code.PARAMERR])
- except Exception as e:
- logger.info('前端传递过来的parent_id异常\n{}'.format(e))
- return json_response(errno=Code.PARAMERR, errmsg='未知异常')
- # 保存到数据库
- new_comment = Comments()
- new_comment.content = content
- new_comment.news_id = news_id
- new_comment.author = request.user
- new_comment.parent_id = parent_id if parent_id else None
- new_comment.save()
- return json_response(data=new_comment.to_dict_data())
序列化comment对象(也就是在上面代码的结尾处用到的方法to_dict_data的书写), 这里我们只实现了2层评论关系,无法多个嵌套
- # 在news/models.py的Comment模型中添加如下方法,用来序列化
- def to_dict_data(self):
- comment_dict = {
- 'news_id': self.news_id,
- 'content_id': self.id,
- 'content': self.content,
- 'author': self.author.username,
- 'update_time': self.update_time.astimezone().strftime('%Y年%m月%d日 %H:%M'),
- 'parent': self.parent.to_dict_data() if self.parent else None
- }
- return comment_dict
路由
- # 在news/urls.py中添加如下路由
- path('news/<int:news_id>/comment/', views.NewsCommentView.as_view(), name='news_comment'),
4.前端代码
html
- <!-- 修改templates/news/news_detail.html中评论部分代码如下 -->
- <!-- news comment start -->
- <div class="comment-contain">
- <div class="comment-pub clearfix">
- <div class="new-comment">
- 文章评论(<span class="comment-count">0</span>)
- </div>
- {% if user.is_authenticated %}
- <div class="comment-control logged-comment" news-id="{{ news.id }}">
- <input type="text" placeholder="请填写评论">
- </div>
- {% else %}
- <div class="comment-control please-login-comment">
- <input type="text" placeholder="请登录后参加评论" readonly>
- </div>
- {% endif %}
- <button class="comment-btn">发表评论</button>
- {% csrf_token %}
- </div>
- <!-- news comment end -->
js代码
- // 修改static/js/news/news_detail.js中的代码如下
- // 修改static/js/news/news_detail.js中的代码如下
- $(function () {
- // 对评论进行评论
- $('.comment-list').delegate('a,input', 'click', function () {
- //获取回复按钮的class属性
- let sClassValue = $(this).prop('class');
- // 如果点击的是回复按钮,就显示输入框
- if (sClassValue.indexOf('reply_a_tag') >= 0) {
- $(this).next().toggle();
- }
- // 如果点击的是取消按钮,就隐藏输入框
- if (sClassValue.indexOf('reply_cancel') >= 0) {
- $(this).parent().toggle();
- }
- if (sClassValue.indexOf('reply_btn') >= 0) {
- // 评论
- let $this = $(this);
- let news_id = $this.parent().attr('news-id');
- let parent_id = $this.parent().attr('comment-id');
- let content = $this.prev().val();
- if (!content) {
- message.showError('请输入评论内容!');
- return
- }
- $
- .ajax({
- url: '/news/' + news_id + '/comment/',
- type: 'POST',
- data: {
- content: content,
- parent_id: parent_id
- },
- dataType: "json"
- })
- .done((res) => {
- if (res.errno === '0') {
- let comment = res.data;
- let html_comment = `<li class="comment-item">
- <div class="comment-info clearfix">
- <img src="/static/images/avatar.jpeg" alt="avatar" class="comment-avatar">
- <span class="comment-user">${comment.author}</span>
- </div>
- <div class="comment-content">${comment.content}</div>
- <div class="parent_comment_text">
- <div class="parent_username">${comment.parent.author}</div>
- <div class="comment_time">${comment.parent.update_time}</div>
- <div class="parent_content_text">
- ${comment.parent.content}
- </div>
- </div>
- <div class="comment_time left_float">${comment.update_time}</div>
- <a href="javascript:;" class="reply_a_tag right_float">回复</a>
- <form class="reply_form left_float" comment-id="${comment.content_id}" news-id="${comment.news_id}">
- <textarea class="reply_input"></textarea>
- <input type="button" value="回复" class="reply_btn right_float">
- <input type="reset" name="" value="取消" class="reply_cancel right_float">
- </form>
- </li>`;
- message.showSuccess('评论成功!');
- setTimeout(() => {
- $('.comment-list').prepend(html_comment);
- }, 800);
- $this.prev().val(''); // 清空输入框
- $this.parent().hide(); // 关闭评论框
- } else if (res.errno === '4101') {
- // 用户未登录
- message.showError(res.errmsg);
- setTimeout(() => {
- window.location.href = '/user/login/'
- }, 800)
- } else {
- // 失败
- message.showError(res.errmsg)
- }
- })
- .fail(() => {
- message.showError('服务器超时,请重试')
- })
- }
- });
- // 对新闻评论
- let $newsComment = $('.logged-comment input'); // 新闻评论框
- let $sendComment = $('.comment-pub .comment-btn'); // 新闻评论按钮
- $sendComment.click(function () {
- let $this = $(this);
- if ($this.prev().hasClass('please-login-comment')) {
- message.showError('未登录,请登录后再评论!');
- setTimeout(() => {
- window.location.href = '/user/login/'
- }, 800);
- return
- }
- let news_id = $this.prev().attr('news-id');
- let content = $newsComment.val();
- if (!content) {
- message.showError('请输入评论内容!');
- return
- }
- $
- .ajax({
- url: '/news/' + news_id + '/comment/',
- type: 'POST',
- data: {
- content: content
- },
- dataType: 'json'
- })
- .done((res) => {
- if (res.errno === '0') {
- let comment = res.data;
- let html_comment = `<li class="comment-item">
- <div class="comment-info clearfix">
- <img src="/static/images/avatar.jpeg" alt="avatar" class="comment-avatar">
- <span class="comment-user">${comment.author}</span>
- <span class="comment-pub-time">${ comment.update_time }</span>
- </div>
- <div class="comment-content">${comment.content}</div>
- <a href="javascript:;" class="reply_a_tag right_float">回复</a>
- <form class="reply_form left_float" comment-id="${comment.content_id}" news-id="${comment.news_id}">
- <textarea class="reply_input"></textarea>
- <input type="button" value="回复" class="reply_btn right_float">
- <input type="reset" name="" value="取消" class="reply_cancel right_float">
- </form>
- </li>`;
- message.showSuccess('评论成功!');
- setTimeout(() => {
- $(".comment-list").prepend(html_comment);
- }, 800);
- // 清空
- $newsComment.val('');
- } else if (res.errno === '4101') {
- // 用户未登录
- message.showError(res.errmsg);
- setTimeout(() => {
- window.location.href = '/user/login/'
- }, 800)
- } else {
- message.showError(res.errmsg);
- }
- })
- .fail(() => {
- message.showError('服务器超时,请重试!');
- })
- })
- });
Django项目: 6.新闻详情页的更多相关文章
- [ionic开源项目教程] - 第10讲 新闻详情页的用户体验优化
目录 [ionic开源项目教程] 第1讲 前言,技术储备,环境搭建,常用命令 [ionic开源项目教程] 第2讲 新建项目,架构页面,配置app.js和controllers.js [ionic开源项 ...
- [ionic开源项目教程] - 第9讲 新闻详情页的实现
目录 [ionic开源项目教程] 第1讲 前言,技术储备,环境搭建,常用命令 [ionic开源项目教程] 第2讲 新建项目,架构页面,配置app.js和controllers.js [ionic开源项 ...
- react-native 项目实战 -- 新闻客户端(7) -- 新闻详情页
http://c.3g.163.com/nc/article/BUH64L0J00031H2L/full.html 观察这个地址,BUH64L0J00031H2L 就是每条新闻数据里的postid. ...
- phpcms新闻详情页上一篇下一篇的实现
在新闻详情页(show.html或show_*.html) 只需要添加类似如下代码即可: <div>上一篇:<a href="{$previous_page[url]}&q ...
- Python爬虫:新浪新闻详情页的数据抓取(函数版)
上一篇文章<Python爬虫:抓取新浪新闻数据>详细解说了如何抓取新浪新闻详情页的相关数据,但代码的构建不利于后续扩展,每次抓取新的详情页时都需要重新写一遍,因此,我们需要将其整理成函数, ...
- Django项目: 5.新闻主页
一.功能需求分析 1.功能 轮播图 推荐文章列表 文章标签导航 文章列表 分页 二.模型设计 根据功能分析,我们需要如下表 1.表和字段分析 文章分类表 文章表 文章评论表 推荐文章表 轮播图表 2. ...
- node——由新闻列表跳转到新闻详情页
当我们在浏览新闻列表页面的时候,想要看感兴趣的新闻内容,需要到详情页面去查看内容. 在之前写好了新闻列表页面,现在需要做列表页面到详情页面的跳转,需要考虑一下问题 1.点击新闻列表某一项跳转到详情页面 ...
- 潭州课堂25班:Ph201805201 django 项目 第二十三课 文章主页 轮播图前端实现 热门新闻推荐实现 详情页实现 (课堂笔记)
前台代码 // 在static/js/news/index.js文件中 $(function () { // 新闻列表功能 let $newsLi = $(".news-nav ul li& ...
- android124 zhihuibeijing 新闻中心-新闻 -北京页签 下拉刷新
缓存工具类:以url为key,json数据为value, package com.itheima.zhbj52.utils; import com.itheima.zhbj52.global.Glob ...
随机推荐
- Laravel依赖
首先有个接口 interface Visit { public function go (); } 然后有三个类Leg,Car,Train class Leg implements Visit { p ...
- JS if 判断
if条件判断 1.语法结构——只判断true,不判断false if(条件判断:结果只有两个true或false) { 条件为true,将执行该代码: } 说明: if是系统关键字,必须全小写. ...
- java读取字符串,生成txt文件
/** * 读取字符串,生成txt 文件 已解决未设置编码时,在项目中直接打开文件,中文乱码问题 * WriteText.writeToText(musicInfo,fileName)直接调用 * * ...
- 《我是一只IT小小鸟》读书笔记 PB16110698 第四周(~3.29)
<我是一只IT小小鸟>读书笔记 本周在邓老师的推荐下,我阅读了<我是一只IT小小鸟>,这本书由21位初入职场的IT人的传记组成,记录了他们成长道路上的酸甜苦辣.书中一段段鲜活生 ...
- Luogu P4180 【模板】严格次小生成树[BJWC2010]
P4180 [模板]严格次小生成树[BJWC2010] 题意 题目描述 小\(C\)最近学了很多最小生成树的算法,\(Prim\)算法.\(Kurskal\)算法.消圈算法等等.正当小\(C\)洋洋得 ...
- openSUSE 安装LAMP记录
按照 openSUSE SDB:LAMP setup安装好了LAMP.运行的大多数命令都是来自与openSUSE SDB:LAMP setup中. 本页面描述如何安装LAMP,这是 Linux Apa ...
- html-from提交表单
使用form创建的仅仅是一个空白的表单, 我们还需要向form中添加不同的表单项 <!DOCTYPE html> <html> <head> <meta ch ...
- day3-编码、文件、集合、函数、递归
学习内容: 1. 文件编码 2. 文件 3. 集合 4.函数 5.递归 6.匿名函数 1. 文件编码: 常见的字符串编码有:ASCII 扩展的ASCII Unicode GBK GB2312 GB18 ...
- 洛谷 2197 nim游戏
题目描述 甲,乙两个人玩Nim取石子游戏. nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取.每次只能从一堆里 ...
- springboot与分布式(zookeeper+dubbo)
docker安装zookeeper命令: docker pull zookeeper:3.4.14 docker启动zookeeper命令: docker run --name zk01 -p 218 ...