f9.4 新闻展示


远程仓库地址
https://gitee.com/cz_zzz/InfoNews24

一.新闻首页

1.排行列表(重点)

  • 接口分析
    渲染方式: 全局刷新/SEO -->后端渲染–>跟路由
    数据库行为:查询点击量排行前10的新闻
  • 代码实现
    在首页路由中, 查询点击量排行前10的新闻
    将排行数据传入模板渲染
index.html:
<ul class="rank_list">
            {% for news in rank_list %}
                <li><span class="{{ loop.index | index_convert }}">{{ loop.index }}</span><a href="/news/1">{{ news.title }}</a></li>
            {% endfor %}
        </ul>
views.py:
# 查询 点击量排行前10的新闻
    try:
        rank_list = News.query.order_by(News.clicks.desc()).limit(10).all()
    except BaseException as e:
        current_app.logger.error(e)
        return abort(500)

    user = user.to_dict() if user else None
    # 将新闻排行数据 传入模板渲染
    return render_template("index.html", user=user, rank_list=rank_list)
  • 使用自定义过滤器来设置排行样式
index.html:
                <li><span class="{{ loop.index | index_convert }}">{{ loop.index }}</span><a href="/news/1">{{ news.title }}</a></li>
common.py:
# 定义自定义过滤器
def func_index_convert(index):  # 1.定义形参接收模板变量
    index_dict = {1: "first", 2: 'second', 3: "third"}
    return index_dict.get(index, "")  # 2.将转换结果返回

init.py:
# 让应用添加过滤器
    from info.utils.common import func_index_convert
    app.add_template_filter(func_index_convert, "index_convert")

2.获取新闻列表(重点)

  • 接口分析
    渲染方式:局部刷新–>前端渲染–>定义路由,返回数据
    数据库行为:按照分类和页码来查询(get)新闻 按发布时间倒叙排列

  • 接口文档

获取新闻列表
/get_news_list(新建)
请求方式
GET
请求参数
cid 分类id
cur_page  当前页码
per_count  每页条数
响应形式
json
  • 代码实现
# 获取新闻列表
@home_blu.route('/get_news_list')
def get_news_list():
    # 获取参数
    cid = request.args.get("cid")
    cur_page = request.args.get("cur_page")
    per_count = request.args.get("per_count", HOME_PAGE_MAX_NEWS)
    # 校验参数
    if not all([cid, cur_page]):
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
    try:
        cid = int(cid)
        cur_page = int(cur_page)
        per_count = int(per_count)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])

    # 数据库行为:  按照分类和页码查询新闻  按发布时间倒序
    try:  
          pn = News.query.filter_by(category_id=cid).order_by(News.create_time.desc()).paginate(cur_page, per_count)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])

    data = {
        "news_list": [news.to_dict() for news in pn.items],
        "total_page": pn.pages  # 前端需要根据总页数来判断是否还需要请求
    }
    # 返回json结果
    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK], data=data)

tips:

  1. 排序时先按时间排好后再分页
  2. jsonify返回前先进行格式转换:
    "news_list": [news.to_dict() for news in pn.items],
    tips: 存在问题—>最新一项没有展示
  • 分类”最新”
# 数据库行为:  按照分类和页码查询新闻  按发布时间倒序
    try:  # 如果是"最新", 所有新闻一起分页排序
        if cid == 1:
            pn = News.query.order_by(News.create_time.desc()).paginate(cur_page, per_count)
        else:  # 如果为其他分类, 根据cid进行筛选
            pn = News.query.filter_by(category_id=cid).order_by(News.create_time.desc()).paginate(cur_page, per_count)
    except BaseException as e:
        current_app.logger.error(e)
        return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])

3优化

1.过滤 分类/最新

# 数据库行为:  按照分类和页码查询新闻  按发布时间倒序
filter_list = []
if cid != 1:
    filter_list.append(News.category_id==cid)  #  filter_list = [News.category_id==cid]

try:  # 如果是"最新", 所有新闻一起分页排序
    pn = News.query.filter(*filter_list).order_by(News.create_time.desc()).paginate(cur_page, per_count)

    # if cid == 1:
    #     pn = News.query.order_by(News.create_time.desc()).paginate(cur_page, per_count)
    # else:  # 如果为其他分类, 根据cid进行筛选
    #     pn = News.query.filter(News.category_id==cid).order_by(News.create_time.desc()).paginate(cur_page, per_count)

