設計我的站點頁面(跨表查詢、分組查詢)

1、我的站點頁面的文章查詢

一、路由配置我的站點url

urls.py:css

urlpatterns = [
    ...
    # 我的站點url
    # 正則規則:\w:匹配數字、字母、下劃線   \W:匹配除數字、字母、下劃線之外的任意字符
    # '(?P<name>...)' 分組匹配  {"username": alex}
    re_path('^(?P<username>\w+)$', views.home_site),
]

二、構建home_site視圖函數及文章查詢

def home_site(request, username):
    """
    我的站點視圖函數
    :param request:
    :param username:  yuan / alex
    :return:
    """
    print("username", username)
    # 去數據庫查找該用戶是否存在
    # ret = UserInfo.objects.filter(username= username).exists()

    # 拿到當前用戶對象
    user = UserInfo.objects.filter(username= username).first()

    # 判斷用戶是否存在
    if not user:
        # 用戶不存在返回404頁面
        return render(request, "not_found.html")

    # 查詢當前站點對象
    blog = user.blog

    # 查看當前用戶或當前站點所對應的全部文章
    # 方法1:基於對象查詢
    # article_list = user.article_set.all()
    # 方法2:基於雙下劃線查詢
    article_list = models.Article.objects.filter(user=user)

    return render(request, "home_site.html")

注意:html

(1)訪問我的網站須要去數據庫查看該用戶是否存在,若是用戶不存在返回404頁面。前端

# 去數據庫查找該用戶是否存在
ret = UserInfo.objects.filter(username= username).exists()

# 判斷用戶是否存在
if not ret:
    # 用戶不存在返回404頁面
    return render(request, "not_found.html")

(2)查看當前站點對象vue

 

(3)查看當前用戶或當前站點所對應的全部文章python

# 查看當前用戶或當前站點所對應的全部文章
# 方法1:基於對象查詢
# article_list = user.article_set.all()
# 方法2:基於雙下劃線查詢
article_list = models.Article.objects.filter(user=user)

2、我的站點頁面標籤和分類查詢

# 每個後的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計字段)).values()
# 查詢每個分類名稱及對應的文章數(最簡單的分組查詢)
ret = models.Category.objects.values("pk").annotate(c=Count("article__title")).values("title", "c")
print(ret)   # <QuerySet [{'title': 'yuan的雞湯', 'c': 1}, {'title': 'Dubbo', 'c': 1}, {'title': '前端', 'c': 1}]>

# 查詢當前站點的每個分類名稱以及對應的文章數
cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list("title", "c")
print(cate_list)  # <QuerySet [{'title': 'yuan的雞湯', 'c': 1}, {'title': 'Dubbo', 'c': 1}]>

# 查詢當前站點的每個標籤名稱及對應的文章數
tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c")
print(tag_list)

3、日期歸檔查詢方式一

  這是一個單表分組查詢。按照日期進行分組。可是create_time字段是包含時分秒的,所以不能使用它分組。mysql

# 查詢當前站點每個年月的名稱及對應的文章數
date_list = models.Article.objects.filter(user=user).extra(select={"y_m_date": "date_format(create_time, '%%Y-%%m')"}).values("y_m_date").annotate(c=Count("nid")).values("y_m_date", "c")
print(date_list)   # <QuerySet [{'y_m_date': '2018-08', 'c': 2}]>

一、date_format函數

  注意date\time\datetime三種事件類型的區別jquery

  建立表並插入當前時間:程序員

create table t_mul_new(d date,t time,dt datetime);

insert into t_mul_new values(now(),now(),now());

  利用date_form函數用於以不一樣的格式顯示日期/時間數據sql

mysql> select * from t_mul_new;
+------------+----------+---------------------+
| d          | t        | dt                  |
+------------+----------+---------------------+
| 2018-08-02 | 14:58:57 | 2018-08-02 14:58:57 |
+------------+----------+---------------------+
1 row in set (0.00 sec)

mysql> select date_format(dt,"%Y/%m/%d") from t_mul_new;
+----------------------------+
| date_format(dt,"%Y/%m/%d") |
+----------------------------+
| 2018/08/02                 |
+----------------------------+
1 row in set (0.00 sec)

  語法和參數以下所示:數據庫

