美文网首页
5.1 django项目-新闻博客系统之新闻主页

5.1 django项目-新闻博客系统之新闻主页

作者: yungege | 来源:发表于2019-08-27 14:21 被阅读0次

    05 新闻主页

    一、功能需求分析

    • 轮播图
    • 热门文章
    • 文章标签导航
    • 文章列表
    • 瀑布流分页

    二、模型设计

    1、表

    • 文章标签(分类)表
    • 文章表
    • 热门文章表
    • 轮播图

    2、模型设计

    1、基类

    抽取公共字段,用于继承

    from django.db import models
    
    
    class BaseModel(models.Model):
        """
        基类,公共字段
        """
        # django会自动创建自增长的id字段
        create_time = models.DateTimeField('创建时间', auto_now_add=True)
        update_time = models.DateTimeField('更新时间', auto_now=True)
        is_delete = models.BooleanField('逻辑删除', default=False)
    
        class Meta:
            # 抽象类,用于继承,迁移时不会创建
            abstract = True
    

    2、模型设计

    from django.db import models
    from utils.models.models import BaseModel
    # Create your models here.
    
    
    class Tag(BaseModel):
        """
        文章分类标签模型
        """
        name = models.CharField('标签名', max_length=64, help_text='标签名')
    
        class Meta:
            ordering = ['-update_time', '-id']      # 排序
            db_table = "tb_tag"                     # 指明数据库表名
            verbose_name = "文章标签"                # 在admin站点中显示的名称
            verbose_name_plural = verbose_name      # 显示的复数名称
    
        def __str__(self):
            return 'Tag <name={}>'.format(self.name)
    
    
    class News(BaseModel):
        """
        文章模型
        """
        title = models.CharField('标题', max_length=150, help_text='标题')
        digest = models.CharField('摘要', max_length=200, help_text='摘要')
        content = models.TextField('内容', help_text='内容')
        clicks = models.IntegerField('点击量', default=0, help_text='点击量')
        image_url = models.URLField('图片url', default='', help_text='图片url')
        tag = models.ForeignKey('Tag', on_delete=models.SET_NULL, null=True)
    
        author = models.ForeignKey('user.User', on_delete=models.SET_NULL, null=True)
    
        class Meta:
            ordering = ['-update_time', '-id']  # 排序
            db_table = "tb_news"  # 指明数据库表名
            verbose_name = "新闻"  # 在admin站点中显示的名称
            verbose_name_plural = verbose_name  # 显示的复数名称
    
        def __str__(self):
            return 'News <title={}>'.format(self.title)
    
    class HotNews(BaseModel):
        """
        推荐文章表
        """
        news = models.OneToOneField('News', on_delete=models.CASCADE)
        priority = models.IntegerField('优先级', help_text='优先级')
    
        class Meta:
            ordering = ['-update_time', '-id']  # 排序
            db_table = "tb_hotnews"  # 指明数据库表名
            verbose_name = "热门新闻"  # 在admin站点中显示的名称
            verbose_name_plural = verbose_name  # 显示的复数名称
    
        def __str__(self):
            return 'HotNews <id={}>'.format(self.id)
    
    
    class Banner(BaseModel):
        """
        轮播图
        """
        image_url = models.URLField('轮播图url', help_text='轮播图url')
        priority = models.IntegerField('优先级', help_text='优先级')
    
        news = models.OneToOneField('News', on_delete=models.CASCADE)
    
        class Meta:
            ordering = ['priority', '-update_time', '-id']  # 排序
            db_table = "tb_banner"  # 指明数据库表名
            verbose_name = "轮播图"  # 在admin站点中显示的名称
            verbose_name_plural = verbose_name  # 显示的复数名称
    
        def __str__(self):
            return 'Banner <id={}>'.format(self.id)
    

    三、文章标签列表

    1、接口设计

    1. 接口说明

      项目项目 说明说明
      请求方法 GET
      url /news/tags/
      参数类型 查询参数
    2. 参数说明

      参数名 类型 是否必须 描述
      size 整型 每页的标签数
    3. 返回结果

      {
          "error": "0",
          "errmsg": "",
          "data": {
              "size": 6,
              "tags":{
                  "id": id,
                  "name": name,
              }
          }
      }
      

    2、后端代码

    def tag_list(request):
        '''
        新闻标签列表接口函数
        :url: /news/tags/?size=10
        :return: tags_list(json)
        '''
        # 获取tag_id
        try:
            size = int(request.GET.get('size', constants.SHOW_TAG_COUNT))
        except Exception as e:
            logger.error('size错误:\n{}'.format(e))
            size = constants.SHOW_TAG_COUNT
    
        tags = Tag.objects.values('id', 'name').filter(is_delete=False)[:size]
    
        data = {
            'size': size,
            'tags': list(tags)
        }
    
        return json_response(data=data)
    

    路由

    path('news/tags/', views.tag_list, name='tag_list'),
    

    导入数据

    mysql -u 用户名 -p -D 数据库名 < tb_news_20181217.sql
    
    mysql -uroot -pqwe123 -D djproject28 < tb_tags_20181217.sql
    

    四、新闻列表

    1、接口设计

    1. 接口说明

      项目项目 说明说明
      请求方法 GET
      url /news/
      参数类型 查询参数
    2. 参数说明

      参数名 类型 是否必须 描述
      tag 整型 标签id
      page 整型 页码
      pagesize 整型 每页的新闻数
    3. 返回结果

      {
          "error": "0",
          "errmsg": "",
          "data": {
              'total_pages': paginator.num_pages,
              'page': page,
              'pagesize': pagesize,
              'news': [
                  {
                      "id": id,
                      "title": title,
                      "digest": digest,
                      "image_url": image_url,
                      "update_time": update_time,
                      "tag_name": tag_name,
                      "author": author
                  },
                              {
                      "id": id,
                      "title": title,
                      "digest": digest,
                      "image_url": image_url,
                      "update_time": update_time,
                      "tag_name": tag_name,
                      "author": author
                  }
              ]
          }
      }
      

    2、后端代码

    def news_list(request):
        '''
        新闻列表接口函数
        :url: /news/?tag=*&page=*&pagesize=*
        :return: news_list(json)
        '''
        # 获取tag_id
        try:
            tag_id = int(request.GET.get('tag', 0))
        except Exception as e:
            logger.error('标签错误:\n{}'.format(e))
            tag_id = 0
        # 获取页码page
        try:
            page = int(request.GET.get('page', 1))
        except Exception as e:
            logger.error('页码错误:\n{}'.format(e))
            page = 1
        # 获取pagesize
        try:
            pagesize = int(request.GET.get('pagesize', constants.NEWS_LIST_PAGESIZE))
        except Exception as e:
            logger.error('pagesize错误:\n{}'.format(e))
            pagesize = constants.NEWS_LIST_PAGESIZE
        # 使用only返回的是对象,所以传递到前端时需要迭代处理
        # news_queryset = News.objects.select_related('tag', 'author').only(
        #     'title', 'digest', 'image_url', 'update_time', 'tag__name', 'author__username')
        # values 返回字典
        news_queryset = News.objects.values('id', 'title', 'digest', 'image_url', 'update_time').annotate(tag_name=F('tag__name'), author=F('author__username'))
        news = news_queryset.filter(is_delete=False, tag_id=tag_id) or news_queryset.filter(is_delete=False)
    
        paginator = Paginator(news, pagesize)
        # 获取页面数据 get_page可以容错
        news_info = paginator.get_page(page)
    
        data = {
            'total_pages': paginator.num_pages,
            'page': page,
            'pagesize': pagesize,
            'news': list(news_info)
        }
    
        return json_response(data=data)
    

    路由

    path('news/', views.news_list, name='news_list'),
    

    导入数据

    mysql -u 用户名 -p -D 数据库名 < tb_news_20181217.sql
    
    mysql -uroot -pqwe123 -D djproject28 < tb_news_20181217.sql
    

    3、media文件访问

    在项目根目录下创建一个media文件夹,用于存放新闻图片以及用户上传文件。

    # 在settings.py文件中添加
    # 媒体文件配置
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    

    django在调试模式下提供静态文件服务,为了能够返回media中的媒体文件还需在根urls.py中做如下配置

    from django.contrib import admin
    from django.urls import path, include
    from django.conf import settings
    from django.conf.urls.static import static
    
    urlpatterns = [
        # path('admin/', admin.site.urls),
        path('course/', include('course.urls')),
        path('doc', include('doc.urls')),
        path('', include('news.urls')),
        path('', include('verification.urls')),
        path('', include('user.urls')),
    ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    

    五、轮播图列表

    1、接口设计

    1. 接口说明

      项目项目 说明说明
      请求方法 GET
      url /news/banners/
      参数类型 查询参数
    2. 参数说明

      参数名 类型 是否必须 描述
      size 整型 每页的新闻数
    3. 返回结果

      {
          "error": "0",
          "errmsg": "",
          "data": {
              'size': size,
              'banners':{
                      "news_id": news_id,
                      "image_url": image_url,
                      "news_title": news_title,
                  }
          }
      }
      

    2、后端代码

    def banner_list(request):
        '''
        轮播图列表接口函数
        :url: /news/banners/?size=6
        :return:banner_list(json)
        '''
        # 获取tag_id
        try:
            banner_size = int(request.GET.get('size', constants.SHOW_BANNER_COUNT))
        except Exception as e:
            logger.error('size错误:\n{}'.format(e))
            banner_size = constants.SHOW_BANNER_COUNT
    
        banners = Banner.objects.values('image_url', 'news_id').annotate(news_title=F('news__title')).filter(is_delete=False)[:banner_size]
        data = {
            'size': banner_size,
            'banners': list(banners)
        }
    
        return json_response(data=data)
    

    路由

    path('news/banners/', views.banner_list, name='banner_list'),
    

    导入数据

    mysql -u 用户名 -p -D 数据库名 < tb_news_20181217.sql
    
    mysql -uroot -pqwe123 -D djproject28 < tb_banners_20181217.sql
    

    六、热门新闻列表

    1、接口设计

    1. 接口说明

      项目项目 说明说明
      请求方法 GET
      url /news/hotnews/
      参数类型 查询参数
    2. 参数说明

      参数名 类型 是否必须 描述
      size 整型 每页的新闻数
    3. 返回结果

      {
          "error": "0",
          "errmsg": "",
          "data": {
              'size': size,
              'banners':{
                      "news_id": news_id,
                      "image_url": image_url,
                      "news_title": news_title,
                  }
          }
      }
      

    2、后端代码

    def hotnews_list(request):
        '''
        热门新闻列表接口函数
        :url:  /news/hotnews/?size=10
        :return:
        '''
        try:
            page_size = int(request.GET.get('size', constants.SHOW_HOTNEWS_COUNT))
        except Exception as e:
            logger.error('size错误:\n{}'.format(e))
            page_size = constants.SHOW_HOTNEWS_COUNT
    
        hot_news = HotNews.objects.values('news_id').\
            annotate(news_title=F('news__title'), news_imageurl=F('news__image_url')).\
            filter(is_delete=False).\
            order_by('priority', '-news__clicks')[:page_size]
    
        data = {
            'size': page_size,
            'hot_news': list(hot_news)
        }
        return json_response(data=data)
    

    路由

    path('news/hotnews/', views.hotnews_list, name='hotnews_list'),
    

    导入数据

    mysql -u 用户名 -p -D 数据库名 < tb_news_20181217.sql
    
    mysql -uroot -pqwe123 -D djproject28 < tb_hotnews_20181217.sql
    

    七、前端设计

    1、前端html

    {% extends 'base/base.html' %}
    {% load static %}
    
    {% block title %}首页{% endblock title %}
    
    {% block link %}
        <link rel="stylesheet" href="{% static 'css/news/index.css' %}">
    {% endblock link %}
    
    {% block main_contain %}
        <div class="main-contain">
            <!-- banner start -->
            <div class="banner">
                <ul class="pic">
                    <!--淡入淡出banner-->
    
                </ul>
                <a href="javascript:void(0);" class="btn prev">
                    <i class="PyWhich py-arrow-left"></i></a>
                <a href="javascript:void(0);" class="btn next">
                    <i class="PyWhich py-arrow-right"></i></a>
                <ul class="tab">
                    <!-- 按钮数量必须和图片一致 -->
    
                </ul>
            </div>
            <!-- banner end -->
    
            <!-- content start -->
            <div class="content">
                <!-- recommend-news start -->
                <ul class="recommend-news">
    
                </ul>
                <!-- recommend-news end -->
    
                <!--  news-nav start-->
                <nav class="news-nav">
                    <ul class="clearfix">
                        <li class="active"><a href="javascript:void(0)">最新资讯</a></li>
    {#                    {% for tag in tags %}#}
    {#                        <li><a href="javascript:void(0)" data-id="{{ tag.id }}">{{ tag.name }}</a></li>#}
    {#                    {% endfor %}#}
                    </ul>
                </nav>
                <!--  news-nav end -->
    
                <!-- news-contain start -->
                <div class="news-contain">
                    <ul class="news-list">
    
                    </ul>
                </div>
                <!-- news-contain end -->
    
                <!-- btn-more start -->
                <a href="javascript:void(0);" class="btn-more">加载更多</a>
                <!-- btn-more end -->
            </div>
            <!-- content end -->
        </div>
    {% endblock main_contain %}
    
    {% block otherjs %}
        <script src="{% static 'js/base/message.js' %}"></script>
        <script src="{% static 'js/news/index.js' %}"></script>
    {% endblock otherjs %}
    

    2、前端js

    /*=== tagsStart ===*/
    $(function () {
        fn_load_tags();
        function fn_load_tags() {
            $
                .ajax({
                    url:'/news/tags/',
                    type:'GET',
                    dataType:'json'
                })
                .done((resp)=>{
                    if(resp.errno === '0')
                    {
                        resp.data.tags.forEach(function (tag) {
                            let content = `<li><a href="javascript:void(0)" data-id="${tag.id}">${tag.name}</a></li>`;
                             $('.news-nav .clearfix').append(content);
                        })
                    }
                })
                .fail(()=>{
                    message.showError('服务器超时,请重试!')
                })
        }
    });
    /*=== tagsEnd ===*/
    
    
    /*=== news_listStart ===*/
    $(()=> {
        // 新闻列表
        // let $newNavLi = $('.news-nav ul li');   // 标签li
        let iPage = 1;                          // 默认第一页
        let iTotalPage = 1;                     // 默认总页数为1
        let iCurrentTagId = 0;                  // 默认分类标签为0
        let bIsLoadData = true;                 // 是否正在向后台加载数据
    
        fn_load_content();
    
        // js加载的标签需要动态获取
        // 动态添加的标签要事件委托才能获取到节点
        $('.news-nav ul').on('mouseenter', function (){
             // 点击分类标签
        $('.news-nav ul li').click(function () {
            // 点击分类标签,则为点击的标签加上一个active的class属性
            // 并移除其他兄弟元素上的active的class属性
            $(this).addClass('active').siblings('li').removeClass('active');
            // 获取绑定在data-id属性上的tag_id
            let iClickTagId = $(this).children('a').attr('data-id');
            if (iClickTagId !== iCurrentTagId){
                iCurrentTagId = iClickTagId;  // 记录当前分类id
                // 重置分页参数
                iPage = 1;
                iTotalPage = 1;
                fn_load_content()
            }
    
        });
        });
    
        // 页面滚动加载
        $(window).scroll(function () {
           // 浏览器窗口高度
            let showHeigtht = $(window).height();
           // 整个网页高度
            let pageHeight = $(document).height();
            //页面可以滚动的距离
            let canScrollHeight = pageHeight - showHeigtht;
            // 页面滚动了多少, 整个是随着页面滚动实时变化的
            let nowScroll = $(document).scrollTop();
            if ((canScrollHeight - nowScroll) < 100){
                if(!bIsLoadData){
                    bIsLoadData = true;
                    //判断页数,去更新新闻,小于总数才加载
                    if(iPage < iTotalPage){
                        iPage += 1;
                        fn_load_content();
    
                    }else {
                        message.showInfo('已全部加载,没有更多内容!');
                        $('a.btn-more').html('已全部加载,没有更多内容!')
                    }
    
                }
            }
        });
    
        // 向后端获取新闻列表数据
        function fn_load_content() {
            $.ajax({
                url: '/news/',
                type: 'GET',
                data:{
                    tag: iCurrentTagId,
                    page: iPage
                },
                dataType: 'json',
                success: function (res) {
                    if(res.errno === '0'){
                        iTotalPage = res.data.total_pages;
                        if(iPage === 1){
                            // 第一页清空内容
                            $('.news-list').html('')
                        }
                        res.data.news.forEach(function (one_news) {
                            let content = `<li class="news-item">
                          <a href="/news/${one_news.id}" class="news-thumbnail"
                             target="_blank">
                              <img src="${one_news.image_url}" alt="${one_news.title}"
                                   title="${one_news.title}">
                          </a>
                          <div class="news-content">
                              <h4 class="news-title"><a
                                      href="/news/${one_news.id}"">${one_news.title}</a>
                              </h4>
                              <p class="news-details">${one_news.digest}</p>
                              <div class="news-other">
                                  <span class="news-type">${one_news.tag_name}</span>
                                  <span class="news-time">${one_news.update_time}</span>
                                  <span class="news-author">${one_news.author}</span>
                              </div>
                          </div>
                      </li>`;
                            $('.news-list').append(content);
                        });
                      // $('.news-list').append($('<a href="javascript:void(0);" class="btn-more">滚动加载更多</a>'));
                      //数据加载完毕,设置正在加载数据变量为false,表示当前没有加载数据
                      bIsLoadData = false;
                      $('a.btn-more').html('滚动加载更多')
                    }else {
                        // 加载失败,打印错误信息
                        message.showError(res.errmsg)
                    }
                },
                error: function () {
                    message.showError('服务器超时,请重试!')
                }
            });
        }
    });
    /*=== newslistEnd ===*/
    
    // /*=== bannerStart ===*/
    $(function () {
        // 新闻轮播图功能
        fn_load_banner();                   // 先加载banner
    
        let $banner = $('.banner');         // banner容器div
        let $picLi = $('.banner .pic li');  // 图片li标签
        let $pre = $('.banner .prev');      // 上一张
        let $next = $('.banner .next');     // 下一张
        let $tabLi = $('.banner .tab li');  // 按钮
        let index = 0;                      // 当前索引
    
        // 导航小圆点
        $tabLi.click(function () {
            index = $(this).index();
            $(this).addClass('active').siblings('li').removeClass('active');
            $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500);
        });
    
        // 点击切换上一张
        $pre.click(()=> {
            index --;
            if(index<0){
                index = $tabLi.length - 1       // 最后一张
            }
            $tabLi.eq(index).addClass('active').siblings('li').removeClass('active');
            $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500);
        });
    
        // 点击切换下一张
        $next.click(()=>{
            auto();
        });
    
        // 图片向前滑动
        function auto() {
            index ++;
            index %= $tabLi.length;
            $tabLi.eq(index).addClass('active').siblings('li').removeClass('active');
            $picLi.eq(index).fadeIn(1500).siblings('li').fadeOut(1500)
    
        }
        // 定时器
        let timer = setInterval(auto, 2500);
        $banner.hover(
            ()=>{
                clearInterval(timer)
            },
            ()=>{
                timer = setInterval(auto, 2500);
            }
        );
        // 定义向后端获取banner
        function fn_load_banner() {
            $
                .ajax({
                    url: '/news/banners/',
                    type: 'GET',
                    async: false,               // 同步执行,下面的代码依赖banner的加载
                    dataType: "json",
    
                })
                .done( (res)=> {
                    if(res.errno === '0'){
                        let content = '';
                        let tab_content = '';
                        res.data.banners.forEach( (one_banner, index) =>{
                            if(index === 0){        // 第一页 加active属性
                                content = `<li style="display:block;"><a href="/news/${one_banner.news_id}/">
                     <img src="${one_banner.image_url}" alt="${one_banner.news_title}"></a></li>`;
                                tab_content = '<li class="active"></li>';
                            }else {
                                content = `<li><a href="/news/${one_banner.news_id}/"><img src="${one_banner.image_url}" alt="${one_banner.news_title}"></a></li>`;
                                tab_content = '<li></li>';
                            }
                            $('.pic').append(content);
                            $('.tab').append(tab_content)
                        })
    
                    }else {
                        message.showError(res.errmsg)
                    }
                })
                .fail(()=>{
                    message.showError('服务器超时,请重试!')
                })
        }
    });
    // /*=== bannerEnd ===*/
    
    // /*=== hotnewsStart ===*/
    $(function () {
        fn_load_hotnews();
        function fn_load_hotnews() {
            $
                .ajax({
                    url:'/news/hotnews/',
                    type:'GET',
                    dataType:'json'
                })
                .done((resp)=>{
                    if(resp.errno === '0')
                    {
                        resp.data.hot_news.forEach(function (one_hot_news) {
                            let content = `<li>
                                                <a href="/news/${one_hot_news.news_id}" target="_blank">
                                                    <div class="recommend-thumbnail">
                                                        <img src="${one_hot_news.news_imageurl}" alt="title">
                                                    </div>
                                                    <p class="info">${one_hot_news.news_title}</p>
                                                </a>
                                            </li>`;
                             $('.content .recommend-news').append(content);
                        })
                    }
                })
                .fail(()=>{
                    message.showError('服务器超时,请重试!')
                })
        }
    });
    // /*=== hotnewsrEnd ===*/
    

    相关文章

      网友评论

          本文标题:5.1 django项目-新闻博客系统之新闻主页

          本文链接:https://www.haomeiwen.com/subject/hyreectx.html