except BaseException as e:
    current_app.logger.error(e)
    return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])

tips:注释部分为原来思路,本次考虑用实参前面加*–>类似于解包---->如果没有值,则结果是""(空字符串)

2.新闻分类列表

  • 接口分析:
    渲染方式: 全局刷新–>后端渲染–> 跟路由
    数据库行为: 查询分类数据

  • demo实现

      # 查询所有的分类数据
      try:
      categories = Category.query.all()
      except BaseException as e:
      current_app.logger.error(e)
      return abort(500)
    
      user = user.to_dict() if user else None
      # 将新闻排行数据 和 分类数据 传入模板渲染
      return render_template("index.html", user=user, rank_list=rank_list, categories=categories)
    

    tips:注意要在return处返回categories=categories

    • 模板渲染

tips:data-cid="{{ category.id }}"用于构建获取新闻列表的请求参数

  • 优化首页展示
    1.<a href="#" class="logo fl">------><a href="{{ url_for("home.index") }}" class="logo fl">------->logo展示
    2.`
      `------>删除了原来样本中的示例数据

    二.新闻详情

    1.显示详情页面(重点)

    • 接口分析
      渲染方式:全局刷新/SEO–>后端渲染—>新定义路由(modules-->views),渲染详情页面
      数据库行为:查询某条新闻数据—>前端需要传递新闻的id

    • 接口文档
      新闻详情
      /news/<news_id> ------->采用动态URL传递新闻id的方式
      请求方式
      GET
      请求参数

      响应形式
      html
      tips:渲染方式---->响应形式;数据库的行为----->请求参数---->请求方式

    • 代码实现
      views界面:

        from flask import current_app, abort, render_template
      
        from info.modules.news import news_blu
        from info.utils.models import News
      
      
        @news_blu.route('/<int:news_id>')  # 新闻详情
        def news_detail(news_id):
            # 根据新闻id查询新闻模型
            try:
        	news = News.query.get(news_id)
            except BaseException as e:
        	current_app.logger.error(e)
        	return abort(500)
      
            # 将新闻数据传入模板渲染
            return render_template("detail.html", news=news.to_dict())
      
    • 模板渲染

        	<h3>{{ news.title }}</h3>
        		    <div class="detail_about clearfix">
        			<span class="time_souce fl">{{ news.create_time }} 来源: {{ news.source }}</span>
        			<span class="comment fr">0</span>
        		    </div>
        		    {{ news.content | safe }}   {# jinja2对html字符进行自动转义,可以使用safe取消转义 #}
      

    tips:

    1. 在`templates–>details里做渲染
    2. {{ news.content | safe }} —>{# jinja2对html字符进行自动转义,可以使用safe取消转义 #}

    2.抽取基类

    • 抽取原则:
      1. 父子不同,父类定义代码块,子类重写
      2. 父子相同,子类直接继承
    • 抽取方法:新建base.html,分别与detail和index进行对比

    3.显示其他数据

    由于home中的首页数据传到了index.html进行渲染,而基类里并没有提留存
    故:
    在详情页(news)的views要对detail进行传参进行渲染
    图1 其他功能

    • 装饰器方式封装数据查询---->抽取函数和采用装饰器均可
      --------对于相同的展示页面可以提取为函数
      图2 装饰器

    • 装饰器bug
      1. flask中不允许同一个蓝图定义的路由的函数标记相同,一旦相同就会报错
      2. 使用functools.wraps装饰器修改闭包函数的函数信息
      解决方法:

        	@functools.wraps(f)  # 此处为Python内置装饰器(非flask装饰器)可以让被装饰的函数使用指定函数的函数信息(函数名__name__, 函数文档__doc__):
      
    1. url_map指向示例指向
      浏览器提示重复指向:
      浏览器提示
    2. flask指向修改指向修改
    3. 多个装饰器引发的指向问题指向会重复,无法判断
    4. @functools.wraps(f)修改修改指定路径的名字
    5. 修改结果

    图三 装饰器问题解决后路径

    三.收藏新闻

    1.收藏/取消收藏(重点)

    • 接口分析
      1. 渲染方式: 局部刷新–> 前端渲染 -->定义新路由(news/news_collect),实现新闻收藏(收藏与否通过两个<a>标签来实现)
      2. 数据库行为: 使用外键(关系属性)来关联用户和收藏的新闻

    • 接口文档
      新闻收藏/取消收藏–>/news/news_collect
      请求方式 —>POST/json
      请求参数 news_id —>新闻id// action 行为 collect/cancel_collect
      响应形式 —> json

    • demo

        			# 新闻收藏
        			@news_blu.route('/news_collect', methods=['POST'])
        			@user_login_data
        			def news_collect():
        			    user = g.user  # 获取登录的用户信息
        			    if not user:  # 用户未登录
        				return jsonify(errno=RET.SESSIONERR, errmsg=error_map[RET.SESSIONERR])
        			
        		    # 获取参数
        		    news_id = request.json.get("news_id")
        		    action = request.json.get("action")
        		    # 校验参数
        		    if not all([news_id, action]):
        			return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
      
        		    if action not in ["collect", "cancel_collect"]:
        			return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
        		    
        		    try:
        			news_id = int(news_id)
        		    except BaseException as e:
        			current_app.logger.error(e)
        			return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
      
        		    # 查询新闻模型
        		    try:
        			news = News.query.get(news_id)
        		    except BaseException as e:
        			current_app.logger.error(e)
        			return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
      
        		    # 数据库行为: 使用关系属性 来关联用户和收藏的新闻
        		    if action == "collect":  # 收藏
        			user.collection_news.append(news)
        		    else:  # 取消收藏
        			user.collection_news.remove(news)
      
        		    # json返回结果
        		    return jsonify(errno=RET.OK, errmsg=error_map[RET.OK])
      

      tips: 采用g变量来进行传递数据

    • 模板渲染---->data-newid="{{news.id}}":用于构建新闻收藏时的请求参数

    2.显示收藏情况(重点)

    • 接口分析
      1. 渲染方式: 全局刷新—>后端渲染–>在详情路由中,渲染页面

      2. 数据库行为:查询新闻是否被当前用户收藏
        demo:

         is_collected = False  # 记录是否收藏了该新闻
         if user:  # 判断用户是否登录
         # 查询新闻是否被当前用户收藏
         if news in user.collection_news:
             is_collected = True
        
         user = user.to_dict() if user else None
         # 将新闻数据传入模板渲染
         return render_template("detail.html", news=news.to_dict(), user=user, rank_list=rank_list, is_collected=is_collected)
        

    tips:利用is_collected = False来传递是否收藏信息,后边也会用到类似变量的传递渲染所需的模板变量(is_collected=is_collected)

    • 模板渲染

        <!-- TODO 未收藏设置display为block/已收藏为none, data-newsid为新闻id-->
        <a href="javascript:;" class="collection block-center" data-newid="{{ news.id }}" style="display: {% if is_collected %}none{% else %}block{% endif %};">收藏</a>
        <!-- TODO 未收藏设置display为none/已收藏为block, data-newsid为新闻id-->
        <a href="javascript:;" class="collected block-center" data-newid="{{ news.id }}" style="display: {% if is_collected %}block{% else %}none{% endif %};">
      

    tips:

    1. TODO注释---->方便前后端的交流
    2. 上边的两个标签是互斥行为,仅在同一位置显示一个,欲知详情可以查看css文件

    四.评论/回复

    基本实现思想和收藏/显示收藏一致,需要区分局部刷新(2)和页面刷新(3)

    1.显示评论框

    demo:

    	<!-- TODO 未收藏设置display为block/已收藏为none, data-newsid为新闻id-->
        <a href="javascript:;" class="collection block-center" data-newid="{{ news.id }}"
           style="display: {% if is_collected %}none{% else %}block{% endif %};">收藏</a>
        <!-- TODO 未收藏设置display为none/已收藏为block, data-newsid为新闻id-->
        <a href="javascript:;" class="collected block-center" data-newid="{{ news.id }}"
           style="display: {% if is_collected %}block{% else %}none{% endif %};">
            <span class="out">已收藏</span><span class="over">取消收藏</span></a>
    
    
        {% if user %}  <!-- TODO 已登录显示上边, 隐藏下边,  data-newsid为新闻id-->
    
            <form action="" class="comment_form" data-newsid="{{ news.id }}">
                <div class="person_pic">
                    <img src="../static/news/images/cat.jpg" alt="用户图标">
                </div>
                <textarea placeholder="请发表您的评论" class="comment_input"></textarea>
                <input type="submit" name="" value="评 论" class="comment_sub">
            </form>
    
        {% else %}
    
            <div class="comment_form_logout">
                登录发表你的评论
            </div>
    
        {% endif %}
    
    • 显示评论数量
      评论数量在新闻的详情页展示,故可以直接在news/detail里面做渲染:
      {{ news.comments_count }}—>中间有to_dict的转换字典格式

    • 显示当前新闻的所有评论可在model的评论里添加条目:
      comments = db.relationship("Comment", lazy="dynamic")

    • 自关联多对一关系属性

        父评论id
        # 自关联多对一关系属性, 需要设置remote_side=[主键名]
        # parent = db.relationship("Comment", remote_side=[id])
        # children = db.relationship("Comment")  # 一对多
        # parent = db.relationship('Comment', remote_side=[id])  # 多对一
        children = db.relationship("Comment", backref=db.backref('parent', remote_side=[id]))
      

    2.评论/回复基本实现(重点)

    • 接口分析
      渲染方式:局部刷新—>前端渲染---->定义路由,实现评论
      数据库行为:增加一条评论数据

    • 接口文档
      新闻评论/子评论
      /news/news_comment
      请求方式
      POST
      请求参数
      comment 评论内容
      news_id 新闻id
      parent_id 父评论id ---->子评论需要参数展示
      响应形式
      json
      tips:需要注意的是子评论与父评论的一对多情况是需要跟据页面详情展示所决定的,此处为一子评论对多父评论.

    • 代码实现

        # 新闻评论
        @news_blu.route('/news_comment', methods=['POST'])
        @user_login_data
        def news_comment():
            user = g.user
            if not user:  # 用户未登录
        	return jsonify(errno=RET.SESSIONERR, errmsg=error_map[RET.SESSIONERR])
      
            # 获取参数
            comment_content = request.json.get("comment")
            news_id = request.json.get("news_id")
            parent_id = request.json.get("parent_id")
      
            # 校验参数
            if not all([comment_content, news_id]):
        	return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
      
            try:
        	news_id = int(news_id)
            except BaseException as e:
        	current_app.logger.error(e)
        	return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
      
            # 数据库行为: 增加一条评论数据
            comment = Comment()
            comment.content = comment_content
            comment.news_id = news_id
            comment.user_id = user.id
            if parent_id:  # 传parent_id, 说明是子评论
        	try:
        	    parent_id = int(parent_id)
        	    comment.parent_id = parent_id
        	except BaseException as  e:
        	    current_app.logger.error(e)
        	    return jsonify(errno=RET.PARAMERR, errmsg=error_map[RET.PARAMERR])
      
            try:
        	db.session.add(comment)
        	db.session.commit()  # 为了在视图函数中生成评论id, 必须手动提交(自动提交在视图函数执行完才会生成评论id)
            except BaseException as e:
        	current_app.logger.error(e)
        	return jsonify(errno=RET.DBERR, errmsg=error_map[RET.DBERR])
      
            # json返回数据  需要将评论的id也返回(用于前端发子评论时, 构建请求参数parent_id)
            return jsonify(errno=RET.OK, errmsg=error_map[RET.OK], data=comment.to_dict())
      

    tips:

    	1. db.session.commit()  # 为了在视图函数中生成评论id, 必须手动提交(自动提交在视图函数执行完才会生成评论id)-------->data=comment.to_dict()
    	2. 因篇幅有限,数据库的操作未做rool_back处理
    

    3.显示评论情况(重点)

    • 接口分析
      渲染方式:全局刷新---->后端渲染---->详情路由中,模板渲染
      数据库行为:查询该新闻的所有评论

    • 代码实现

        # 查询该新闻的所有评论  按照发布时间倒序排列
        try:
        comments = news.comments.order_by(Comment.create_time.desc()).all()
        except BaseException as e:
        current_app.logger.error(e)
        return abort(500)
      
        user = user.to_dict() if user else None
        # 将新闻数据传入模板渲染
        return render_template("detail.html", news=news.to_dict(), user=user, rank_list=rank_list, is_collected=is_collected, comments=[comment.to_dict() for comment in comments])
      

    tips:comments=[comment.to_dict() for comment in comments]---->列表推导式,区分三元运算符

    • 模板渲染
      tips:
    1. 区分{{ comment.user.nick_name }}和{{ comment.parent.user.nick_name }}
    # 定义自关联多对一关系属性时, 必须设置形参remote_side=[主键]
    # children = db.relationship("Comment")  # 一对多
    # parent = db.relationship('Comment', remote_side=[id])  # 多对一
    
    children = db.relationship("Comment", backref=db.backref('parent', remote_side=[id]))
    
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值