docker
简介
- 使用容器让创建,部署,运行应用程序更简单的一个工具
- Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口
- 它是目前最流行的 Linux 容器解决方案
- Docker 将应用程序与该程序的依赖,打包在一个文件里面,运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样
- 有了 Docker,就不用担心环境问题
- 容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小
- 传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而Docker 只需要启动 10 个隔离的应用即可
- docker 架构
基本概念
- Docker 中包括三个基本的概念:
- Image(镜像)
- Container(容器)
- Repository(仓库)
Image(镜像)
- Image(镜像)是 Docker 运行容器的前提,仓库是存放镜像的场所,可见镜像更是 Docker 的核心
- Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等),镜像不包含任何动态数据,其内容在构建之后也不会被改变
- 镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义
从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker 内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统 (union file system) 技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式
Container (容器)
-
容器 (container) 的定义和镜像 (image) 几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的
-
由于容器的定义并没有提及是否要运行容器,所以实际上,容器 = 镜像 + 读写层
Repository (仓库)
Docker 仓库是集中存放镜像文件的场所。镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry (仓库注册服务器)就是这样的服务。有时候会把仓库 (Repository) 和仓库注册服务器 (Registry) 混为一谈,并不严格区分。Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。实际上,一个 Docker Registry 中可以包含多个仓库 (Repository) ,每个仓库可以包含多个标签 (Tag),每个标签对应着一个镜像。所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。
- 仓库又可以分为两种形式:
- public(公有仓库)
- private(私有仓库)
安装 docker
- 官网地址:https://docs.docker.com/install/linux/docker-ce/ubuntu/
- 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc
- 更新apt:
sudo apt-get update
- 添加证书安装包以允许apt通过HTTPS:
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
- 添加Docker的官方GPG密钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- 添加仓库。
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
- 安装docker ce
sudo apt-get install docker-ce docker-ce-cli containerd.io
- 测试
sudo docker run hello-world
- 添加当前用户到 docker 用户组
sudo usermod -aG docker xiaoge
#xiaoge 为当前用户名 - 安装完成后,启动 docker 服务并使其能够在每次系统引导时启动
systemctl start docker
systemctl enable docker
- 接着,我们将添加一个名为 xiaoge 的新用户并将其添加到 docker 组
sudo useradd -m -s /bin/bash xiaoge_docker
sudo usermod -a -G docker xiaoge_docker
- 以 xiaoge_docker用户身份登录并运行 docker 命令,如下所示
su - xiaoge_docker
- docker 删除 images,通过 image 的 id 来指定删除谁
docker rmi <image id>
新闻搜索功能实现
需求分析
- 可以使用数据库的模糊查询(like 关键字)来实现,但效率极低
SELECT * FROM users WHERE username LIKE "%python%" AND is_delete=0;
% 表示任意多个字符,_ 表示一个任意字符 - 在多个字段中查询,使用 like 关键字不方便
- 因此使用搜索引擎来实现全文检索
搜索引擎原理
- 搜索引擎并不是直接在数据库中进行查询
- 会对数据库中的数据进行一遍预处理,单独建立一份索引结构数据
- 类似字典的索引检索页
elasticsearch
- 开源
- 搜索引擎首选
- 底层是开源库 lucene
- REST API 的操作接口
- 搜索引擎在对数据构建索引时,需要进行分词处理,分词是指将一句话拆解成多个单字或词,这些字或词便是这句话的关键词
- Elasticsearch 不支持对中文进行分词建立索引,需要配合拓展elasticsearch-analysis-ik来实现中文分词处理
使用 docker 安装 elasticsearch
获取镜像
- 获取镜像 image
- 拉取镜像到本地仓库,类似于 git
docker image pull delron/elasticsearch-ik:2.4.6-1.0
#由于 pull 拉取会比较慢,所以不推荐,在百度网盘中获取 elasticsearch-ik-2.4.6_docker.tar 文件更快
#然后倒入镜像
sudo docker load -i elasticsearch-ik-2.4.6_docker.tar
- 查看本地仓库是否有这个镜像
docker images
#或者 docker image ls
更改配置
- 同样将配置文件 elasticsearh.zip 也下载下来,解压,将 elasticsearch/config/elasticsearch.yml 中的 ip 地址改为 0.0.0.0,端口改为 8002
创建 docker 容器
- 创建 docker 容器并运行
docker run -dti --network=host --name=elasticsearch -v /home/xiaoge/web_prv/elasticsearch/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0
# 如果运行一直显示 elasticsearch 处于 Exit 状态,用下面这个
docker run -dti --network=host --name=elasticsearch -v /home/xiaoge/web_prv/elasticsearch/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0 tail -f /dev/null
#根据自己的 elasticsearch 所在位置更改上面代码的 config: 前面的路径,-ik表示可以搜索中文
- 查看是否创建成功
docker ps -a
#docker container ls -a
#加上 -a 表示不管是否启动都查出来
通过 STATUS 可以看出已经启动成功
- 运行如下命令,如果有显示则 elasticsearch 配置成功,如下图
curl 127.0.0.1:8002 #端口要对应配置中的端口
- 停止容器
docker stop elasticsearch
- 启动容器
docker start elasticsearch
- 删除容器
docker rm elasticsearch
#删除容器时,容器必须是停止状态,否则会报错
#删除容器后,还要删除容器的目录 /var/
#然后再执行重启
- 查看容器日志
docker logs elasticsearch
安装 django-haystack elasticsearch
- 进入项目的虚拟环境,安装相关包
pip install django-haystack
pip install elasticsearch==2.4.1
#如果安装 django-haystack 出现下面情况(下载完安装包就卡住不安装了或者报错
distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('setuptools_scm')
就先安装 setuptools-scm)
django 项目
settings.py
- settings 文件的 INSTALLED_APPS 中添加 haystack
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.course',
'apps.users',
'apps.news',
'apps.doc',
'apps.verifications',
'haystack',
]
- 添加如下配置
ELASTICSEARCH_DSL = {
'default': {
'hosts': '127.0.0.1:8002' #ip 和端口都对应上
},
}
# Haystack
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://127.0.0.1:8002/', # 此处为 elasticsearch 运行的服务器 ip 地址和端口
'INDEX_NAME': 'web_prv', # 指定 elasticsearch 建立的索引库的名称
},
}
# 设置每页显示的数据量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
# 当数据库改变时,会自动更新索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
后端功能实现
- 在 apps/news 创建 search_indexes.py 文件
- 在 templates 创建 search/indexes/news/news_text.txt 文件(模板名_text.txt)
apps/news/search_indexes.py
#!/home/xiaoge/env python3.6
# -*- coding: utf-8 -*-
"""
__title__ = ' search_indexes.py'
__author__ = 'xiaoge'
__mtime__ = '2019/6/20 上午10:27'
# code is far away from bugs with the god animal protecting
I love animals. They taste delicious.
┏┓ ┏┓
┏┛┻━━━━━━┛┻┓
┃ ☃ ┃
┃ ┳┛ ┗┳ ┃
┃ ┻ ┃
┗━┓ ┏━┛
┃ ┗━━━┓
┃ 神兽保佑 ┣┓
┃永无BUG!┏┛
┗┓┓┏━┳┓┏┛
┃┫┫ ┃┫┫
┗┻┛ ┗┻┛
"""
from haystack import indexes
from .models import News
class NewsIndex(indexes.SearchIndex,indexes.Indexable):
"""
News 索引数据模型类
继承 indexes 的两个类 SearchIndex Indexable
"""
# document=True use_template=True作用:为 templates/search/indexes/news/news_text.txt 的三个字段创建索引
text = indexes.CharField(document=True,use_template=True)
#下面这些字段可以不定义
# 后面模板要用到的字段就可以不用 object,如 {{ one_news.object.image_url }} 可以直接写成 {{ one_news.image_url }}
id = indexes.IntegerField(model_attr='id')
title = indexes.CharField(model_attr='title')
digest = indexes.CharField(model_attr='digest')
content = indexes.CharField(model_attr='content')
image_url = indexes.CharField(model_attr='image_url')
def get_model(self):
"""
就是返回索引的模型类
:return: 创建索引的模型类
"""
return News
def index_queryset(self,using=None):
"""
限制查询范围,此处 tag_id 为1-6,为全查询
:param using:
:return:要建立索引的数据查询集
"""
# return self.get_model().objects.filter(is_delete=False,tag_id=1)
#字段名__in 表示字段的值在后面的列表中
return self.get_model().objects.filter(is_delete=False,tag_id__in=[1,2,3,4,5,6])
apps/news/views.py
- 添加类 SearchView
import logging
import json
from django.shortcuts import render
from django.views import View
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger #页码不是整数类型
from django.http import Http404
from web_prv import settings
from haystack.views import SearchView as _SearchView
from apps.news import models
from apps.news import constants
from utils.json_fun import to_json_data
from utils.res_code import Code,error_map
logger = logging.getLogger('django')
# def index(request):
# return render(request,'news/index.html')
class IndexView(View):
"""
constants
"""
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确定要查询的字段,其他的不查
#使用select_related关联表优化,一对一和一对多都可用,查询集,用切片获取对象
hot_news = models.HotNews.objects.select_related('news').only('news__title','news__image_url',\
'news_id').filter(is_delete=False).order_by('priority','-news__clicks')[0:constants.SHOW_HOTNEWS_COUNT]
# 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 EmptyPage:
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_pages':paginator.num_pages #总页码数
}
#6.返回给前端
return to_json_data(data=data)
class NewsBanner(View):
"""
ajax
"""
#不传参,不改数据,用get
def get(self,request):
banners = models.Banner.objects.select_related('news').only('image_url','news_id','news__title').\
filter(is_delete=False).order_by('priority')[0:constants.SHOW_BANNER_COUNT]
#序列化输出
banners_info_list = []
for b in banners:
banners_info_list.append(
{
'image_url':b.image_url,
'news_id':b.news_id,
'news_title':b.news.title
}
)
data = {
'banners':banners_info_list,
}
return to_json_data(data=data) #返回给前端
#文章详情
#参数:news_id
#title author__username update_time tag_names content
class NewsDetailView(View):
"""
/news/<int:news_id>/
将文章id携带到url
"""
def get(self,request,news_id):
news = models.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:
"""
字段:content,update_time,parent.username,parent.content,parent.update_time
"""
comments = models.Comments.objects.select_related('author','parent').only('content','update_time',\
'author__username','parent__content','parent__author__username','parent__update_time')\
.filter(is_delete=False,news_id=news_id)
#序列化输出
comments_list = []
for comm in comments:
comments_list.append(comm.to_dict_data())
return render(request,'news/news_detail.html',locals())
else:
raise Http404('新闻id不存在'.format(news_id))
class NewsCommentView(View):
"""
给文章评论和给评论人评论用一个接口,只不过确定是否有父类,要对应想要评论的父类
局部刷新,用ajax
参数:content,parent_id,news_id(通过url传递)
请求方式:post
url:/news/<int:news_id>/comments/
"""
def post(self,request,news_id):
#1.获取参数
#2.校验参数
#3.存入数据库
#4.返回给前端
if not request.user.is_authenticated: #判断是否登录
return to_json_data(errno=Code.SESSIONERR,errmsg=error_map[Code.SESSIONERR]) #用户未登陆
if not models.News.objects.only('id').filter(is_delete=False,id=news_id).exists():
return to_json_data(errno=Code.PARAMERR,errmsg='新闻不存在')
json_data = request.body
if not json_data:
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
dict_data = json.loads(json_data.decode('utf8'))
content = dict_data.get('content')
if not content:
return to_json_data(errno=Code.PARAMERR,errmsg='评论的内容不能为空')
#父评论验证:父评论是否存在,parent_id必须是数字,数据库里面是否存在,父评论的新闻的id是否和news_id一致
parent_id = dict_data.get('parent_id') #判断前端传来的是否有parent_id
try: #正常通过页面评论parent_id是数字,如果通过其他渠道可能会出错
if parent_id:
parent_id = int(parent_id)
if not models.Comments.objects.only('id').filter(is_delete=False,
id=parent_id,
news_id=news_id).exists():
return to_json_data(errno=Code.PARAMERR,errmsg=error_map[Code.PARAMERR])
except Exception as e:
logger.info('前端传的parent_id异常{}'.format(e))
return to_json_data(errno=Code.PARAMERR,errmsg='未知异常')
#存到数据库
new_comment = models.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 to_json_data(data=new_comment.to_dict_data())
class SearchView(_SearchView):
"""
"""
template = 'news/search.html'
#重写响应方式,如果请求参数 q 为空,返回模型 News 的热门新闻数据,否则根据参数 q 搜索相关数据
def create_response(self):
"""
如果没有要搜索的数据即 q 为空时,显示所有数据,按点击量排序
如果有要搜索的数据,即 q=* 时,用父类 SearchView 的 create_response 方法进行搜索
:return:
"""
kw = self.request.GET.get('q','')
if not kw: #没搜索时显示热门新闻,
show_all = True #显示所有数据
hot_news = models.HotNews.objects.select_related('news').only('news__title','news__image_url',\
'news__id').filter(is_delete=False).order_by('priority','-news__clicks')
# 对 hot_news 进行分页,每页五个数据
paginator = Paginator(hot_news,settings.HAYSTACK_SEARCH_RESULTS_PER_PAGE)
try:
page = paginator.page(int(self.request.GET.get('page',1)))
except PageNotAnInteger:
#如果参数 page 的数据类型不是整形,则返回第一页数据
page = paginator.page(1)
except EmptyPage:
#用户访问的页数大于实际页数,返回最后一页的数据
page = paginator.page(paginator.num_pages)
return render(self.request,self.template,locals())
else:
show_all = False
qs = super(SearchView,self).create_response()
return qs
templates/search/indexes/news/news_text.txt
{{ object.title }}
{{ object.digest }}
{{ object.content }}
apps/news/urls.py
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'),
path('news/banners/',views.NewsBanner.as_view(),name='news_banner'),
path('news/<int:news_id>/',views.NewsDetailView.as_view(),name='news_detail'),
path('news/<int:news_id>/comments/',views.NewsCommentView.as_view(),name='news_comments'),
path('search/',views.SearchView(),name='search')
]
- 然后执行命令
python manage.py rebuild_index
创建搜索索引
前端功能实现
templates/news1/search.html
#将 class= 'main-contain' 的div标签的内容替换成下面代码
<div class="main-contain ">
<!-- search-box start -->
<div class="search-box">
<form action="" style="display: inline-flex;">
<input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control">
<input type="submit" value="搜索" class="search-btn">
</form>
<!-- 可以用浮动 垂直对齐 以及 flex -->
</div>
<!-- search-box end -->
<!-- content start -->
<div class="content">
<!-- search-list start -->
{% if not show_all %}
<div class="search-result-list">
<h2 class="search-result-title">
搜索结果 <span style="font-weight: 700;color: #ff6620;">{{ paginator.num_pages }}</span>页
</h2>
<ul class="news-list">
{# 导入自带高亮功能 #}
{% load highlight %}
{% for one_news in page.object_list %}
<li class="news-item clearfix">
<a href="{% url 'news:news_detail' one_news.id %}" class="news-thumbnail"
target="_blank">
<img src="{{ one_news.object.image_url }}">
</a>
<div class="news-content">
<h4 class="news-title">
<a href="{% url 'news:news_detail' one_news.id %}">
{% highlight one_news.title with query %}
</a>
</h4>
<p class="news-details">{% highlight one_news.digest with query %}</p>
<div class="news-other">
<span class="news-type">{{ one_news.object.tag.name }}</span>
<span class="news-time">{{ one_news.object.update_time }}</span>
<span
class="news-author">{% highlight one_news.object.author.username with query %}
</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="news-contain">
<div class="hot-recommend-list">
<h2 class="hot-recommend-title">热门推荐</h2>
<ul class="news-list">
{% for one_hotnews in page.object_list %}
<li class="news-item clearfix">
<a href="#" class="news-thumbnail">
<img src="{{ one_hotnews.news.image_url }}">
</a>
<div class="news-content">
<h4 class="news-title">
<a href="{% url 'news:news_detail' one_hotnews.news.id %}">{{ one_hotnews.news.title }}</a>
</h4>
<p class="news-details">{{ one_hotnews.news.digest }}</p>
<div class="news-other">
<span class="news-type">{{ one_hotnews.news.tag.name }}</span>
<span class="news-time">{{ one_hotnews.update_time }}</span>
<span class="news-author">{{ one_hotnews.news.author.username }}</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<!-- search-list end -->
<!-- news-contain start -->
{# 分页导航 #}
<div class="page-box" id="pages">
<div class="pagebar" id="pageBar">
<a class="a1">{{ page.paginator.count }}条</a>
{# 上一页的URL地址 #}
{% if page.has_previous %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.previous_page_number }}"
class="prev">上一页</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.previous_page_number }}" class="prev">上一页</a>
{% endif %}
{% endif %}
{# 列出所有的URL地址 #}
{% for num in page.paginator.page_range|slice:":10" %}
{% if num == page.number %}
<span class="sel">{{ page.number }}</span>
{% else %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ num }}"
target="_self">{{ num }}</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ num }}" target="_self">{{ num }}</a>
{% endif %}
{% endif %}
{% endfor %}
{# 如果页数大于10,则打两点 #}
{% if page.paginator.num_pages > 10 %}
..
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.paginator.num_pages }}"
target="_self">{{ page.paginator.num_pages }}</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.paginator.num_pages }}"
target="_self">{{ page.paginator.num_pages }}</a>
{% endif %}
{% endif %}
{# 下一页的URL地址 #}
{% if page.has_next %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.next_page_number }}"
class="next">下一页</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.next_page_number }}" class="next">下一页</a>
{% endif %}
{% endif %}
</div>
</div>
<!-- news-contain end -->
</div>
<!-- content end -->
</div>
{% extends 'base/base.html' %}
{% load static %}
{% block title %}<title>searchPage</title>{% endblock %}
{% block link %}
<link rel="stylesheet" href="{% static 'css/news/search.css' %}">{% endblock %}
{% block contain %}
{# <div class="main-contain ">#}
{# <!-- search-box start -->#}
{# <div class="search-box">#}
{# <form action="" style="display: inline-flex;">#}
{##}
{# <input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control">#}
{##}
{##}
{# <input type="submit" value="搜索" class="search-btn">#}
{# </form>#}
{# <!-- 可以用浮动 垂直对齐 以及 flex -->#}
{# </div>#}
{# <!-- search-box end -->#}
{# <!-- content start -->#}
{# <div class="content">#}
{# <!-- search-list start -->#}
{##}
{##}
{# <!-- search-list end -->#}
{# <!-- news-contain start -->#}
{##}
{# <div class="news-contain">#}
{# <div class="hot-recommend-list">#}
{# <h2 class="hot-recommend-title">热门推荐</h2>#}
{# <ul class="news-list">#}
{##}
{# <li class="news-item clearfix">#}
{# <a href="#" class="news-thumbnail">#}
{# <img src="{% static 'img/python_gui.jpg' %}">#}
{# </a>#}
{# <div class="news-content">#}
{# <h4 class="news-title">#}
{# <a href="#">Python GUI 教程 25行代码写一个小闹钟</a>#}
{# </h4>#}
{# <p class="news-details">#}
{# </p>#}
{# <div class="news-other">#}
{# <span class="news-type">PythonGUI</span>#}
{# <span class="news-time">11/11 18:22</span>#}
{# <span class="news-author">python</span>#}
{# </div>#}
{# </div>#}
{# </li>#}
{##}
{# <li class="news-item clearfix">#}
{# <a href="#" class="news-thumbnail">#}
{# <img src="{% static 'img/python_advanced.jpg' %}">#}
{# </a>#}
{# <div class="news-content">#}
{# <h4 class="news-title">#}
{# <a href="#">python高性能编程方法一</a>#}
{# </h4>#}
{# <p class="news-details">#}
{# </p>#}
{# <div class="news-other">#}
{# <span class="news-type">Python高级</span>#}
{# <span class="news-time">11/11 17:13</span>#}
{# <span class="news-author">python</span>#}
{# </div>#}
{# </div>#}
{# </li>#}
{##}
{# <li class="news-item clearfix">#}
{# <a href="#" class="news-thumbnail">#}
{# <img src="{% static 'img/jichujiaochen.jpeg' %}">#}
{# </a>#}
{# <div class="news-content">#}
{# <h4 class="news-title">#}
{# <a href="#">python基础 split 和 join函数比较</a>#}
{# </h4>#}
{# <p class="news-details">#}
{# </p>#}
{# <div class="news-other">#}
{# <span class="news-type">Python基础</span>#}
{# <span class="news-time">11/11 16:09</span>#}
{# <span class="news-author">python</span>#}
{# </div>#}
{# </div>#}
{# </li>#}
{##}
{# <li class="news-item clearfix">#}
{# <a href="#" class="news-thumbnail">#}
{# <img src="{% static 'img/python_web.jpg' %}">#}
{# </a>#}
{# <div class="news-content">#}
{# <h4 class="news-title">#}
{# <a href="#">Django调试工具django-debug-toolbar安装使用教程</a>#}
{# </h4>#}
{# <p class="news-details">#}
{# </p>#}
{# <div class="news-other">#}
{# <span class="news-type">python框架</span>#}
{# <span class="news-time">11/11 15:28</span>#}
{# <span class="news-author">python</span>#}
{# </div>#}
{# </div>#}
{# </li>#}
{##}
{# </ul>#}
{# </div>#}
{# </div>#}
{##}
{##}
{# <!-- news-contain end -->#}
{# </div>#}
{# <!-- content end -->#}
{# </div>#}
<div class="main-contain ">
<!-- search-box start -->
<div class="search-box">
<form action="" style="display: inline-flex;">
<input type="search" placeholder="请输入要搜索的内容" name="q" class="search-control">
<input type="submit" value="搜索" class="search-btn">
</form>
<!-- 可以用浮动 垂直对齐 以及 flex -->
</div>
<!-- search-box end -->
<!-- content start -->
<div class="content">
<!-- search-list start -->
{% if not show_all %}
<div class="search-result-list">
<h2 class="search-result-title">
搜索结果 <span style="font-weight: 700;color: #ff6620;">{{ paginator.num_pages }}</span>页
</h2>
<ul class="news-list">
{# 导入自带高亮功能 #}
{% load highlight %}
{% for one_news in page.object_list %}
<li class="news-item clearfix">
<a href="{% url 'news:news_detail' one_news.id %}" class="news-thumbnail"
target="_blank">
<img src="{{ one_news.object.image_url }}">
</a>
<div class="news-content">
<h4 class="news-title">
<a href="{% url 'news:news_detail' one_news.id %}">
{% highlight one_news.title with query %}
</a>
</h4>
<p class="news-details">{% highlight one_news.digest with query %}</p>
<div class="news-other">
<span class="news-type">{{ one_news.object.tag.name }}</span>
<span class="news-time">{{ one_news.object.update_time }}</span>
<span
class="news-author">{% highlight one_news.object.author.username with query %}
</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="news-contain">
<div class="hot-recommend-list">
<h2 class="hot-recommend-title">热门推荐</h2>
<ul class="news-list">
{% for one_hotnews in page.object_list %}
<li class="news-item clearfix">
<a href="#" class="news-thumbnail">
<img src="{{ one_hotnews.news.image_url }}">
</a>
<div class="news-content">
<h4 class="news-title">
<a href="{% url 'news:news_detail' one_hotnews.news.id %}">{{ one_hotnews.news.title }}</a>
</h4>
<p class="news-details">{{ one_hotnews.news.digest }}</p>
<div class="news-other">
<span class="news-type">{{ one_hotnews.news.tag.name }}</span>
<span class="news-time">{{ one_hotnews.update_time }}</span>
<span class="news-author">{{ one_hotnews.news.author.username }}</span>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<!-- search-list end -->
<!-- news-contain start -->
{# 分页导航 #}
<div class="page-box" id="pages">
<div class="pagebar" id="pageBar">
<a class="a1">{{ page.paginator.count }}条</a>
{# 上一页的URL地址 #}
{% if page.has_previous %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.previous_page_number }}"
class="prev">上一页</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.previous_page_number }}" class="prev">上一页</a>
{% endif %}
{% endif %}
{# 列出所有的URL地址 #}
{% for num in page.paginator.page_range|slice:":10" %}
{% if num == page.number %}
<span class="sel">{{ page.number }}</span>
{% else %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ num }}"
target="_self">{{ num }}</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ num }}" target="_self">{{ num }}</a>
{% endif %}
{% endif %}
{% endfor %}
{# 如果页数大于10,则打两点 #}
{% if page.paginator.num_pages > 10 %}
..
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.paginator.num_pages }}"
target="_self">{{ page.paginator.num_pages }}</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.paginator.num_pages }}"
target="_self">{{ page.paginator.num_pages }}</a>
{% endif %}
{% endif %}
{# 下一页的URL地址 #}
{% if page.has_next %}
{% if query %}
<a href="{% url 'news:search' %}?q={{ query }}&page={{ page.next_page_number }}"
class="next">下一页</a>
{% else %}
<a href="{% url 'news:search' %}?page={{ page.next_page_number }}" class="next">下一页</a>
{% endif %}
{% endif %}
</div>
</div>
<!-- news-contain end -->
</div>
<!-- content end -->
</div>
{% endblock %}
<!-- main-contain end -->
{#{% block domready %}#}
{# <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>#}
{#<script src="{% static 'js/commons.js' %}"></script>#}
{# <script src="{% static 'js/news/index.js' %}"></script>#}
{#{% endblock %}#}
static/css/news/search.css
/* ================= main start ================= */
#main {
margin-top: 25px;
min-height: 700px;
}
/* ========= main-contain start ============ */
#main .main-contain {
width: 800px;
float: left;
background: #fff;
}
/* === search-box start === */
.main-contain .search-box {
padding: 40px 50px;
width: 700px;
box-shadow: 1px 2px rgba(0,0,0,.1);
display: inline-flex;
}
.main-contain .search-box .search-control {
width: 600px;
height: 40px;
border-radius: 20px 0 0 20px;
border: 1px solid #ddd;
border-right: none;
padding-left: 0.88em;
font-size: 20px;
}
.main-contain .search-box .search-btn {
width: 100px;
height: 40px;
border: 1px solid red;
background: red;
color: #fff;
font-size: 20px;
border-radius: 0 20px 20px 0;
cursor: pointer;
}
/* === search-box end === */
/* === content start === */
/* == search-list start == */
.content .search-result-list {
padding-top: 20px;
}
.content .search-result-list .search-result-title {
padding-left: 20px;
font-size: 20px;
line-height: 26px;
}
/* == search-list end == */
/* == news-contain start == */
.content .news-contain .hot-recommend-list {
padding-top: 20px;
}
.hot-recommend-list .hot-recommend-title {
padding-left: 20px;
font-size: 20px;
line-height: 26px;
}
.content .news-contain li {
border-bottom: 1px solid #ededed;
}
.news-list .news-item {
padding: 20px;
}
.news-list .news-item .news-thumbnail {
float: left;
width: 224px;
height: 160px;
margin-right: 30px;
overflow: hidden;
}
.news-item .news-thumbnail img {
width: 100%;
height: 100%;
transition: all 0.3s ease-out;
}
.news-item .news-thumbnail:hover img {
transform: scale(1.1);
transition: all 0.3s ease-in;
}
.news-list .news-item .news-content {
width: 500px;
height: 170px;
float: right;
color: #878787;
font-size: 14px;
}
.news-item .news-content .news-title{
color: #212121;
font-size: 22px;
height: 52px;
line-height: 26px;
transition:all 0.3s ease-out;
}
.news-item .news-content .news-title:hover {
color: #5b86db;
transition:all 0.3s ease-in;
}
.news-item .news-content .news-details {
height: 44px;
line-height: 22px;
margin-top: 19px;
text-align: justify;
}
.news-item .news-content .news-other {
margin-top: 30px;
}
.news-content .news-other .news-type {
color: #5b86db;
}
.news-content .news-other .news-author {
float: right;
margin-right: 15px;
}
.news-content .news-other .news-time {
float: right;
}
/* === content end === */
/* ================= main end ================= */
/* === current index start === */
#pages {
padding: 32px 0 10px;
}
.page-box {
text-align: center;
/*font-size: 14px;*/
}
#pages a.prev, a.next {
width: 56px;
padding: 0
}
#pages a {
display: inline-block;
height: 26px;
line-height: 26px;
background: #fff;
border: 1px solid #e3e3e3;
text-align: center;
color: #333;
padding: 0 10px
}
#pages .sel {
display: inline-block;
height: 26px;
line-height: 26px;
background: #0093E9;
border: 1px solid #0093E9;
color: #fff;
text-align: center;
padding: 0 10px
}
.highlighted{
color:coral;
mso-ansi-font-weight: bold;
}
/* === current index end === */