新闻列表页功能
分析
- 业务流程处理
- 判断前端传的标签分类id是否为空,是否为整数,是否超过范围
- 判断前端传的当前文章页数是否为空,是否为整数,是否超过范围
- 请求方法:get
- url定义:
/news/
- 请求参数:url路径参数
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
tag_id | 整数 | 是 | 标签分类id |
page | 整数 | 是 | 当前文章页数 |
- 向前端返回的数据格式为json格式,返回实例如下:
{
"data": {
"total_pages": 61,
"news": [
{
"digest": "在python用import或者from...import或者from...import...as...来导入相应的模块,作用和使用方法与C语言的include头文件类似。其实就是引入...",
"title": "import方法引入模块详解",
"author": "python",
"image_url": "/media/jichujiaochen.jpeg",
"tag_name": "Python基础",
"update_time": "2018年12月17日 14:48"
},
{
"digest": "如果你原来是一个php程序员,你对于php函数非常了解(PS:站长原来就是一个php程序员),但是现在由于工作或者其他原因要学习python,但是p...",
"title": "给曾经是phper的程序员推荐个学习网站",
"author": "python",
"image_url": "/media/jichujiaochen.jpeg",
"tag_name": "Python基础",
"update_time": "2018年12月17日 14:48"
}
]
},
"errno": "0",
"errmsg": ""
}
分页
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。
select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询
settings.py
- 在项目根目录下创建一个media文件夹,用于存放新闻图片以及用户上传的文件
- 在settings.py文件中添加下面配置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('',include('apps.news.urls')),
path('users/',include('apps.users.urls')),
path('doc/',include('apps.doc.urls')),
path('course/',include('apps.course.urls')),
path('',include('apps.verifications.urls')),
]+static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
- 添加测试数据
mysql -u 用户名 -p -D 数据库名 < 文件名
mysql -uroot -p -D web_prv < tb_news_20181217.sql
apps/news/views.py
import logging
from django.shortcuts import render
from django.views import View
from django.core.paginator import Paginator,EmptyPage #不存在的页码报错
from apps.news import models
from apps.news import constants
from utils.json_fun import to_json_data
logger = logging.getLogger('django')
def index(request):
return render(request,'news/index.html')
class IndexView(View):
"""
"""
def get(self,request):
# tags = models.Tag.objects.filter(is_delete=False)
#通过表中的is_delete判断是否被删除
tags = models.Tag.objects.only('id','name').filter(is_delete=False) #only确定要查询的字段,其他的不查
# context = {
# 'tags':tags,
# }
#将当前方法下的变量都传进locals,python内置函数
# return render(request,'news/index.html',context=context)
return render(request,'news/index.html',locals())
#ajax请求
#传参 tag_id page
#后台的返回 图片,标题,摘要,作者,标签,更新时间,文章id
#请求方式:GET
#/news/
#url查询字符串 ?tag_id=1&page=2 /news/?tag_id=1&page=2
class NewsListView(View):
"""
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。
select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。
它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询
"""
def get(self,request):
"""
# 1.获取参数
# 2.校验参数
# 3.从数据库拿数据
# 4.分页
# 5.序列化输出
#6.返回给前端
:param request:
:return:
"""
#1.获取参数,校验参数
#如果通过不正当手段导致int转换不成功时,返回最新资讯给前端
try:
# 如果没传或者格式不对得不到数据,为0,则把最新资讯id设置为0,当刚进入首页没进行选择标签时起作用,即默认为最新资讯
tag_id = int(request.GET.get('tag_id',0)) #get到的是str类型,存入数据库是int类型
except Exception as e:
logger.error('标签错误:\n{}'.format(e))
tag_id = 0
try:
page = int(request.GET.get('page',1))
except Exception as e:
logger.error('页码错误:\n{}'.format(e))
page = 1
#3.从数据库中拿数据
#title,digest(摘要),image_url,update_time
#select_related用于多表查询,是优化措施,参数是关联外键的字段,id为主键,无论怎样都会查到,所以不需要列出来
#News中的tag字段对应Tag标签中的name字段,News中的author字段对应Users中的username字段
news_queryset = models.News.objects.select_related('tag','author').only('title','digest',\
'image_url','update_time','tag__name','author__username')
#is_delete:是否被逻辑删除,tag_id=0在queryset中表示为空
news = news_queryset.filter(is_delete=False,tag_id=tag_id) or news_queryset.filter(is_delete=False)
#4.分页
paginator = Paginator(news,constants.PER_PAGE_NEWS_COUNT) #第一个参数是数据,第二个参数是每页的新闻个数
try:
news_info = paginator.page(page) #对应页的新闻
except Exception as e:
logger.error('用户访问页数大于总页数')
news_info = paginator.page(paginator.num_pages) #展示最大页码的新闻
#5.序列化输出
news_info_list = []
for n in news_info:
news_info_list.append({
'id':n.id,
'title':n.title,
'digest':n.digest,
'image_url':n.image_url,
'update_time':n.update_time.strftime('%Y-%m-%d %H:%M:%S'),
'tag_name':n.tag.name, #tag的name属性
'author':n.author.username,
})
data = {
'news':news_info_list,
'total_page':paginator.num_pages #总页码数
}
#6.返回给前端
return to_json_data(data=data)
apps/news/urls.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
__title__ = ''
__author__ = 'xiaoge'
__mtime__ = '2019/5/10'
# code is far away from bugs with the god animal protecting
I love animals. They taste delicious.
┏┓ ┏┓
┏┛┻━━━━━━┛┻┓
┃ ☃ ┃
┃ ┳┛ ┗┳ ┃
┃ ┻ ┃
┗━┓ ┏━┛
┃ ┗━━━┓
┃ 神兽保佑 ┣┓
┃永无BUG!┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
"""
from django.urls import path, re_path
from apps.news import views
app_name = 'news'
urlpatterns = [
# path('',views.index,name='index'),
path('',views.IndexView.as_view(),name='index'),
path('news/',views.NewsListView.as_view(),name='news_list')
]
报错
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`web_prv`.`tb_news`, CONSTRAINT `tb_news_author_id_4af218b2_fk_tb_users_id` FOREIGN KEY (`author_id`) REFERENCES `tb_users` (`id`))
- 原因:我的Users表中的username的id分别是5和7(因为之前删过几个用户,id为1,2,3,4的让我删了),author是News外键关联Users,tag是外键关联Tag,下图是News的字段顺序,所以 author_id 应该和 Users 的 id 一样,改为下面这样就 ok 了
INSERT INTO `tb_news` VALUES (1, '2018-12-17 14:48:21.373325', '2018-12-17 14:48:21.373341', 0, 'Python 中__new__方法详解及使用', '__new__ 的作用在Python中__new__方法与__init__方法类似,但是如果两个都存在那么__new__闲执行。在基础类object中,__new__被定义成了一 ', '<div class=\"content\">\n <h2>__new__ 的作用</h2><p>在Python中__new__方法与__init__方法类似,但是如果两个都存在那么__new__闲执行。</p><p>在基础类object中,__new__被定义成了一个静态方法,并且需要传递一个参数cls。Cls表示需要实例化的类,此参数在实例化时 由Python解析器自动提供。</p><p>new()是在新式类中新出现的方法,它作用在构造方法init()建造实例之前,可以这么理解,在Python 中存在于类里面的构造方法init()负责将类的实例化,而在init()调用之前,new()决定是否要使用该init()方法,因为new()可以调用其他类的构造方法或者直接返回别 的对象来作为本类 的实例。 </p><h3>new()方法的特性: </h3><p>new()方法是在类准备将自身实例化时调用。 </p><p>new()方法始终都是类的静态方 法,即使没有被加上静态方法装饰器。</p><h2>实例</h2><pre class=\"brush:python;toolbar:false\">class Person(object):\r\n \r\n def __init__(self, name, age):\r\n self.name = name\r\n self.age = age\r\n \r\n def __new__(cls, name, age):\r\n if 0 < age < 150:\r\n return object.__new__(cls)\r\n # return super(Person, cls).__new__(cls)\r\n else:\r\n return None\r\n \r\n def __str__(self):\r\n return \'{0}({1})\'.format(self.__class__.__name__, self.__dict__)\r\n \r\nprint(Person(\'Tom\', 10))\r\nprint(Person(\'Mike\', 200))</pre><p>结果:</p><pre class=\"brush:bash;toolbar:false\">Person({\'age\': 10, \'name\': \'Tom\'})\r\nNone</pre><h2>Python3和 Python2中__new__使用不同</h2><h3>Python2的写法</h3><p>注意 Python 版本大于 等于2.7才支持</p><pre class=\"brush:python;toolbar:false\">class Singleton(object):\r\n def __new__(cls,*args, **kwargs):\r\n if not hasattr(cls,\'_inst\'):\r\n print(cls)\r\n cls._inst = super(Singleton, cls).__new__(cls,*args,**kwargs)\r\n return cls._inst</pre><p><br></p><h3>Python3的写法</h3><pre class=\"brush:python;toolbar:false\">class Singleton(object):\r\n def __new__(cls,*args, **kwargs):\r\n if not hasattr(cls,\'_inst\'):\r\n print(cls)\r\n cls._inst = super(Singleton, cls).__new__(cls)\r\n return cls._inst</pre><p>如果Python3的写法跟Python2写法一样,那么倒数第二行会报错\"TypeError: object() takes no parameters\"</p><p><br></p> </div>', 641, '/media/jichujiaochen.jpeg', 5, 1);
- News 的表
- Users 的表
- Tag 的表
csrf
- csrf_token是随机生成的,每次都变
django.middleware.csrf.CsrfViewMiddleware
:django自带的中间键,客户端用户发送POST,服务器端会在cookie中设置csrf_token,客户端发送的请求需要携带上csrf_token,用于验证,html的form表单中也要加上{% csrf_token %}
- 也可以在后台设置,比如想要在登录视图中加入
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
class LoginView(View):
"""
/users/login/
"""
# 类装饰器
@method_decorator(ensure_csrf_cookie) #在post请求时就携带上csrf_token,所以在post之前设置
def get(self,request):
return render(request,'users/login.html')
def post(self,request):
#1.获取参数
json_data = request.body #获取的是bytes格式
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8')) #或者'utf-8' 将bytes格式解码成字符串,再转成json格式
#2.校验:账号,账号格式,是否为空,账号和密码,数据库
form = LoginForm(data=dict_data,request=request) #类的实例化
#3.返回给前端
if form.is_valid():
return to_json_data(errmsg='恭喜您!登陆成功!')
else:
#定义一个错误信息列表
err_msg_list = []
for item in form.errors.get_json_data().values():
err_msg_list.append(item[0].get('message'))
err_msg_str = '/'.join(err_msg_list) #拼接错误信息为一个字符串
return to_json_data(errno=Code.PARAMERR,errmsg=err_msg_str)
- 因为有很多地方会用到,所以还可以通过中间键设置
- 在utils下新建MyMiddleware.py
#!/home/xiaoge/env python3.6
# -*- coding: utf-8 -*-
"""
__title__ = ' MyMiddleware'
__author__ = 'xiaoge'
__mtime__ = '2019/6/7 下午9:21'
# code is far away from bugs with the god animal protecting
I love animals. They taste delicious.
┏┓ ┏┓
┏┛┻━━━━━━┛┻┓
┃ ☃ ┃
┃ ┳┛ ┗┳ ┃
┃ ┻ ┃
┗━┓ ┏━┛
┃ ┗━━━┓
┃ 神兽保佑 ┣┓
┃永无BUG!┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
"""
from django.utils.deprecation import MiddlewareMixin #用于继承的中间键的基类
from django.middleware.csrf import get_token
class MyMiddleware(MiddlewareMixin):
"""
"""
def process_request(self,request):
get_token(request)
- 在settings.py中设置自定义的中间键
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.MyMiddleware.MyMiddleware' #加入自定义的中间键
]