Django實戰之開發面向用戶的界面

書接: https://blog.csdn.net/weixin_32759777/article/details/104098399

## 搭建基礎結構與展示文章數據
**分析url和頁面數據**
博客首頁
博文詳情頁
分類列表頁
標籤列表頁
友鏈展示頁
提前定義好URL
博客首頁      :https://localhost/
博文詳情頁  :https://localhost/post/<post_id>.html
分類列表頁  :https://localhost/category/<category_id>/
標籤列表頁  :https://localhost/tag/<tag_id>/
友鏈展示頁  :https://localhost/links/
通常博客頁面佈局如下:
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200217051539116.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zMjc1OTc3Nw==,size_16,color_FFFFFF,t_70)
根據上面可知道大部分數據是公用的 本質上都是文章列表頁面 只是其他信息稍有差別
因此view的邏輯大致分爲兩類
根據不同的查詢條件展示列表頁
展示博文詳情頁
不過友鏈是獨立的邏輯
所以View只需要三個即可
列表頁View : 根據不同的查詢條件分別展示博客首頁、分類列表頁、和標籤列表頁
文章頁View : 展示博文詳情頁
友鏈頁View : 展示所有友情鏈接


在setting.py同級urls.py中添加代碼如下

```python
from blog.views import post_list
from blog.views import post_detail
from config.views import links



# from testblog.custom_site import custom_site
urlpatterns = [
    
    re_path('^$',post_list),
    re_path('^category/(?P<category_id>\d+)/$',post_list),
    re_path('^tag/(?P<tag_id>\d+)/$',post_list),
    re_path('^post/(?P<post_id>\d+).html$',post_detail),
    re_path('^links/$',links),
    path('admin/',admin.site.urls),
	#path 更加簡潔好用只不過沒有正則表達式更爲豐富
	#可以參考https://docs.djangoproject.com/en/3.0/ref/urls/#django.urls.include
	
    # path('admin/', custom_site.urls),
]

這段代碼導入了三個沒有寫的 View post_list post_detail links 在編寫他們之前先解釋一下URL
path(’^$’,post_list)
這代表首頁 當訪問首頁的時候就把請求對象交給post_list 處理
完整函數
path(<正則或者字符轉>,,<固定參數context>,<URL名稱>)
完整例子

 re_path('^category/(?P<category_id>\d+)/$',post_list,{'example':'nop'},name='category'_list),
 

(?P<category_id>\d+)
這個group的參數表達式可以把URL這個位置的字符作爲名爲category_id的參數傳遞給post_list函數
第二個參數用來處理請求的函數
第三個參數爲固定參數也就是無論怎樣都會傳遞{‘example’:‘nop’}到post_list中
第四個參數是url的名稱

接下來寫view的代碼
先寫 簡單的搭個架子blog/views.py中的連個函數

post_list 和post_detail 代碼如下
from django.shortcuts import render
from django.http import HttpResponse


# Create your views here.
def post_list(request, category_id=None, tag_id=None):
    content = 'post_list category_id={category_id},tag_id={tag_id}'.format(category_id=category_id, tag_id=tag_id)
    return HttpResponse(content)
def post_detail(request,post_id):
    return HttpResponse('detail')

在寫config/views.py中的links代碼如下

from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def links(request):
    return HttpResponse("links")

這三個代碼只是簡單的返回了一些字符到頁面中
可以執行python3 manage.py runserver
測試一下體會一下正則表達式和傳參的過程
http://127.0.0.1:8000/links/
http://127.0.0.1:8000/post/2.html
http://127.0.0.1:8000
http://127.0.0.1:8000/category/2
http://127.0.0.1:8000/tag/2
接下來繼續修改blog/views.py
修改代碼如下

from django.shortcuts import render
from django.http import HttpResponse


# Create your views here.
# def post_list(request, category_id=None, tag_id=None):
#     print('dddddddd',request)
#     content = 'post_list category_id={category_id},tag_id={tag_id}'.format(category_id=category_id, tag_id=tag_id)
#     return HttpResponse(content)
# def post_detail(request,post_id):
#     return HttpResponse('detail')


def post_list(request, category_id=None, tag_id=None):

    return render(request,'blog/list.html',context={'name':'post_list'})
def post_detail(request,post_id=None):
    return render(request,'blog/detail.html',context={'name':'post_detail'})

這裏需要介紹一下render方法

render(request, template_name, context=None, content_type=None, status=None, using=None)

render的參數介紹
request
封裝了HTTP請求的request對象
template_name
模板名稱 可以向前面的代碼那樣帶上路徑
context
字典數據 它會傳遞到模板
content_type
頁面編碼類型,默認值是text/html
status
狀態碼,默認值是200
using
使用哪種模板引擎解析,這可以在setting.py中配置,默認使用django自帶的模板

配置模板
創建模板的兩種方法
一種是每個app內各自創建自己的模板
另外就是統一放到django和app同級的目錄templates中
如圖
在這裏插入圖片描述
在這個templates中創建 blog 並創建detail.html和list.html
list.html代碼如下

<h1>list</h1>
{{ name }}

detail.html代碼如下

<h1>detail</h1>
{{ name }}

http://127.0.0.1:8000/
訪問首頁可以看到
在這裏插入圖片描述
訪問這個http://127.0.0.1:8000/post/1.html網址可以看到
在這裏插入圖片描述
編寫正式view代碼
先完成post_list 和post_detail的邏輯
post_list的邏輯是
使用model從數據庫中批量拿取數據 然後把標題和摘要展示到頁面上
post_detail的邏輯是
同上 只不過是展示一條數據
具體代碼如下

from django.shortcuts import render
from blog.models import Tag
from blog.models import Post

# Create your views here.
# def post_list(request, category_id=None, tag_id=None):
#     print('dddddddd',request)
#     content = 'post_list category_id={category_id},tag_id={tag_id}'.format(category_id=category_id, tag_id=tag_id)
#     return HttpResponse(content)
# def post_detail(request,post_id):
#     return HttpResponse('detail')


def post_list(request, category_id=None, tag_id=None):
    if tag_id:
        try:
            tag=Tag.objects.get(id=tag_id)
        except Tag.DoesNotExist:
            post_list=[]
        else:
            #差標籤狀態正常的數據
            post_list=tag.post_set.filter(status=Post.STATUS_NORMAL)
    else:
        # 查標籤狀態正常的數據
        post_list=Post.objects.filter(status=Post.STATUS_NORMAL)
        if category_id:
            post_list=post_list.filter(category_id=category_id)
    return render(request,'blog/list.html',context={'post_list':post_list})
        

def post_detail(request,post_id=None):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        post=None
        
    return render(request,'blog/detail.html',context={'post':post})

配置模板數據
list.html代碼如下

<ul>
    {% for post in post_list %}
    <li>
        <a href="/post/{{post.id}}.html">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
</ul>

detail.html的代碼如下

{% if post %}
<h1>{{ post.title }}</h1>
 
<div>
    <span>作者:{{ post.owner.username }}</span>
    <span>分類:{{ post.category.name }}</span>
</div>
<hr/>
<p>
{{ post.content }}
</p>
{% endif %}

訪問http://127.0.0.1:8000/post/2.html如圖所示
在這裏插入圖片描述
訪問首頁如下圖http://127.0.0.1:8000/

完整模板信息
blog/views.py代碼如下

from django.shortcuts import render
from blog.models import Tag
from blog.models import Post

# Create your views here.
# def post_list(request, category_id=None, tag_id=None):
#     print('dddddddd',request)
#     content = 'post_list category_id={category_id},tag_id={tag_id}'.format(category_id=category_id, tag_id=tag_id)
#     return HttpResponse(content)
# def post_detail(request,post_id):
#     return HttpResponse('detail')


def post_list(request, category_id=None, tag_id=None):
    category=None
    tag=None
    if tag_id:
        try:
            tag=Tag.objects.get(id=tag_id)
        except Tag.DoesNotExist:
            post_list=[]
        else:
            #差標籤狀態正常的數據
            post_list=tag.post_set.filter(status=Post.STATUS_NORMAL)
    else:
        # 查標籤狀態正常的數據
        post_list=Post.objects.filter(status=Post.STATUS_NORMAL)
        if category_id:
            post_list=post_list.filter(category_id=category_id)
    context={
        "category":category,
        "tag":tag,
        "post_list":post_list,
    }
    return render(request,'blog/list.html',context=context)


def post_detail(request,post_id=None):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        post=None

    return render(request,'blog/detail.html',context={'post':post})

對應的list.html代碼如下

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>博客系統</title>
</head>
<body>
{% if tag %}
    標籤頁:{{ tag.name }}
{% endif %}


{% if category %}
    分類頁:{{ category.name }}
{% endif %}

<ul>
    {% for post in post_list %}
    <li>
        <a href="/post/{{post.id}}.html">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
</ul>
</body>
</html>

detail.html代碼如下

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>博客系統</title>
</head>
<body>

{% if post %}
<h1>{{ post.title }}</h1>

<div>
    <span>作者:{{ post.owner.username }}</span>
    <span>分類:{{ post.category.name }}</span>
</div>
<hr/>
<p>
{{ post.content }}
</p>
{% endif %}

</body>
</html>


重構post_list視圖

由於View部分的代碼於複雜,這麼一點就如此,後期業務多了會有很多煩惱
所以要重構post_list視圖
重構有兩個問題
第一 我們可以把複雜的代碼抽取出來成獨立的函數,
第二 我們將多個url交給一個函數處理不得不使用各種條件語句來處理業務邏輯

我們先解決第一個問題
首先抽取兩個函數分別處理標籤和分類 因爲這兩個數據用於處理Post層所以把他們定義到model層
同時把獲取最新文章數據的操作也放到model層
首先修改blog/models.py 中的Post
代碼如下

from django.contrib.auth.models import User
from django.db import models


# Create your models here.

class Category(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = ((STATUS_NORMAL, "正常"), (STATUS_DELETE, "刪除"),)

    name = models.CharField(max_length=50, verbose_name="名稱")
    status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="狀態")
    is_nav = models.BooleanField(default=False, verbose_name="是否爲導航")
    owner = models.ForeignKey(User, verbose_name="作者",on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")

    class Meta:
        verbose_name = verbose_name_plural = "分類"
# 將顯示對象改爲顯示title
    def __str__(self):
        return self.name

class Tag(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = ((STATUS_NORMAL, "正常"), (STATUS_DELETE, "刪除"),)

    name = models.CharField(max_length=50, verbose_name="名稱")
    status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="狀態")
    owner = models.ForeignKey(User, verbose_name="作者",on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")

    class Meta:
        verbose_name = verbose_name_plural = "標籤"

    # 將顯示對象改爲顯示title
    def __str__(self):
        return self.name


class Post(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_DRAFT = 2
    STATUS_ITEMS = ((STATUS_NORMAL, "正常"), (STATUS_DELETE, "刪除"), (STATUS_DRAFT, "草稿"),)

    title = models.CharField(max_length=255,verbose_name="標題")
    desc = models.CharField(max_length=1024,blank=True,verbose_name="摘要")
    content =models.TextField(verbose_name="正文",help_text="正文必須爲MarkDown格式")
    status = models.PositiveIntegerField(default=STATUS_NORMAL,choices=STATUS_ITEMS,verbose_name="狀態")
    category = models.ForeignKey(Category,verbose_name="分類",on_delete=models.CASCADE)
    tag = models.ManyToManyField(Tag,verbose_name="標籤")
    owner = models.ForeignKey(User, verbose_name="作者",on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")


    class Meta:
            verbose_name = verbose_name_plural="文章"
            ordering=["-id"] #根據id進行降序排列
            # 將顯示對象改爲顯示title

    def __str__(self):
        return self.title
    
    
    @staticmethod
    def get_by_tag(tag_id):
        try:
            tag = Tag.objects.get(id=tag_id)
        except Tag.DoesNotExist:
            tag = None
            post_list=[]
        else:
            post_list=tag.post_set.filter(status=Post.STATUS_NORMAL).select_related('owner','category')

        return post_list,tag

    @staticmethod
    def get_by_category(category_id):
        try:
            category = Category.objects.get(id=category_id)
        except Tag.DoesNotExist:
            category = None
            post_list = []
        else:
            post_list = category.post_set.filter(status=Post.STATUS_NORMAL).select_related('owner', 'category')

        return post_list, category
    @classmethod
    def latest_posts(cls):
        queryset=cls.objects.filter(status=cls.STATUS_NORMAL)
        return queryset

    


接着修改blog/views.py
代碼如下

from django.shortcuts import render
from blog.models import Tag
from blog.models import Post



def post_list(request, category_id=None, tag_id=None):
    category=None
    tag=None
    if tag_id:
        post_list,tag=Post.get_by_tag(tag_id)
    elif category_id:
        post_list, category = Post.get_by_category(category_id)
    else:
        post_list=Post.latest_posts()
        
    context={
        "category":category,
        "tag":tag,
        "post_list":post_list,
    }
    return render(request,'blog/list.html',context=context)


def post_detail(request,post_id=None):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        post=None

    return render(request,'blog/detail.html',context={'post':post})

展示導航信息
我們來獲取分類的代碼 需要注意的是 我們需要一個獨立的函數post_list
和post_detail使用該函數獲取數據
我們既可以考慮在view中編寫也可以考慮在model層編寫 來完成不過數據操作部分建議放到model層
在blog/models.py中編寫代碼
代碼如下

class Category(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = ((STATUS_NORMAL, "正常"), (STATUS_DELETE, "刪除"),)

    name = models.CharField(max_length=50, verbose_name="名稱")
    status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="狀態")
    is_nav = models.BooleanField(default=False, verbose_name="是否爲導航")
    owner = models.ForeignKey(User, verbose_name="作者",on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")

    class Meta:
        verbose_name = verbose_name_plural = "分類"
# 將顯示對象改爲顯示title
    def __str__(self):
        return self.name
    @classmethod
    def get_navs(cls):
        categories=cls.objects.filter(status=cls.STATUS_NORMAL)
        nav_categories=categories.filter(is_nav=True)
        normal_categories=categories.filter(is_nav=False)
        return {'navs':nav_categories,'categories':normal_categories}

這個函數用來獲取所有分類,並且區分是否爲導航
但這種寫法存在一個問題那就是他會產生兩次數據庫請求
爲了減少IO操作
將上面代碼重構如下


class Category(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = ((STATUS_NORMAL, "正常"), (STATUS_DELETE, "刪除"),)

    name = models.CharField(max_length=50, verbose_name="名稱")
    status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="狀態")
    is_nav = models.BooleanField(default=False, verbose_name="是否爲導航")
    owner = models.ForeignKey(User, verbose_name="作者",on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")

    class Meta:
        verbose_name = verbose_name_plural = "分類"
# 將顯示對象改爲顯示title
    def __str__(self):
        return self.name

    @classmethod
    def get_navs(cls):
        categories = cls.objects.filter(status=cls.STATUS_NORMAL)

        nav_categories = []
        normal_categories = []
        for cate in categories:
            if cate.is_nav:
                nav_categories.append(cate)
            else:
                normal_categories.append(cate)
        return {'navs': nav_categories, 'categories': normal_categories}


這樣就可指示查一次數據庫
接着要改寫 post_list和post_detail中context部分代碼,增加上面分類數據
代碼如下

from django.shortcuts import render
from blog.models import Category
from blog.models import Post



def post_list(request, category_id=None, tag_id=None):

    category=None
    tag=None
    if tag_id:
        post_list,tag=Post.get_by_tag(tag_id)
    elif category_id:
        post_list, category = Post.get_by_category(category_id)
    else:
        post_list=Post.latest_posts()

    context={
        "category":category,
        "tag":tag,
        "post_list":post_list,
    }
    context.update(Category.get_navs())

    return render(request,'blog/list.html',context=context)


def post_detail(request,post_id=None):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        post=None
    context={"post":post}
    context.update(Category.get_navs())
    return render(request,'blog/detail.html',context={'post':post})

現在將分類數據添加到網頁上
list.html中代碼如下

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>博客系統</title>
</head>
<body>

<div>頂部分類
    {% for cate in navs %}
        <a href="/category/{{ cate.id }}/">{{cate.name }}</a>
    {% endfor %}
    
</div>
<hr/>


{% if tag %}
    標籤頁:{{ tag.name }}
{% endif %}


{% if category %}
    分類頁:{{ category.name }}
{% endif %}

<ul>
    {% for post in post_list %}
    <li>
        <a href="/post/{{post.id}}.html">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
</ul>



<hr/>

<div>頂部分類
    {% for cate in navs %}
        <a href="/category/{{ cate.id }}/">{{cate.name }}</a>
    {% endfor %}
    
</div>
</body>
</html>

訪問http://127.0.0.1:8000/admin/blog/category/2/change/後在這裏勾選是否爲導航
佈局規劃,每個頁面都會有側邊欄數據根據,因此來需要增加側邊欄數據,同上面一樣
我們需要新增加一個函數來獲取側邊欄數據,這樣在post_list和post_detail都能使用
直接在config/models.py中的SideBar類中添加get_all
代碼如下

@classmethod
def get_all(cls):
    return cls.objects.filter(status=cls.STATUS_SHOW)

接着修改post_list 和 post_detail 中context 部分的代碼如下

context={
        "category":category,
        "tag":tag,
        "post_list":post_list,
        'sidebars':SideBar.get_all(),
    }

post_detail

context={"post":post,"sidebars":SideBar.get_all()}

接着在list.html和detail.html底部(之上)添加如下代碼

<div>側邊欄展示
    {% for sidebar in sidebars %}
        <h4>{{ sidebars.title }}</h4>
        {{ sidebar.content }}
    {% endfor %}
</div>

訪問http://127.0.0.1:8000/admin/config/sidebar/添加側邊欄查看效果

封裝側邊欄邏輯
這裏主要有兩個問題
一:一個是把複雜邏輯邏輯封裝起來,在模板中只需要使用sidebar.content即可

二:另一個是調整post模型,以滿足我們獲取最熱文章的邏輯
調整模型
我們需要給Post增加兩個字段
分別是pv和uv,
他們用來同級每篇文章的訪問量,同時,也需要把最新最熱的文章包裝到Post的方法上
修改Post模型定義添加字段和方法
代碼如下

#省略其他代碼
pv=models.PositiveIntegerField(default=1)
uv=models.PositiveIntegerField(default=1)
#省略其他代碼
@classmethod
def hot_posts(cls):
    return cls.objects.filter(status=cls.STATUS_NORMAL).order_by('-pv')

執行下面代碼在終端

python3 manage.py makemigrations
python3 manage.py migrate

封裝好SideBar
根據需要展示的類型,在model層直接對數據做渲染,最終返回渲染好的數據,因爲有幾種類型,不同類型的數據,展示不一樣,所以要處理不同的數據源
在SideBar模型中增加一個方法,同時修改之前SIDE_TYPE中用到的數字,通過變量替代
避免代碼中出現MagicNumber的問題代碼如下
config/models.py

from django.template.loader import render_to_string
#省略其他代碼
class SideBar(models.Model):
    DISPLAY_HTML = 1
    DISPLAY_LATEST = 2
    DISPLAY_HOT = 3
    DISPLAY_COMMENT = 4

    STATUS_SHOW = 1
    STATUS_HIDE = 0
    STATUS_ITEMS = ((STATUS_SHOW, "展示"), (STATUS_HIDE, "隱藏"),)
    SIDE_TYPE = ((1, "HTML"), (2, "最新文章"), (3, "最熱文章"), (4, "最近評論"))
    title = models.CharField(max_length=50, verbose_name="標題")
    display_type = models.PositiveIntegerField(default=1, choices=SIDE_TYPE, verbose_name="內容",
                                               help_text="如果設置的不是HTML類型,可爲空")
    status = models.PositiveIntegerField(default=STATUS_SHOW, choices=STATUS_ITEMS, verbose_name="狀態")
    content = models.CharField(max_length=500, blank=True, verbose_name="內容", help_text="如果設置的不是html可以爲空")
    owner = models.ForeignKey(User, verbose_name="作者", on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")

    class Meta:
        verbose_name = verbose_name_plural = "側邊欄"

    # 將顯示對象改爲顯示title
    def __str__(self):
        return self.title

    @classmethod
    def get_all(cls):
        return cls.objects.filter(status=cls.STATUS_SHOW)
    @property
    def content_html(self):
        """直接渲染模板"""
        from blog.models import Post
        from comment.models import Comment
        result=''
        if self.display_type==self.DISPLAY_HTML:
            result=self.content
        elif self.display_type==self.DISPLAY_LATEST:
            context={
                "posts":Post.latest_posts()
                
            }
            result=render_to_string('config/blocks/sidebar_posts.html',context)
        elif self.display_type==self.DISPLAY_HOT:
            context = {
                "posts": Post.hot_posts()

            }
            result = render_to_string('config/blocks/sidebar_posts.html',context)
        elif self.display_type==self.DISPLAY_COMMENT:
            context = {
                "comments":Comment.objects.filter(status=Comment.STATUS_NORMAL)

            }
            result = render_to_string('config/blocks/sidebar_comments.html',context)
        return result

接下來完成sidebar_posts.html和sidebar_comments.html代碼
這兩個建立在
templates/config/blocks/目錄下
代碼如下

sidebar_posts.html
代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客系統</title>
</head>
<body>
<ul>
    {% for post in posts  %}
    <li>
        <a href="/post/{{ post.id}}.html">{{ post.title }}</a>
    </li>
    {% endfor %}
</ul>
</body>
</html>

sidebar_comments.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客系統</title>
</head>
<body>
<ul>
    {% for comment in comments  %}
    <li>
        <a href="/post/{{ comment.target_id }}.html">{{ comment.target.title }}</a>|
    {{ comment.nickname }}:{{ comment.content }}
    </li>
    {% endfor %}
</ul>
</body>
</html>

然後將list.html和detail.html代碼中sidebar.content改爲sidebar.content_html

代碼如下

<div>側邊欄展示
    {% for sidebar in sidebars %}
        <h4>{{ sidebars.title }}</h4>
        {{ sidebar.content_html }}
    {% endfor %}
</div>

抽取基礎模板
在list.html目錄下創建base.html 然後把通用代碼從list.html 中剪切黏貼過去
使用繼承方式提取出基礎模板
代碼如下

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}首頁{% endblock %}博客系統</title>
</head>
<body>

<div>頂部分類
    {% for cate in navs %}
        <a href="/category/{{ cate.id }}/">{{cate.name }}</a>
    {% endfor %}

</div>
<hr/>

{% block main %}
{% endblock %}

<hr/>

<div>底部分類
    {% for cate in navs %}
        <a href="/category/{{ cate.id }}/">{{cate.name }}</a>
    {% endfor %}

</div>
<div>側邊欄展示
    {% for sidebar in sidebars %}
        <h4>{{ sidebars.title }}</h4>
        {{ sidebar.content_html }}
    {% endfor %}
</div>
</body>
</html>


接下來list.html 和detail.html中只需要進行繼承即可
list.html

{% extends "./base.html" %}

{% block title %}

{% if tag %}
    標籤頁:{{ tag.name }}
{% endif %}


{% if category %}
    分類頁:{{ category.name }}
{% endif %}
{% endblock %}

{% block main %}
<ul>
    {% for post in post_list %}
    <li>
        <a href="/post/{{post.id}}.html">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
</ul>

{% endblock %}




detatil.html
代碼如下

{% extends "./base.html" %}

{% block title %}
{{ post.title }}
{% endblock %}

{% block main %}
{% if post %}
<h1>{{ post.title }}</h1>

<div>
    <span>作者:{{ post.owner.username }}</span>
    <span>分類:{{ post.category.name }}</span>
</div>
<hr/>
<p>
{{ post.content }}
</p>
{% endif %}
{% endblock %}

返向解析URL
這個方法以來定義URL的時候寫上name參數
所以要修改之前定義的url代碼如下

from django.contrib import admin
from django.urls import path,re_path
from blog.views import post_list
from blog.views import post_detail
from config.views import links

#

# from testblog.custom_site import custom_site
urlpatterns = [

    re_path('^$',post_list,name="index"),
    re_path('^category/(?P<category_id>\d+)/$',post_list,name="category_list"),
    re_path('^tag/(?P<tag_id>\d+)/$',post_list,name="tag_list"),
    re_path('^post/(?P<post_id>\d+).html$',post_detail,name='post_detail'),
    re_path('^links/$',links,name="links"),
    path('admin/',admin.site.urls),

    # path('admin/', custom_site.urls),
]

接下來list.html

 <a href="{% url "post_detail" post.id %}">{{ post.title }}</a>

其他html類似

類視圖
類視圖和普通視圖沒有區別,只是適用的場景不同而已
Django提供了下面幾個class_base view
view
基礎的view 它實現了基於HTTP方法的分發邏輯,比如get請求會調用get方法,POST請求會調用Post方法 但是自己沒有具體的實現get和post方法
TemplateView
繼承自View,可以直接用來返回指定的模板,它實現了get方法,可以傳變量到模板進行數據展示
DetailView
繼承自view,實現了get方法,可以通過綁定某一個模板,用來獲取單個實例的數據
ListView
繼承自view 實現了get方法 可以通過該綁定模板來批量的獲取數據上面的簡單解釋看起來比較抽象,下面是通過代碼實際體驗一番
function view這麼寫

from django.http import HttpResponse
def my_view(request):
	if request.method == "GET":
		#<view logic>
		return HttpResponse("result")

class_base View 這麼寫

from django.http import HttpResponse
from django.views import View
class MyView(View):
	def get(self,request):
		#<view logic>
		return HttpResponse('result')

這樣就可以直接增加方法而不用去判斷是post還是get請求了
看怎麼使用
View的用法

#urls.py
from django.urls import re_path
from myapp.views import MyView
urlpatterns=[re_path("about/",MyView.as_view()),]

as_view的邏輯
返回一個閉包(就是在一個函數中返回一個嵌套函數,但是嵌套函數中使用了,嵌套函數自己外部的變量,按常理來說,一個外部函數return 後,局部變量會消失但是返回的嵌套函數調用了它,所以再內部函數沒調用之前他是不會消失的,這就是閉包,就是將要消失的變量留住,這裏面說的被保存的是request)
嵌套的view是保留request的內嵌方法,
內部的基本操作就是給View賦值 一些參數包括 request args kwargs
根據HTTP方法分發請求會調用類方法中的get還是post等

請求到達之後的邏輯
流程如下
假設是get
1,分發
2,調用get
get請中會調用get_querset方法,拿到數據源
接着調用get_context_data方法 ,拿到要渲染到模板中的數據
在get_context_data中,首先會調用get_paginate_by拿到每頁數據
接着調用get_context_object_name拿到要渲染到模板中的這個queryset名稱
調用paginate_queryset 進行分頁處理
最後拿到的數據轉爲dict並返回
調用render_to_response渲染數據到頁面中
在 render_to_response中調用get_template_names拿到模板名
而後把request,context,template等傳遞到模板中

as_view代碼如下

@classonlymethod
def as_view(cls, **initkwargs):
   """Main entry point for a request-response process."""
   for key in initkwargs:
       if key in cls.http_method_names:
           raise TypeError("You tried to pass in the %s method name as a "
                           "keyword argument to %s(). Don't do that."
                           % (key, cls.__name__))
       if not hasattr(cls, key):
           raise TypeError("%s() received an invalid keyword %r. as_view "
                           "only accepts arguments that are already "
                           "attributes of the class." % (cls.__name__, key))

   def view(request, *args, **kwargs):
       self = cls(**initkwargs)
       if hasattr(self, 'get') and not hasattr(self, 'head'):
           self.head = self.get
       self.setup(request, *args, **kwargs)
       if not hasattr(self, 'request'):
           raise AttributeError(
               "%s instance has no 'request' attribute. Did you override "
               "setup() and forget to call super()?" % cls.__name__
           )
       return self.dispatch(request, *args, **kwargs)
   view.view_class = cls
   view.view_initkwargs = initkwargs

   # take name and docstring from class
   update_wrapper(view, cls, updated=())

   # and possible attributes set by decorators
   # like csrf_exempt from dispatch
   update_wrapper(view, cls.dispatch, assigned=())
   return view

TemplateView的用法

#urls.py
from django.urls import re_path
from myapp.views import TemplateView 
urlpatterns=[re_path("about/",TemplateView.as_view(template_name="about.html")),]

還可以通過繼承TemplateView 然後重寫get_context_data 方法 將要展示的數據傳遞到模板

DetailView
通過我們的項目來實際體驗一下
定義一個PostDetailView 來替換blog/view.py 中定義的post_detail方法
代碼如下

from django.views.generic import DetailView
class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/detail.html'

blog/detail.html的代碼改爲

{% if post %}
<h1>{{ post.title }}</h1>

<div>
    <span>作者:{{ post.owner.username }}</span>
    <span>分類:{{ post.category.name }}</span>
</div>
<hr/>
<p>
{{ post.content }}
</p>
{% endif %}

urls.py的配置如下

re_path('^post/(?P<pk>\d+).html$',PostDetailView.as_view(),name='post_detail'),

PostDetailView的屬性和接口
model屬性
指定當前view要使用的Model
queryset屬性
和model一樣 二選一 設定基礎的數據集,model 的設定沒有過濾的功能 可以通過該queryset=Post.objects.filter(status=Post.STAUTUS_NORMAl)進行過濾
template_name屬性
模板名稱
get_queryset接口
同前面介紹過的所有get_queryset方法一樣,用來獲取數據,如果設定了queryset,則會直接返回queryset
get_object接口
根據url參數 從queryset上獲取到對應的實例
get_context_data 接口
獲取渲染到模板中的所有上下文,如果有新增數據需要傳遞到模板中可以重寫該方法來完成
ListView的使用
在blog/views.py中增加代碼

from django.views.generic import ListView
class PostListView(ListView):
    queryset = Post.latest_posts()
    paginate_by = 1
    context_object_name = "post_list"
    template_name = "blog/list.html"

paginate_by = 1 每頁數量
list.html代碼如下(替換block main即可)

{% block main %}
<ul>
    {% for post in post_list %}
    <li>
{#        <a href="/post/{{post.id}}.html">{{ post.title }}</a>#}
        <a href="{% url "post_detail" post.id %}">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
    {% if page_obj %}
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}">上一頁</a>
        {% endif %}
        Page {{ page_obj.number }} of {{ page_obj.num_pages }}
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">下一頁</a>
        {% endif %}
    {% endif %}

</ul>

{% endblock %}

最後大改造
首先是首頁代碼

#省略其他代碼
class IndexView(ListView):
    queryset = Post.latest_posts()
    paginate_by = 2
    context_object_name = "post_list"
    template_name = "blog/list.html"
#省略其他代碼

接下來是分類 側邊欄等
代碼如下

#省略其他代碼
class CommonViewMixin:
    def get_context_data(self,**kwargs):
        context=super().get_context_data(**kwargs)
        context.update({"sidebars":SideBar.get_all(),})
        context.update(Category.get_navs())
        return context

class IndexView(CommonViewMixin,ListView):
    queryset = Post.latest_posts()
    paginate_by = 2
    context_object_name = "post_list"
    template_name = "blog/list.html"


#省略其他代碼

CommonViewMixin這個類只不過是將所有的之前寫的方法都集合在一起而已
接來寫分類列表頁和標籤列表頁的處理邏輯
首先分析一下相對於首頁兩個頁面的差別有哪些
主要有兩個
1,queryset中的互數據要根據當前選擇的分類或者標籤進行過濾
2,渲染到模板中的數據需要加上當前選擇的分類的數據
需要重寫get_context_data另外一個是get_queryset方法,用來獲取Model或Queryset
我們繼續在blog/views.py中添加代碼

from django.shortcuts import get_object_or_404


class CategoryView(IndexView):
    def get_context_data(self,**kwargs):
        context=super().get_context_data(**kwargs)
        category_id = self.kwargs.get("category_id")
        category= get_object_or_404(Category,pk=category_id)
        context.update({"category":category})
        return context
    def get_queryset(self):
        """重寫queryset,根據標籤過濾"""
        queryset = super().get_queryset()
        category_id = self.kwargs.get("category_id")
        return queryset.filter(category_id=category_id)


class TagView(IndexView):
    def get_context_data(self,**kwargs):
        context=super().get_context_data(**kwargs)
        tag_id = self.kwargs.get("tag_id")
        tag= get_object_or_404(Tag,pk=tag_id)
        context.update({"tag":tag})
        return context
    def get_queryset(self):
        """重寫queryset,根據標籤過濾"""
        queryset = super().get_queryset()
        tag_id = self.kwargs.get("tag_id")
        return queryset.filter(tag_id=tag_id)

get_object_or_404 是一個快捷方式 用來獲取一個對象的實例 如果獲取到了返回實例對象,如果不存在,直接拋出404錯誤
在tag_id = self.kwargs.get(“tag_id”)裏面self.kwargs中的數據其實是從我們的URL定義中拿到的,你可以對比一下

繼續在blog/views.py添加詳情頁代碼

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/detail.html'
    context_object_name = "post"
    pk_url_kwarg = "post_id"

接着寫url
代碼如下

from django.contrib import admin
from django.urls import path,re_path

from blog.views import PostDetailView
from blog.views import IndexView
from blog.views import CategoryView
from blog.views import TagView


from config.views import links

#

# from testblog.custom_site import custom_site
urlpatterns = [

    re_path('^$',IndexView.as_view(),name="index"),
    re_path('^category/(?P<category_id>\d+)/$',CategoryView.as_view(),name="category_list"),
    re_path('^tag/(?P<tag_id>\d+)/$',TagView.as_view(),name="tag_list"),

    re_path('^post/(?P<post_id>\d+).html$',PostDetailView.as_view(),name='post_detail'),

    re_path('^links/$',links,name="links"),
    path('admin/',admin.site.urls),

    # path('admin/', custom_site.urls),
]

接着完成list.html

{% extends "./base.html" %}

{% block title %}

{% if tag %}
    標籤頁:{{ tag.name }}
{% elif category %}
    分類頁:{{ category.name }}
{% else  %}
    首頁
{% endif %}
{% endblock %}

{% block main %}
<ul>
    {% for post in post_list %}
    <li>
{#        <a href="/post/{{post.id}}.html">{{ post.title }}</a>#}
        <a href="{% url "post_detail" post.id %}">{{ post.title }}</a>
        <div>
            <span>作者:{{ post.owner.username }}</span>
            <span>分類:{{ post.category.name }}</span>
        </div>
        <p>{{ post.desc }}</p>
    </li>
    {% endfor %}
    {% if page_obj %}
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}">上一頁</a>
        {% endif %}
        Page {{ page_obj.number }} of {{ page_obj.num_pages }}
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">下一頁</a>
        {% endif %}
    {% endif %}

</ul>

{% endblock %}




Django的view是如何處理請求的

當Django接受到一個請求之後 將HTTP請求轉化爲request對象

請求經過所有的middleware的process_request方法
然後解析URL
接着根據URL和View的映射,把request 對象傳遞到View中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章