語法:
DATE_FORMAT(date,format)
    date 參數是合法的日期。format 規定日期/時間的輸出格式。

經常使用格式:
%a	縮寫星期名
%b	縮寫月名
%c	月,數值
%D	帶有英文前綴的月中的天
%d	月的天,數值(00-31)
%e	月的天,數值(0-31)
%H	小時 (00-23)
%h	小時 (01-12)
%I	小時 (01-12)
%i	分鐘,數值(00-59)
%j	年的天 (001-366)
%M	月名
%m	月,數值(00-12)
%p	AM 或 PM
%r	時間,12-小時(hh:mm:ss AM 或 PM)
%S	秒(00-59)
%s	秒(00-59)
%T	時間, 24-小時 (hh:mm:ss)

二、extra函數

語法:

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

  有些狀況下,Django的查詢語法難以簡單的表達複雜的 WHERE 子句,對於這種狀況, Django 提供了 extra() QuerySet修改機制 — 它能在 QuerySet生成的SQL從句中注入新子句。

  extra能夠指定一個或多個參數,例如 select, where or tables. 這些參數都不是必須的,可是你至少要使用一個!要注意這些額外的方式對不一樣的數據庫引擎可能存在移植性問題.(由於你在顯式的書寫SQL語句),除非萬不得已,儘可能避免這樣作。

select參數:

  The select 參數可讓你在 SELECT從句中添加其餘字段信息,它應該是一個字典,存放着屬性名到 SQL 從句的映射。

queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

  結果集中每一個 Entry 對象都有一個額外的屬性is_recent, 它是一個布爾值,表示 Article對象的create_time 是否晚於2017-09-05.

  若是要進一步過濾結果,拿到匹配元素的名稱和是否最近的結果:

ret = models.Article.objects.extra(select={"is_recent": "create_time > '2017-09-05'"}).values("title", "is_recent")
print(ret) # <QuerySet [{'is_recent': 1, 'title': '追求優秀,纔是合格的程序員'}, {'is_recent': 1, 'title': 'Dubbo負載均衡與集羣容錯機制'}, {'is_recent': 1, 'title': 'vue相關'}]>

利用extra查看對應全部文章,並獲得年-月-日形式發佈時間:

ret = models.Article.objects.extra(select={"y_m_d_date": "date_format(create_time, '%%Y-%%m-%%d')"}).values("title", "y_m_d_date")
print(ret)  # <QuerySet [{'y_m_d_date': '2018-08-01', 'title': '追求優秀,纔是合格的程序員'}, {'y_m_d_date': '2018-08-01', 'title': 'Dubbo負載均衡與集羣容錯機制'}, {'y_m_d_date': '2018-08-01', 'title': 'vue相關'}]>

三、再加入filter(user=user)過濾出當前站點用戶,並使用.annotate(c=Count("nid"))分組

date_list = models.Article.objects.filter(user= user).extra(select={"y_m_date": "date_format(create_time, '%%Y-%%m')"}).values("y_m_date").annotate(c=Count("nid")).values("y_m_date", "c")
print(date_list)   # <QuerySet [{'y_m_date': '2018-08', 'c': 2}]>

  如此就獲得了當前站點每個年月的文章數。可是這個數據傳入模板中,調用不了,由於它並非字典。

# 改用values_list,獲得字典
date_list = models.Article.objects.filter(user=user).extra(
    select={"y_m_date": "date_format(create_time, '%%Y-%%m')"}).values("y_m_date").annotate(c=Count("nid")).values_list(
    "y_m_date", "c")
print(date_list)  # <QuerySet [('2018-08', 2)]>

4、日期歸檔查詢方式二

  應用django提供的接口來快速處理日期歸檔查詢,這裏引入的是TruncMonth,比較經常使用的還有TruncDay等。

from django.db.models.functions import TruncMonth
ret = models.Article.objects.filter(user=user).annotate(month=TruncMonth("create_time")).values("month").annotate(c=Count("nid")).values_list("month", "c")
print("ret---->", ret)   # ret----> <QuerySet [(datetime.datetime(2018, 8, 1, 0, 0), 2)]>

一、TruncMonth模塊及使用方式

from django.db.models.functions import TruncMonth
       
