所有演示均基于Django2.0
閱讀此篇文章你可以:
需求說明
一張會議記錄表,里邊有一個字段存放會議舉行的地點,例如北京、上海、洛陽等等,需要取舉行會議最多的前20個地點繪制成柱狀圖展示,項目為前后端分離的架構
需求分析
看了需求主要有三個關鍵點:
1.前后端分離:前端只負責頁面渲染,后端提供API負責數據輸出
2.需要繪制成柱狀圖:繪制圖表的第三方插件有很多,我們這里就選擇百度開源的echarts,簡單好用且功能強大
3.取舉行會議最多的前20個地點:了解一點SQL知識的話就知道需要先要對地點字段進行group by,然后order by desc倒序,最后limit取前20
那么在Django中應該如何group by,并在group by之后order by排序,最后limit呢?這里我們介紹django的兩個函數 aggregate 和 annotate
aggregate
aggregate聚合函數,用于對QuerySet整個對象結果的匯總,例如獲取員工總數(COUNT),平均(AVG)年齡,最大(MAX)年齡,最小(MIN)年齡,銷售總額(SUM)等,輸出的結果是一個字典
我們有一個model如下:
class Employee(models.Model): name = models.CharField(max_length=32, verbose_name='姓名') age = models.IntegerField(verbose_name='年齡') salary = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='薪資')
想要獲取員工的工資總額,我們可以這樣寫
>>> from django.db.models import Sum>>> Employee.objects.aggregate(Sum('salary')){'salary__sum': Decimal('5000.00')}想要同時獲取員工的平均年齡、最大年齡和最小年齡,我們可以這樣寫
>>> from django.db.models import Avg, Max, Min>>> Employee.objects.aggregate(Avg('age'), Max('age'), Min('age')){'age__avg': 23.333333333333332, 'age__max': 30, 'age__min': 18}annotate
annotate函數區別于aggregate函數的一個最重要的地方是annotate函數 輸出的結果是一個QuerySet對象 ,這個非常重要,aggregate函數最后輸出的結果是個字典,也就不能再在字典的基礎上進行QuerySet操作了,而annotate函數執行完成后輸出QuerySet對象可以繼續調用Django內置的filter、order_by等函數來完成更加復雜的查詢計算操作
用到annotate函數的邏輯往往比較復雜,Django非常人性化的提供了query方法,方便查看annotate生成的SQL語句幫助我們確定執行過程
以上邊的實際需求為例,model如下:
class EventInfo(models.Model): event_location = models.CharField(max_length=30) class Meta: db_table = "app_event_info"
我們需要先對地點event_location進行group by:
>>> _t = EventInfo.objects.values_list('event_location').annotate(Count('id'))# values_list可以獲取evnet_location的元組列表。# values_list方法加個參數flat=True可以獲取event_location的值列表。group by之后我們就需要order by排序了,如果我們不知道order by的字段,我們可以通過query先查看group by生成的SQL語句
>>> print(_t.query)SELECT "app_event_info"."event_location", COUNT("app_event_info"."id") AS "id__count" FROM "app_event_info" GROUP BY "app_event_info"."event_location" 這個時候可以看到實際上輸出的結果有一個叫 id__count 的字段表示地點的總數,那么我們就可以接著對地點總數進行排序了,因為是要倒敘,需要在字段名 id__count 前邊加上 - 號來表示倒序
>>> _x = _t.order_by('-id__count')>>>>>> print(_x.query)SELECT "app_event_info"."event_location", COUNT("app_event_info"."id") AS "id__count" FROM "app_event_info" GROUP BY "app_event_info"."event_location" ORDER BY "id__count" DESC最后limit取前二十,Django中limit可以直接通過QuerySet結果后加python的數組切片語法來實現,就像[0:20](其中0可以省略)相當于limit 20一樣,[10:20]意思為取第10到第20條數據
>>> _y = _x[:20]>>>>>> print(_y.query)SELECT "app_event_info"."event_location", COUNT("app_event_info"."id") AS "id__count" FROM "app_event_info" GROUP BY "app_event_info"."event_location" ORDER BY "id__count" DESC LIMIT 20上邊的每一步我們都通過query打印了SQL,確定是我們想要的結果了。需求分析清楚,所有的關鍵點我們也都知道怎么處理了,那么接下來實現就水到渠成了。
實現代碼
URL如下:
from django.urls import pathfrom django.views.generic.base import TemplateViewfrom .views import echarts_dataurlpatterns = [ path('echarts/', TemplateView.as_view(template_name='echarts.html'), name='echarts-url'), path('api/echarts/', echarts_data, name='api-echarts')] 因為是前后端分離的,所以我這里用了兩個url echarts 和 api/echarts
echarts 為前臺訪問地址,對應下邊的html代碼,通過ajax方式調用后端接口,所以這里直接用了TemplateView,不需要再寫額外的view代碼
api/echarts 為后端API的地址,對應下邊的view代碼,為前臺提供數據接口
前端HTML:
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>ops-coffee</title> <!-- 引入 echarts.js --> <script src="/static/js/jquery.min.js"></script> <script src="/static/js/echarts/echarts.common.min.js"></script></head><body> <!-- 為ECharts準備一個具備大小(寬高)的Dom --> <div id="main" style="height:400px;"></div> <script type="text/javascript"> // 基于準備好的dom,初始化echarts實例 var myChart = echarts.init(document.getElementById('main')); $.ajax({ type: "get", url: "/api/echarts", dataType: "json", success: function (data) { // 指定圖表的配置項和數據 var option = { title: { left: 'center', text: 'ops-coffee 運維咖啡吧' }, tooltip: {}, xAxis: { data: data.key }, yAxis: {}, series: [{ name: '數量', type: 'bar', data: data.value }] }; // 使用剛指定的配置項和數據顯示圖表。 myChart.setOption(option); }, error: function () { alert('Error: ajax 請求出錯!') } }); </script></body></html>實例比較簡單,抄的echarts官方示例,這里會看到echarts渲染圖形實際上只需要X軸和Y軸兩個數據變量,且都為list列表類型
后端VIEW:
from django.http import JsonResponsefrom django.db.models import Countfrom .models import EventInfodef echarts_data(request): _x = EventInfo.objects.values_list('event_location').annotate(Count('id')).order_by('-id__count')[:20] jsondata = { "key": [i[0] for i in _x], "value": [i[1] for i in _x] } return JsonResponse(jsondata)最核心的那行group by + order by + limit的ORM拼接,我們上邊已經詳細的介紹過了,那么這里只需要在輸出的結果中單獨的把城市跟數量轉成兩個列表對應echarts里邊需要的X軸Y軸數據就可以了
最后訪問url: https://ops-coffee.cn/echarts 可以看到我們想要的結果
整個Demo示例介紹完成。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答