5.1 django项目-新闻博客系统之新闻主页
2019-08-27 本文已影响0人
yungege
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、接口设计
-
接口说明
项目项目 说明说明 请求方法 GET
url
/news/tags/
参数类型 查询参数 -
参数说明
参数名 类型 是否必须 描述 size 整型 是 每页的标签数 -
返回结果
{ "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、接口设计
-
接口说明
项目项目 说明说明 请求方法 GET
url
/news/
参数类型 查询参数 -
参数说明
参数名 类型 是否必须 描述 tag 整型 是 标签id page 整型 是 页码 pagesize 整型 是 每页的新闻数 -
返回结果
{ "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、接口设计
-
接口说明
项目项目 说明说明 请求方法 GET
url
/news/banners/
参数类型 查询参数 -
参数说明
参数名 类型 是否必须 描述 size 整型 是 每页的新闻数 -
返回结果
{ "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、接口设计
-
接口说明
项目项目 说明说明 请求方法 GET
url
/news/hotnews/
参数类型 查询参数 -
参数说明
参数名 类型 是否必须 描述 size 整型 是 每页的新闻数 -
返回结果
{ "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 ===*/