Sales.objects
	.annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
	.values('month')                          # Group By month
	.annotate(c=Count('id'))                  # Select the count of the grouping
	.values('month', 'c')                     # (might be redundant, haven't tested) select month and count    

注意:

(1).annotate(month=TruncMonth('timestamp'))並無作分組,是把日期截斷到年月爲止,並賦值給month的字段;

(2).values('month') 是利用剛剛截斷出來的month字段來進行分組;

(3).annotate(c=Count('id')) 統計id的數量;

(4).values('month', 'c') 顯示month和統計出來的c值。

二、django中的時區設置TIME_ZONE,USE_TZ

  運行代碼發現報錯:Database returned an invalid datetime value. Are time zone definitions for your database installed?

  檢查發現是settings.py中改寫過TIME_ZONE = "Asia/Shanghai"後,須要將USE_TZ=True改成USE_TZ=False。 

  Django若是開啓了Time Zone功能,則全部的存儲和內部處理,甚至包括直接print顯示全都是UTC的。只有經過模板進行表單輸入/渲染輸出的時候,纔會執行UTC本地時間的轉換。建議後臺處理時間的時候,最好徹底使用UTC,不要考慮本地時間的存在。而顯示時間的時候,也避免手動轉換,儘可能使用Django模板系統代勞。

  啓用 USE_TZ = True 後,處理時間方面,有兩條 「黃金法則」:

一、保證存儲到數據庫中的是 UTC 時間;
二、在函數之間傳遞時間參數時,確保時間已經轉換成 UTC 時間;

# 一般獲取當前時間用的是:
import datetime
now = datetime.datetime.now()

# 啓用 USE_TZ = True 後,須要寫成:
import datetime 
from django.utils.timezone import utc
utcnow = datetime.datetime.utcnow().replace(tzinfo=utc)

  除非應用支持用戶設置本身所在的時區,一般咱們不須要關心模板的時區問題。模板在展現時間的時候,會使用 settings.TIME_ZONE 中的設置自動把 UTC 時間轉成 settings.TIME_ZONE 所在時區的時間渲染。

TIME_ZONE = 'Asia/Shanghai'  

5、我的站點頁面渲染布局

一、將數據傳入模板home_site.html中:

def home_site(request, username):
    ...
    return render(request, "home_site.html", {"blog": blog, "article_list": article_list, "cate_list": cate_list, "date_list": date_list})

二、home_site.html頁面佈局及數據獲取代碼以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 引入 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css">
    <!-- jQuery (Bootstrap 的全部 JavaScript 插件都依賴 jQuery,因此必須放在前邊) -->
    <script src="/static/js/jquery-3.3.1.js"></script>
    <!-- 引入 Bootstrap 核心 JavaScript 文件 -->
    <script src="/static/blog/bootstrap-3.3.7/js/bootstrap.js"></script> <!--依賴jquery-->
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .header {
            width: 100%;
            height: 60px;
            background-color: #369;
        }

        .header .title {
            font-size: 18px; /* 字體大小 */
            font-weight: 100; /* 字體粗細 */
            line-height: 60px; /* 行高與頁頭一致,完成居中 */
            color: white;
            margin-left: 15px;
            margin-top: -10px;
        }

        .backend {
            float: right; /* 浮動到右邊 */
            color: white;
            text-decoration: none; /* 去除下劃線 */
            font-size: 16px;
            margin-right: 12px;
            margin-top: 10px;
        }
        .pub_info {
            margin-top: 10px;
            color: darkgray;
        }
    </style>
</head>
<body>

<div class="header">
    <div class="content">
        <!--站點標題-->
        <p class="title">
            <span>{{ blog.title }}</span>
            <a href="" class="backend">管理</a>
        </p>
    </div>
</div>

