在整個博客的搭建中,文章相關的功能是最關鍵的,好比文章相關數據模型的設計、不一樣分類下文章的篩選顯示、以及對顯示功能完善的分頁功能。本文針對本博客的文章主要功能經過這幾方面進行介紹,參考所有代碼請到Github查看。html
在數據庫設計以前,咱們首先要肯定網站功能,結合本站,最主要的是咱們的博文表,名字能夠直接叫作 article,其中包含博文的標題、內容、發表時間、修改時間、分類、標籤、閱讀量、喜歡量、做者、關鍵詞等。博文表直接關聯的有分類表(一對多)、標籤表(多對多)和文章關鍵詞表 (多對多),分類表是隸屬在導航欄下,到此咱們能夠肯定出這些最基本的數據表,博客(Article)、分類(Category)、標籤(Tag)與文章關鍵詞 (Keyword)、導航(Bigcategory)。前端
首先打開項目根目錄,建立 Storm APPpython
python manage.py startapp Storm
在 Myblog -> storm -> models.py 中首先設計導航表 (Bigcategory)與分類表(Category)。正則表達式
from django.db import models from django.conf import settings #引入定義字段SEO設置(提早設置)與自定義User(參考管理用戶登陸與註冊博文) from django.shortcuts import reverse #查找URL import re # 網站導航菜單欄表 class BigCategory(models.Model): # 導航名稱 name = models.CharField('導航大分類', max_length=20) # 用做文章的訪問路徑,每篇文章有獨一無二的標識 slug = models.SlugField(unique=True) #此字符串字段能夠創建惟一索引 # 分類頁描述 description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,help_text='用來做爲SEO中description,長度參考SEO標準') # 分類頁Keywords keywords = models.TextField('關鍵字', max_length=240, default=settings.SITE_KEYWORDS,help_text='用來做爲SEO中keywords,長度參考SEO標準') class Meta: #元信息 # admin中顯示的表名稱 verbose_name = '一級導航' verbose_name_plural = verbose_name #複數形式相同 def __str__(self): return self.name # 導航菜單分類下的下拉菜單分類 class Category(models.Model): # 分類名字 name = models.CharField('文章分類', max_length=20) # 用做分類路徑,獨一無二 slug = models.SlugField(unique=True) # 分類欄目頁描述 description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,help_text='用來做爲SEO中description,長度參考SEO標準') # 導航菜單一對多二級菜單,django2.0後定義外鍵和一對一關係的時候須要加on_delete選項,此參數爲了不兩個表裏的數據不一致問題 bigcategory = models.ForeignKey(BigCategory,related_name="Category", on_delete=models.CASCADE,verbose_name='大分類') class Meta:#元信息 # admin中顯示的表名稱 verbose_name = '二級導航' verbose_name_plural = verbose_name # 默認排序 ordering = ['name'] def __str__(self): return self.name #返回當前的url(一級分類+二級分類) def get_absolute_url(self): return reverse('blog:category', kwargs={'slug': self.slug, 'bigslug': self.bigcategory.slug}) #尋找路由爲blog:category的url #返回當前二級分類下全部發表的文章列表 def get_article_list(self): return Article.objects.filter(category=self)
標籤(Tag)與關鍵字(Keyword)表的建立:數據庫
# 文章標籤 class Tag(models.Model): name = models.CharField('文章標籤', max_length=20) slug = models.SlugField(unique=True) description = models.TextField('描述', max_length=240, default=settings.SITE_DESCRIPTION,help_text='用來做爲SEO中description,長度參考SEO標準') class Meta: verbose_name = '標籤' verbose_name_plural = verbose_name ordering = ['id'] def __str__(self): return self.name def get_absolute_url(self): return reverse('blog:tag', kwargs={'tag': self.name}) def get_article_list(self): #返回當前標籤下全部發表的文章列表 return Article.objects.filter(tags=self) # 文章關鍵詞,用來做爲 SEO 中 keywords class Keyword(models.Model): name = models.CharField('文章關鍵詞', max_length=20) class Meta: verbose_name = '關鍵詞' verbose_name_plural = verbose_name ordering = ['name'] def __str__(self): return self.name
博客(Article)表的建立:django
from mdeditor.fields import MDTextField #admin markdown編輯器插件 import markdown #導入markdown # 文章 class Article(models.Model): # 文章默認縮略圖 IMG_LINK = '/static/images/article/default.jpg' # 文章信息(做者一對多註冊用戶,這樣用戶也能夠有發文權限) author = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, verbose_name='做者') title = models.CharField(max_length=150, verbose_name='文章標題') summary = models.TextField('文章摘要', max_length=230, default='文章摘要等同於網頁description內容,請務必填寫...') # 文章內容(普通字段models.TextField(verbose_name='文章內容')) body = MDTextField(verbose_name='文章內容') #圖片連接 img_link = models.CharField('圖片地址', default=IMG_LINK, max_length=255) #自動添加建立時間 create_date = models.DateTimeField(verbose_name='建立時間', auto_now_add=True) #自動添加修改時間 update_date = models.DateTimeField(verbose_name='修改時間', auto_now=True) #瀏覽點贊整數字段 views = models.IntegerField('閱覽量', default=0) loves = models.IntegerField('喜好量', default=0) # 文章惟一標識符 slug = models.SlugField(unique=True) #分類一對多文章 #related_name反向查詢 category = models.ForeignKey(Category,on_delete=models.CASCADE, verbose_name='文章分類') #標籤多對多文章 tags = models.ManyToManyField(Tag, verbose_name='標籤') #文章關鍵詞多對多文章 keywords = models.ManyToManyField(Keyword, verbose_name='文章關鍵詞',help_text='文章關鍵詞,用來做爲SEO中keywords,最好使用長尾詞,3-4個足夠') class Meta: verbose_name = '博文' verbose_name_plural = verbose_name ordering = ['-create_date'] def __str__(self): return self.title[:20] #返回當前文章的url def get_absolute_url(self): return reverse('blog:article', kwargs={'slug': self.slug}) #將內容markdown def body_to_markdown(self): return markdown.markdown(self.body, extensions=[ # 包含 縮寫、表格等經常使用擴展 'markdown.extensions.extra', # 語法高亮擴展 'markdown.extensions.codehilite', # 自動生成目錄擴展 'markdown.extensions.toc', ]) #點贊+1方法 def update_loves(self): self.loves += 1 self.save(update_fields=['loves']) #更新字段 #瀏覽+1方法 def update_views(self): self.views += 1 self.save(update_fields=['views']) #更新字段 #前篇方法:當前小於文章並倒序排列的第一個 def get_pre(self): return Article.objects.filter(id__lt=self.id).order_by('-id').first() #後篇方法:當前大於文章並正序排列的第一個 def get_next(self): return Article.objects.filter(id__gt=self.id).order_by('id').first()
其中模型中定義的一些方便給前端傳遞數據的方法,可使用Django的自定義templatetags功能,前端引用模板語言能夠達到一樣效果並使用更自由。markdown
在此以前先配置urlapp
#Myblog/urls.py from django.conf.urls import re_path,include urlpatterns = [ ... # storm博客應用 re_path(r'^',include('Storm.urls', namespace='blog')), ... ]
#Myblog/Storm/urls.py from django.urls import path from django.conf.urls import re_path from Storm import views app_name='Storm' urlpatterns = [ ... #一級二級菜單分類文章列表 #django 2.x中用re_path兼容1.x中的url中的方法(如正則表達式) re_path(r'category/(?P<bigslug>.*?)/(?P<slug>.*?)/',views.CtegoryView.as_view(),name='category'),#?分隔實際的URL和參數,?p數據庫裏面惟一索引 & URL中指定的參數間的分隔符 re_path(r'category/(?P<bigslug>.*?)/',views.CtegoryView.as_view(),name='category'), # 標籤搜索文章列表 re_path(r'tags/(?P<tagslug>.*?)/', views.CtegoryView.as_view(),name='tag'), ... ]
網站前端功能中,能夠進行篩選文章列表顯示的途徑有:經過一級導航、二級分類、標籤以及自定義一級導航下的最新與最熱篩選,咱們經過url傳參進行視圖分別的處理。 通常的,視圖函數從數據庫中獲取文章列表數據:數據庫設計
def index(request): # ... def archives(request, year, month): # ... def category(request, pk): # ...
在Django中專門提供了各類功能的處理類來使咱們快捷的處理數據,其中ListView視圖幫咱們內部作這些查詢等操做,只需將 model 指定爲 Article,告訴 Django 我要獲取的模型是 Article。template_name 指定這個視圖渲染的模板。context_object_name 指定獲取的模型列表數據保存的變量名。這個變量會被傳遞給模板。 paginate_by 經過指定屬性便可開啓分頁功能。編輯器
from django.shortcuts import render,get_object_or_404 from Storm import models #從數據庫中獲取某個模型列表數據基類ListView from django.views.generic import ListView #Django自帶的分頁模塊 from django.core.paginator import Paginator #分類查找文章列表視圖類 class CtegoryView(ListView): model=models.Article template_name = 'articleList.html' context_object_name = 'articleList' paginate_by = 8
因爲針對不一樣url進行文章篩選的方式不一樣,因此咱們經過覆寫了父類的 get_queryset 方法獲取定製文章列表數據,經過覆寫def get_context_data方法來獲取定製的分頁效果,其中調用了自定義方法 pagination_data 得到顯示分頁導航條須要的數據。
#分類查詢文章與視圖類 class CtegoryView(ListView): model=models.Article template_name = 'articleList.html' context_object_name = 'articleList' paginate_by = 8 #指定 paginate_by 屬性來開啓分頁功能 #覆寫了父類的 get_queryset 方法獲取定製數據 #類視圖中,從 URL 捕獲的命名組參數值保存在實例的 kwargs 屬性(是一個字典)裏,非命名組參數值保存在實例的 args 屬性(是一個列表)裏 def get_queryset(self): #get_queryset方法得到所有文章列表 queryset = super(CtegoryView, self).get_queryset() # 導航菜單 big_slug = self.kwargs.get('bigslug', '') # 二級菜單 slug = self.kwargs.get('slug', '') # 標籤 tag_slug = self.kwargs.get('tagslug', '') if big_slug: big = get_object_or_404(models.BigCategory, slug=big_slug) queryset = queryset.filter(category__bigcategory=big) if slug: if slug=='newest': queryset = queryset.filter(category__bigcategory=big).order_by('-create_date') elif slug=='hottest': queryset = queryset.filter(category__bigcategory=big).order_by('-loves') else : slu = get_object_or_404(models.Category, slug=slug) queryset = queryset.filter(category=slu) if tag_slug: tlu = get_object_or_404(models.Tag, slug=tag_slug) queryset = queryset.filter(tags=tlu) return queryset #在視圖函數中將模板變量傳遞給模板是經過給 render 函數的 context 參數傳遞一個字典實現的 def get_context_data(self, **kwargs): # 首先得到父類生成的傳遞給模板的字典。 context = super().get_context_data(**kwargs) paginator = context.get('paginator') page = context.get('page_obj') is_paginated = context.get('is_paginated') # 調用本身寫的 pagination_data 方法得到顯示分頁導航條須要的數據,見下方。 pagination_data = self.pagination_data(paginator, page, is_paginated) # 將分頁導航條的模板變量更新到 context 中,注意 pagination_data 方法返回的也是一個字典。 context.update(pagination_data) return context def pagination_data(self, paginator, page, is_paginated): if not is_paginated:# 若是沒有分頁,則無需顯示分頁導航條,不用任何分頁導航條的數據,所以返回一個空的字典 return {} # 當前頁左邊連續的頁碼號,初始值爲空 left = [] # 當前頁右邊連續的頁碼號,初始值爲空 right = [] # 標示第 1 頁頁碼後是否須要顯示省略號 left_has_more = False # 標示最後一頁頁碼前是否須要顯示省略號 right_has_more = False # 標示是否須要顯示第 1 頁的頁碼號。 first = False # 標示是否須要顯示最後一頁的頁碼號 last = False # 得到用戶當前請求的頁碼號 page_number = page.number # 得到分頁後的總頁數 total_pages = paginator.num_pages # 得到整個分頁頁碼列表,好比分了四頁,那麼就是 [1, 2, 3, 4] page_range = paginator.page_range #請求的是第一頁的數據 if page_number == 1: #獲取了當前頁碼後連續兩個頁碼 right = page_range[page_number:(page_number + 2) if (page_number + 2) < paginator.num_pages else paginator.num_pages] # 若是最右邊的頁碼號比最後一頁的頁碼號減去 1 還要小, # 說明最右邊的頁碼號和最後一頁的頁碼號之間還有其它頁碼,所以須要顯示省略號,經過 right_has_more 來指示。 if right[-1] < total_pages - 1: right_has_more = True # 若是最右邊的頁碼號比最後一頁的頁碼號小,說明當前頁右邊的連續頁碼號中不包含最後一頁的頁碼 # 因此須要顯示最後一頁的頁碼號,經過 last 來指示 if right[-1] < total_pages: last = True # 若是用戶請求的是最後一頁的數據, elif page_number == total_pages: #獲取了當前頁碼前連續兩個頁碼 left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1] # 若是最左邊的頁碼號比第 2 頁頁碼號還大, # 說明最左邊的頁碼號和第 1 頁的頁碼號之間還有其它頁碼,所以須要顯示省略號,經過 left_has_more 來指示。 if left[0] > 2: left_has_more = True # 若是最左邊的頁碼號比第 1 頁的頁碼號大,說明當前頁左邊的連續頁碼號中不包含第一頁的頁碼, # 因此須要顯示第一頁的頁碼號,經過 first 來指示 if left[0] > 1: first = True else: # 用戶請求的既不是最後一頁,也不是第 1 頁,則須要獲取當前頁左右兩邊的連續頁碼號, # 這裏只獲取了當前頁碼先後連續兩個頁碼,你能夠更改這個數字以獲取更多頁碼。 left = page_range[(page_number - 3) if (page_number - 3) > 0 else 0:page_number - 1] right = page_range[page_number:(page_number + 2) if (page_number + 2) < paginator.num_pages else paginator.num_pages] # 是否須要顯示最後一頁和最後一頁前的省略號 if right[-1] < total_pages - 1: right_has_more = True if right[-1] < total_pages: last = True # 是否須要顯示第 1 頁和第 1 頁後的省略號 if left[0] > 2: left_has_more = True if left[0] > 1: first = True data = { 'left': left, 'right': right, 'left_has_more': left_has_more, 'right_has_more': right_has_more, 'first': first, 'last': last, } return data
經過視圖類處理後的文章數據 articleList 在前端中用Django的模板語言能夠直接引用,前端模板根據需求進行自定義。
{% for article in articleList %} {{article.category.name}} {{article.title}} ... {{article.create_date | date:"Y-m-j"}}< {{article.loves}} {% endfor %}
分頁傳來的數據中,除了咱們自定義的 data 數據,還自帶了paginator
:Paginator 的實例,page_obj
:當前請求頁面分頁對象,is_paginated
:是否開啓分頁,其中page_obj
具備當前頁屬性page_obj.number
、判斷是否含有上一頁:page_obj.has_previous
,是否含有下一頁:page_obj.has_next
。注意咱們在這裏用了Bootstrap的分頁模板,須要在開頭引入相關文件。
{% if is_paginated %} <div class="PageList"> <nav aria-label="Page navigation"> <ul class="pagination pagination-sm"> <li class="{% if not page_obj.has_previous %} disabled {% endif %}"> <a href="{% if page_obj.has_previous %} ?page={{ page_obj.previous_page_number }} {% endif %}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {% if first %} <li> <a href="?page=1">1</a> </li> {% endif %} {% if left %} {% if left_has_more %} <li> <span>...</span> </li> {% endif %} {% for i in left %} <li> <a href="?page={{ i }}">{{ i }}</a> </li> {% endfor %} {% endif %} <li class="active"><a href="?page={{ page_obj.number }}">{{ page_obj.number }}</a></li> {% if right %} {% for i in right %} <li> <a href="?page={{ i }}">{{ i }}</a> </li> {% endfor %} {% if right_has_more %} <li> <span>...</span> </li> {% endif %} {% endif %} {% if last %} <li> <a href="?page={{ paginator.num_pages }}">{{ paginator.num_pages }}</a> </li> {% endif %} <li class="{% if not page_obj.has_next %} disabled {% endif %}"> <a href="{% if page_obj.has_next %} ?page={{ page_obj.next_page_number }} {% endif %}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> </div>
<small>參考:追夢任務 | Django Pagination分頁功能 </small>