注意:模板加载静态文件,在模板头部要导入 {% load staticfiles %}
第一部分: 支持Markdown
1 安装模块:
pip install markdown
1.2 安装pygments(把代码切分成带刺,为单词添加css样式,markdown自动调用)
pip install Pygments
2 渲染markdown(将markdown模块的文本渲染成html文本)
blog/views.py import markdown from django.shortcuts import render, get_object_or_404 from .models import Post def detail(request, pk): post = get_object_or_404(Post, pk=pk) # 记得在顶部引入 markdown 模块 post.body = markdown.markdown(post.body, extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) return render(request, 'blog/detail.html', context={'post': post}) 注意这里我们给 markdown 渲染函数传递了额外的参数 extensions,它是对 Markdown 语法的拓展,这里我们使用了三个拓展,分别是 extra、codehilite、toc。extra 本身包含很多拓展,而 codehilite 是语法高亮拓展,这为我们后面的实现代码高亮功能提供基础,而 toc 则允许我们自动生成目录(在以后会介绍) 此时django渲染后模板{{ post.body }} 就不是原始的markdown文本了,而是HTML文本.django处于安全考虑,会对HTML的代码转义,就是现实原始HTML代码.所以要加过滤器safe.告诉django这段文本是安全的.{{ safe.body | safe }} markdowo另外一种渲染方法:(首先实例化,然后convert转化) # 首先实例化一个 Markdown 类,用于渲染 body 的文本 md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', ]) # 先将 Markdown 文本渲染成 HTML 文本 # strip_tags 去掉 HTML 文本的全部 HTML 标签 # 从文本摘取前 54 个字符赋给 excerpt self.excerpt = strip_tags(md.convert(self.body))[:54]
3 引用样式文件,实现代码高亮.
4 自动生成文章目录.
只要加入markdown.extensions.toc,的拓展就会自动生成目录
写markdown的时候,在想要生成母的的地方插入[TOC]标记就可以啦.
如果要在除了文章中以外使用目录,那么增加一个toc属性就可以啦.如下,然后就可以在模板中使用了post.toc这个目录列表.
blog/views.py class PostDetailView(DetailView): # 这些属性的含义和 ListView 是一样的 model = Post template_name = 'blog/detail.html' context_object_name = 'post' def get(self, request, *args, **kwargs): # ... def get_object(self, queryset=None): # 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染 md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) post.body = md.convert(post.body) post.toc = md.toc return post def get_context_data(self, **kwargs): # ...
第二部分:自定义模板标签:
1 创建目录结构,在app应用下创建一个templatetags的文件夹,文件夹下创建一个init.py
文件,使他成为一个python包,之后在templatetags文件夹下创建一个app_tags.py的文件,用来存放自定义的模板标签代码.
2编写自定义模板标签
blog/templatetags/blog_tags.py from django import template #从django中导入这个模块 from ..models import Post #导入你的app模型 register = template.Library() #获取注册自定义标签对象 @register.simple_tag #中simple_tag方式进行装饰 def get_recent_posts(num=5): #h函数名就是你的自定义标签名 return Post.objects.all().order_by('-created_time')[:num] # 返回值就是自定标签在模板加载时的值 使用方式: 1 必须导入 自定义模板 {% load blog_tags %} 2 使用 {% get_recent_posts %} 3 可以起别名,进使用: {% get_recent_posts as post_list %}
5 美化锚点(文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太美观,)
使用django处理text文本的slugify方法.
blog/views.py from django.utils.text import slugify from markdown.extensions.toc import TocExtension class PostDetailView(DetailView): # 这些属性的含义和 ListView 是一样的 model = Post template_name = 'blog/detail.html' context_object_name = 'post' def get(self, request, *args, **kwargs): # ... def get_object(self, queryset=None): # 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染 md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', # 记得在顶部引入 TocExtension 和 slugify TocExtension(slugify=slugify), ]) post.body = md.convert(post.body) post.toc = md.toc return post def get_context_data(self, **kwargs): # ... ------------------------------------------------------------------- extensions 中的 toc 拓展不再是字符串 markdown.extensions.toc ,而是 TocExtension 的实例。TocExtension 在实例化时其 slugify 参数可以接受一个函数作为参数,这个函数将被用于处理标题的锚点值。Markdown 内置的处理方法不能处理中文标题,所以我们使用了 django.utils.text 中的 slugify 方法,该方法可以很好地处理中文。
第三部分;表单设计:
1 编写模型:
导入forms模板表单类必须继承forms.Form或者forms.ModelForm类.
在Meta类里面指定一些与表单相关的东西>
model=comment 指定表单的数据库模型
fileds=['name','email','url','text'] #指定表单要显示的字段.
2 编写视图
# HTTP 请求有 get 和 post 两种,一般用户通过表单提交数据都是通过 post 请求, # 因此只有当用户的请求为 post 时才需要处理表单数据。 if request.method == 'POST': # 用户提交的数据存在 request.POST 中,这是一个类字典对象。 # 我们利用这些数据构造了 CommentForm 的实例,这样 Django 的表单就生成了。 form = CommentForm(request.POST) # 当调用 form.is_valid() 方法时,Django 自动帮我们检查表单的数据是否符合格式要求。 if form.is_valid(): # 检查到数据是合法的,调用表单的 save 方法保存数据到数据库, # commit=False 的作用是仅仅利用表单的数据生成 Comment 模型类的实例,但还不保存评论数据到数据库。 comment = form.save(commit=False) # 将评论和被评论的文章关联起来。 comment.post = post # 最终将评论数据保存进数据库,调用模型实例的 save 方法 comment.save() # 重定向到 post 的详情页,实际上当 redirect 函数接收一个模型的实例时,它会调用这个模型实例的 get_absolute_url 方法, # 然后重定向到 get_absolute_url 方法返回的 URL。 return redirect(post) else: # 检查到数据不合法,重新渲染详情页,并且渲染表单的错误。 # 因此我们传了三个模板变量给 detail.html, # 一个是文章(Post),一个是评论列表,一个是表单 form # 注意这里我们用到了 post.comment_set.all() 方法, # 这个用法有点类似于 Post.objects.all() # 其作用是获取这篇 post 下的的全部评论, # 因为 Post 和 Comment 是 ForeignKey 关联的, # 因此使用 post.comment_set.all() 反向查询全部评论。 # 具体请看下面的讲解。 comment_list = post.comment_set.all() context = {'post': post, 'form': form, 'comment_list': comment_list } return render(request, 'blog/detail.html', context=context) # 不是 post 请求,说明用户没有提交数据,重定向到文章详情页。 return redirect(post)
3 绑定url
4自动渲染表单
<form action="{% url 'comments:post_comment' post.pk %}" method="post" class="comment-form"> {% csrf_token %} <div class="row"> <div class="col-md-4"> <label for="{{ form.name.id_for_label }}">名字:</label> {{ form.name }} {{ form.name.errors }} </div> <div class="col-md-4"> <label for="{{ form.email.id_for_label }}">邮箱:</label> {{ form.email }} {{ form.email.errors }} </div> <div class="col-md-4"> <label for="{{ form.url.id_for_label }}">URL:</label> {{ form.url }} {{ form.url.errors }} </div> <div class="col-md-12"> <label for="{{ form.text.id_for_label }}">评论:</label> {{ form.text }} {{ form.text.errors }} <button type="submit" class="comment-btn">发表</button> </div> </div> <!-- row --> </form> 使用 Django 表单的一个好处就是 Django 能帮我们自动渲染表单。我们在表单的视图函数里传递了一个 form 变量给模板,这个变量就包含了自动生成 HTML 表单的全部数据。
第四部分:错误
1 运行程序时,No module named django.core.wsgi:
解决方法,切换到虚拟环境中,安装django
2 在用 Gunicorn 启动服务器进程时,出现了如下的报错: ImportError: No module named 'blogproject'
解决方法:切换到项目根目录中执行 gunicorn命令.也就是和manage.py在同一目录下.
如下:切换到这里, ls 结果如下.
blog comments nohup.out static whoosh_index blogproject manage.py requirement.txt templates
第五部分:复写save方法
1 作用,当保存内容的时候,可以按自己的需求进行保存字段
2 重写方法:在调用父方法之前,将数据整理成自己想要的数据,然后调用父方法.
def save(self, *args, **kwargs): # 如果没有填写摘要 if not self.excerpt: # 首先实例化一个 Markdown 类,用于渲染 body 的文本 md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', ]) # 先将 Markdown 文本渲染成 HTML 文本 # strip_tags 去掉 HTML 文本的全部 HTML 标签 # 从文本摘取前 54 个字符赋给 excerpt self.excerpt = strip_tags(md.convert(self.body))[:54] # 调用父类的 save 方法将数据保存到数据库中 super(Post, self).save(*args, **kwargs)
第六部分:django部分方法:
1 模板过滤器:(truncatechars),实现获取文本中的最前面字符串
{{ post.body|truncatechars:54 }} #获取post.body的前54个字符.
2 去掉html文本全部html标签:strip_tags()
from django.utils.html import strip_tags self.excerpt = strip_tags(md.convert(self.body))#去出self.body的html标签
3 通用视图:
1 类视图调用实例的get方法返回响应的结果.(也就是渲染过后的最终结果) 2 #类视图中中从url捕获的命名组参数值保存在实例的kwargs属性中(是一个字典).非命名组参数值保存在实例的args属性中(是一个里列表).获取kwarg属性的值,使用self.kwargs.get('pk') 3 重写方法的时候,应该首先调用父类的方法,获得原来的数据.
3.1 通用视图:ListView.
作用:针对从数据库获取某个模型列表数据的视图.
#1 导入模块 from django.views.generic import ListView #创建类,继承ListView class IndexView(ListView): #model指定获取哪一个模型的数据 model = Post #指定要渲染的模板 template_name = 'blog/index.html' #指定要传给模板的变量,ListView自动调用model.objects.all(),把结果给变量post_list' context_object_name = 'post_list' #重写get_queryset()方法,可以传给模板的变量自定义,返回值作为context_object_name的值. def get_queryset(self): cate = get_object_or_404(Category, pk=self.kwargs.get('pk')) return super(CategoryView, self).get_queryset().filter(category=cate) 配置urls.py:调用类的as_view()方法进行渲染. blog/urls.py app_name = 'blog' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), ... ]
3.2 通用视图:detailView.
#导入模块 from django.views.generic import ListView, DetailView # 创建类,继承DetailView class PostDetailView(DetailView): # 这些属性的含义和 ListView 是一样的 model = Post template_name = 'blog/detail.html' context_object_name = 'post' #调用get方法返回响应的结果. def get(self, request, *args, **kwargs): # 覆写 get 方法的目的是因为每当文章被访问一次,就得将文章阅读量 +1 # get 方法返回的是一个 HttpResponse 实例 # 之所以需要先调用父类的 get 方法,是因为只有当 get 方法被调用后, # 才有 self.object 属性,其值为 Post 模型实例,即被访问的文章 post response = super(PostDetailView, self).get(request, *args, **kwargs) # 将文章阅读量 +1 # 注意 self.object 的值就是被访问的文章 post self.object.increase_views() # 视图必须返回一个 HttpResponse 对象 return response #这个方法返回模型的对象,是context_object_name指向的对象.,可以重新改造. def get_object(self, queryset=None): # 覆写 get_object 方法的目的是因为需要对 post 的 body 值进行渲染 post = super(PostDetailView, self).get_object(queryset=None) post.body = markdown.markdown(post.body, extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) return post #这个函数传递额外内容到模板,返回一个context对象.直接用字典的update方法,增加新的变量. def get_context_data(self, **kwargs): # 覆写 get_context_data 的目的是因为除了将 post 传递给模板外(DetailView 已经帮我们完成), # 还要把评论表单、post 下的评论列表传递给模板。 context = super(PostDetailView, self).get_context_data(**kwargs) form = CommentForm() comment_list = self.object.comment_set.all() context.update({ 'form': form, 'comment_list': comment_list }) return context
4 通用视图:重定向
用来进行跳转,默认永久重定向(301)可以在urls.py中使用
from django.conf.urls import patterns, url #首先导入 from django.views.generic.base import RedirectView urlpatterns = patterns('', #直接使用永久重定向 url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'), #直接使用重定向,不是永久的. url(r'^go-to-ziqiangxuetang/$', RedirectView.as_view(url='http://www.ziqiangxuetang.com',permant=False), name='go-to-zqxt'), ) ------------------------------------------------------------------------- 自定义使用重定向 1 导入 from django.shortcuts import get_object_or_404 from django.views.generic.base import RedirectView #创建模块继承 class ArticleCounterRedirectView(RedirectView): url = ' # 要跳转的网址,' # url 可以不给,用 pattern_name 和 get_redirect_url() 函数 来解析到要跳转的网址 permanent = False #是否为永久重定向, 默认为 True query_string = True # 是否传递GET的参数到跳转网址,True时会传递,默认为 False pattern_name = 'article-detail' # 用来跳转的 URL, 看下面的 get_redirect_url() 函数 # 如果url没有设定,此函数就会尝试用pattern_name和从网址中捕捉的参数来获取对应网址 # 即 reverse(pattern_name, args) 得到相应的网址, # 在这个例子中是一个文章的点击数链接,点击后文章浏览次数加1,再跳转到真正的文章页面 def get_redirect_url(self, *args, **kwargs): article = get_object_or_404(Article, pk=kwargs['pk']) article.update_counter() # 更新文章点击数,在models.py中实现 #调用父类来实现 return super(ArticleCounterRedirectView, self).get_redirect_url(*args, **kwargs)
4 Pagination分页效果:
4.1 简单使用:
1 导入:from django.core.paginator import Paginator 2 实例化pagintor对象,传入一个需要分页的列表对象,就可以得到分页后的对象数据 p = Paginator(item_list_obj,2) #对item_list进行分页,每页显示两个. =----------------------------------------------------------------------------= 实例方法; 1 取特定页的数据: page_num_list=p.page(num) #num是特定的页,, 2 显示特页的数据: page_num_list . object_list 3 查询特定也的页码数: number= page_num_list.number 4 查看分页后的总页数: p.num_pages 5 查看是否还有上一页: page_num_list . has_previous() 6 查看是否还有上一页的页码 page_num_list.previous_page_number() 7 查看是否还有下一页: page_num_list . has_next() 8 查看下一页的页码 page_num_list.next_page_number() 例如: from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.shortcuts import render def listing(request): contact_list = Contacts.objects.all() paginator = Paginator(contact_list, 25) # 每页显示 25 个联系人 page = request.GET.get('page') try: contacts = paginator.page(page) except PageNotAnInteger: # 如果用户请求的页码号不是整数,显示第一页 contacts = paginator.page(1) except EmptyPage: # 如果用户请求的页码号超过了最大页码号,显示最后一页 contacts = paginator.page(paginator.num_pages) return render(request, 'list.html', {'contacts': contacts})
4.2 在类视图中,逻辑不需要写,只需要指定paginate_by属性开启分页功能就可以啦
blog/views.py class IndexView(ListView): model = Post template_name = 'blog/index.html' context_object_name = 'post_list' # 指定 paginate_by 属性后开启分页功能,其值代表每一页包含多少篇文章 paginate_by = 10
4.3 模板中设置分页导航:
ListView 传递了以下和分页有关的模板变量供我们在模板中使用: paginator : Paginator 的实例。 page_obj :前请求页面分页对象。 is_paginated:是否已分页。只有当分页后页面超过两页时才算已分页。 object_list:请求页面的对象列表,和 post_list 等价。所以在模板中循环文章列表时可以选 post_list ,也可以选 object_list。 ------------------------------------------------------------------------------ 例子: {% if is_paginated %} <div class="pagination-simple"> <!-- 如果当前页还有上一页,显示一个上一页的按钮 --> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}">上一页</a> {% endif %} <!-- 显示当前页面信息 --> <span class="current">第 {{ page_obj.number }} 页 / 共 {{ paginator.num_pages }} 页</span> <!-- 如果当前页还有下一页,显示一个下一页的按钮 --> {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}">下一页</a> {% endif %} </div> {% endif %}
5 Q对象:Q 对象用于包装查询表达式,其作用是为了提供复杂的查询逻辑
blog/views.py #导入Q对象 from django.db.models import Q def search(request): q = request.GET.get('q') error_msg = '' if not q: error_msg = "请输入关键词" return render(request, 'blog/index.html', {'error_msg': error_msg}) ---------------------------------------------------------------- """这里 Q(title__icontains=q) | Q(body__icontains=q) 表示标题(title)含有关键词 q 或者正文(body)含有关键词 q ,或逻辑使用 | 符号。如果不用 Q 对象,就只能写成 title__icontains=q, body__icontains=q,这就变成标题(title)含有关键词 q 且正文(body)含有关键词 q,就达不到我们想要的目的。 """ post_list = Post.objects.filter(Q(title__icontains=q) | Q(body__icontains=q)) -------------------------------------------------------------------------- return render(request, 'blog/index.html', {'error_msg': error_msg, 'post_list': post_list})
第六部分:Django Haystack 全文检索与关键词高亮
6.1 介绍 :django-haystack 是一个专门提供搜索功能的 django 第三方应用,它支持 Solr、Elasticsearch、Whoosh、Xapian 等多种搜索引擎,配合著名的中文自然语言处理库 jieba 分词,就可以为我们的博客提供一个效s果不错的博客文章搜索系统
6.2 安装依赖包:
pip install whoosh
pip install django-haystack
pip install jieba
6.3 简单配置:
1 将django haystack安装到项目中:
blogproject/settings.py INSTALLED_APPS = [ 'django.contrib.admin', # 其它 app... 'haystack', 'blog', 'comments', ] -------------------------------------------- 并在后面添加以下配置 blogproject/settings.py HAYSTACK_CONNECTIONS = { 'default': { #engine 指定使用的搜索引擎,将安装后的whoosh文件夹中的 #从你安装的 haystack 中把 haystack/backends/whoosh_backends.py 文件拷贝到 #blog/ 下,重命名为 whoosh_cn_backend.py ----------------------------------------------- 'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine', #设置索引文件存放的位置,建立索引时会自动创建. 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), }, } #指定如何对搜素结果分页,这里设置为每10项一页 HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10 #设置为么当有文章更新时,就更新索引. HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
6.4 处理数据:(使用那些数据建立索引,以及如何存放索引)
1 在blog应用下新建一个search_indexes.py的文件(规定对那个app进行全文检索,就在那个app下面创建一个search_indexes.py文件,这是索引文件提高检索速度.) 2 写入以下代码 -------------------------------------------------------------- from haystack import indexes from .models import Post #一定要继承indexes.SearchIndex, indexes.Indexable class PostIndex(indexes.SearchIndex, indexes.Indexable): #索引里面必须有且只有个一个document=True,将使用这个字段的内容进行检索. #一般约定为text,以防止后台混乱. #use_template=True,允许我们使用数据模板取建立搜索引擎索引文件, #就是在索引里面放一些东西,比如title,这样就可以根据title进行搜索. text = indexes.CharField(document=True, use_template=True) def get_model(self): return Post def index_queryset(self, using=None): return self.get_model().objects.all() ----------------------------------------------------------- 3 创建数据模板: 路径:项目名/templates/search/indexes/youapp/\<model_name>_text.txt ------------------------------------------------- 例如: templates/search/indexes/blog/post_text.txt #根据这两个字段建立索引,索引的时候会对这两个字段做全文检索匹配,然后将匹配结果排序后返回. {{ object.title }} {{ object.body }}
6.5 配置URL:
搜索的视图函数和 URL 模式 django haystack 都已经帮我们写好了,只需要项目的 urls.py 中包含它:
blogproject/urls.py urlpatterns = [ # 其它... url(r'^search/', include('haystack.urls')), ]
6.5 修改搜索表单,让它提交数据到djangohaystack视图对应的URL
主要是把表单的 action 属性改为 {% url 'haystack_search' %} 必须写成这个action --------------------------------------------------- form role="search" method="get" id="searchform" action="{% url 'haystack_search' %}"> <input type="search" name="q" placeholder="搜索" required> <button type="submit"><span class="ion-ios-search-strong"></span></button> </form>
6.6 创建搜索结果页面创建搜索结果页面
haystack_search
视图函数会将搜索结果传递给模板 search/search.html,因此创建这个模板文件,对搜索结果进行渲染:
haystack对搜索结果做了分页,传给模的变量包含一个page对象.包含一个query变量,query
变量的值即为用户搜索的关键词。
所以我们从 page 中取出这一页对应的搜索结果,然后对其循环显示,即 {% for result in page.object_list %}。另外要取得 Post(文章)以显示文章的数据如标题、正文,需要从 result 的 object 属性中获取。
6.7 高亮关键词:
在 django haystack 中实现含有用户搜索的关键词的地方都是被标红的效果也非常简单,只需要使用 {% highlight %} 模板标签即可,
使用默认值 {% highlight result.summary with query %} # 这里我们为 {{ result.summary }} 里所有的 {{ query }} 指定了一个<div></div>标签,并且将class设置为highlight_me_please,这样就可以自己通过CSS为{{ query }}添加高亮效果了,怎么样,是不是很科学呢 {% highlight result.summary with query html_tag "div" css_class "highlight_me_please" %} # 可以 max_length 限制最终{{ result.summary }} 被高亮处理后的长度 {% highlight result.summary with query max_length 40 %}
6.8 修改搜索引擎为中文分词.
们使用 Whoosh 作为搜索引擎,但在 django haystack 中为 Whoosh 指定的分词器是英文分词器,可能会使得搜索结果不理想,我们把这个分词器替换成 jieba 中文分词器。 从你安装的 haystack 中把 haystack/backends/whoosh_backends.py 文件拷贝到 blog/ 下,重命名为 whoosh_cn_backend.py(之前我们在 settings.py 中 的 HAYSTACK_CONNECTIONS 指定的就是这个文件),然后找到如下一行代码:
schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True)
将其中的 analyzer 改为 ChineseAnalyzer,当然为了使用它,你需要在文件顶部引入:from jieba.analyse import ChineseAnalyzer。
from jieba.analyse import ChineseAnalyzer ... #注意先找到这个再修改,而不是直接添加 schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(),field_boost=field_class.boost, sortable=True)
6.9 建立索引文件:
运行命令 python manage.py rebuild_index
就可以建立索引文件了。
6.10:大功完成,开始搜索吧.