<div class="container">
    <div class="row">
        <div class="col-md-3">
            <!--添加bootstrap面板-->
            <div class="panel panel-warning">
                <div class="panel-heading">個人標籤</div>
                <div class="panel-body">
                    {% for tag in tag_list %}
                        <p>{{ tag.0 }}({{ tag.1 }})</p>
                    {% endfor %}
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">隨筆分類</div>
                <div class="panel-body">
                    {% for cate in cate_list %}
                        <p>{{ cate.0 }}({{ cate.1 }})</p>
                    {% endfor %}
                </div>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">隨筆歸檔</div>
                <div class="panel-body">
                    {% for date in date_list %}
                        <p>{{ date.0 }}({{ date.1 }})</p>
                    {% endfor %}
                </div>
            </div>
        </div>
        <div class="col-md-9">
            <div class="article_list">
                <div class="article_list">
                    {% for article in article_list %}
                        <div class="article-item clearfix">
                            <h5><a href="">{{ article.title }}</a></h5>
                            <div class="article-desc">
                                {# 文章摘要 #}
                                {{ article.desc }}
                            </div>
                            <!--文章下方詳細信息-->
                            <div class="small pub_info pull-right">
                                {# 文章發佈時間 #}
                                <span>發佈於  {{ article.create_time|date:"Y-m-d H:i" }}</span>   
                                {# 評論數 #}
                                <span class="glyphicon glyphicon-comment"></span> 評論({{ article.comment_count }})  
                                {# 點贊數 #}
                                <span class="glyphicon glyphicon-thumbs-up"></span> 點贊({{ article.up_count }})
                            </div>
                        </div>
                        <hr>
                    {% endfor %}
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>

注意:

(1)這裏文章列表能夠複用首頁的文章列表代碼來顯示,作去除頭像等微調便可。

(2)div標籤添加class="clearfix",解決標籤浮動問題。

  經過爲父元素添加 .clearfix 類能夠很容易地清除浮動(float)。這裏所使用的是 Nicolas Gallagher 創造的 micro clearfix 方式。此類還能夠做爲 mixin 使用。

<div class="article-item clearfix">
    <h5><a href="">{{ article.title }}</a></h5>
    <div class="article-desc">
        {# 文章摘要 #}
        {{ article.desc }}
    </div>
    <!--文章下方詳細信息-->
    <div class="small pub_info pull-right">
        {# 文章發佈時間 #}
        <span>發佈於  {{ article.create_time|date:"Y-m-d H:i" }}</span>   
        {# 評論數 #}
        <span class="glyphicon glyphicon-comment"></span> 評論({{ article.comment_count }})  
        {# 點贊數 #}
        <span class="glyphicon glyphicon-thumbs-up"></span> 點贊({{ article.up_count }})
    </div>
</div>

三、顯示效果:

  

6、我的站點頁面跳轉過濾功能

一、頁面跳轉路由設計

  在點擊標籤、隨筆分類、隨筆日期歸檔的時候,顯示對應的過濾信息。須要利用到路由跳轉。

  仿照博客園頁面地址:https://www.cnblogs.com/xiugeng/category/1156842.html

urlpatterns = [
    ...
    # 我的站點url
    re_path('^(?P<username>\w+)$', views.home_site),

    # 我的站點跳轉   有名分組
    re_path('^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site),
]

  當用戶訪問我的站點時,傳給視圖函數home_site兩個實參:homesite(request, username='yuan');

  當用戶訪問我的站點跳轉時,傳遞四個參數:homesite(request, username='yuan', condition='tag', param='運維');

二、home_site視圖函數改寫

def home_site(request, username, **kwargs):
    """
    我的站點視圖函數
    :param request:
    :param username:  yuan / alex
    :return:
    """
    # 拿到當前用戶對象
    user = UserInfo.objects.filter(username=username).first()

    # 判斷用戶是否存在
    if not user:
        # 用戶不存在返回404頁面
        return render(request, "not_found.html")

    # 查詢當前站點對象
    blog = user.blog

    if kwargs:
        condition = kwargs.get("condition")
        param = kwargs.get("param")  # 2012-12

        if condition=="category":
            article_list = models.Article.objects.filter(user=user).filter(category__title=param)
        elif condition=="tag":
            article_list = models.Article.objects.filter(user=user).filter(tags__title=param)
        else:
            year, month = param.split("-")
            article_list = models.Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month)
    else:
        article_list = models.Article.objects.filter(user=user)

    # 查詢每個分類名稱及對應的文章數(最簡單的分組查詢)
    ret = models.Category.objects.values("pk").annotate(c=Count("article__title")).values("title", "c")
    print(ret)  # <QuerySet [{'title': 'yuan的雞湯', 'c': 1}, {'title': 'Dubbo', 'c': 1}, {'title': '前端', 'c': 1}]>

    # 查詢當前站點的每個分類名稱以及對應的文章數
    cate_list = models.Category.objects.filter(blog=blog).values("pk").annotate(c=Count("article__title")).values_list(
        "title", "c")
    print(cate_list)  # <QuerySet [{'title': 'yuan的雞湯', 'c': 1}, {'title': 'Dubbo', 'c': 1}]>

    # 查詢當前站點的每個標籤名稱及對應的文章數
    tag_list = models.Tag.objects.filter(blog=blog).values("pk").annotate(c=Count("article")).values_list("title", "c")
    print(tag_list)

    # 改用values_list,獲得字典
    date_list = models.Article.objects.filter(user=user).extra(
        select={"y_m_date": "date_format(create_time, '%%Y-%%m')"}).values("y_m_date").annotate(
        c=Count("nid")).values_list(
        "y_m_date", "c")
    print(date_list)  # <QuerySet [('2018-08', 2)]>

    return render(request, "home_site.html",
                  {"blog": blog, "article_list": article_list, "cate_list": cate_list, "tag_list": tag_list,
                   "date_list": date_list})

注意要點:

(1)給home_site()函數添加**kwargs參數。在訪問我的站點跳轉時,能夠接收到其餘參數。

(2)基於kwargs是否存在,判斷是不是我的站點跳轉,若是是的,拿到condition和param的值:

if kwargs:
    condition = kwargs.get("condition")
    param = kwargs.get("param")  # 2012-12

(3)因爲condition的值只多是tag\category\archive這三種,根據這三種狀況拿到對應的文章列表

if kwargs:
    condition = kwargs.get("condition")
    param = kwargs.get("param")  # 2012-12

    if condition=="category":
        article_list = models.Article.objects.filter(user=user).filter(category__title=param)
    elif condition=="tag":
        article_list = models.Article.objects.filter(user=user).filter(tags__title=param)
    else:
        year, month = param.split("-")
        article_list = models.Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month)
else:
    article_list = models.Article.objects.filter(user=user)

  因爲models.Article.objects.filter(user=user)出現了屢次重複,代碼優化以下:

article_list = models.Article.objects.filter(user=user)
if kwargs:
    condition = kwargs.get("condition")
    param = kwargs.get("param")  # 2012-12

    if condition=="category":
        article_list = article_list.filter(category__title=param)
    elif condition=="tag":
        article_list = article_list.filter(tags__title=param)
    else:
        year, month = param.split("-")
        article_list = article_list.filter(create_time__year=year, create_time__month=month)

(4)日期歸檔時,當condition="archive"時,param的值是相似2012-12這樣的格式,須要進行切割處理

year, month = param.split("-") 

 訪問驗證

  

  

  

三、home_site.html模板修改

  須要在views.py的return render中再給模板傳遞一個"username":

def home_site(request, username, **kwargs):
  ...   return render(request, "home_site.html", {"username": username, "blog": blog, "article_list": article_list, "cate_list": cate_list, "tag_list": tag_list, "date_list": date_list})

(1)個人標籤 添加<a>並填寫訪問路徑

<div class="panel panel-warning">
    <div class="panel-heading">個人標籤</div>
    <div class="panel-body">
        {% for tag in tag_list %}
            <p><a href="/{{ username }}/tag/{{ tag.0 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
        {% endfor %}
    </div>
</div>

  注意{{tag.0}}是標籤名稱。點擊驗證以下:

  

(2)隨筆分類  處理方式和tag相似

<div class="panel panel-danger">
    <div class="panel-heading">隨筆分類</div>
    <div class="panel-body">
        {% for cate in cate_list %}
            <p><a href="/{{ username }}/category/{{ cate.0 }}">{{ cate.0 }}({{ cate.1 }})</a></p>
        {% endfor %}
    </div>
</div>

  顯示效果:

  

(3)隨筆歸檔處理以下:

<div class="panel panel-success">
    <div class="panel-heading">隨筆歸檔</div>
    <div class="panel-body">
        {% for date in date_list %}
            <p><a href="/{{ username }}/archive/{{ date.0 }}">{{ date.0 }}({{ date.1 }})</a></p>
        {% endfor %}
    </div>
</div>

  顯示效果以下:

  

相關文章
相關標籤/搜索