前端使用ant design vue,后端使用DRF結(jié)合django_filters挂疆,完成分頁(yè)改览,過(guò)濾搜索功能

這是一個(gè)比較大的過(guò)程,我盡量把相關(guān)的重要知識(shí)點(diǎn)連貫起來(lái)缤言。

一宝当,先上效果圖

截屏2020-12-11下午4.07.28.png

二,后端Model定義

import uuid
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()


# 指標(biāo)集
class ViewSet(models.Model):
    view_id = models.CharField(max_length=64,
                               verbose_name='指標(biāo)集id')
    view_name = models.CharField(max_length=64,
                                 verbose_name='指標(biāo)集名稱')
    description = models.CharField(max_length=1024,
                                   null=True,
                                   blank=True,
                                   verbose_name='指標(biāo)集描述')
    create_user = models.ForeignKey(User,
                                    null=True,
                                    blank=True,
                                    related_name='ra_view_set',
                                    on_delete=models.CASCADE,
                                    verbose_name='創(chuàng)建者')
    create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
    update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
    status = models.BooleanField(default=True, verbose_name='狀態(tài)')

    @property
    def username(self):
        return self.create_user.username

    def __str__(self):
        return self.view_name

    class Meta:
        db_table = 'ViewSet'
        ordering = ('-update_date', )


# 指標(biāo)
class Attr(models.Model):
    attr_id = models.CharField(max_length=64,
                               verbose_name='指標(biāo)id')
    attr_name = models.CharField(max_length=64,
                                 verbose_name='指標(biāo)名稱')
    view_set = models.ForeignKey(ViewSet,
                                 related_name='ra_attr',
                                 on_delete=models.CASCADE,
                                 verbose_name='指標(biāo)集')
    description = models.CharField(max_length=1024,
                                   null=True,
                                   blank=True,
                                   verbose_name='指標(biāo)集描述')
    security_token = models.CharField(max_length=64,
                                      null=True,
                                      blank=True,
                                      verbose_name='連接token')
    url = models.CharField(max_length=1024,
                           null=True,
                           blank=True,
                           verbose_name='監(jiān)控url')
    create_user = models.ForeignKey(User,
                                    null=True,
                                    blank=True,
                                    related_name='ra_attr',
                                    on_delete=models.CASCADE,
                                    verbose_name='創(chuàng)建者')
    create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
    update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
    status = models.BooleanField(default=True, verbose_name='狀態(tài)')

    @property
    def username(self):
        return self.create_user.username

    def __str__(self):
        return self.attr_name

    class Meta:
        db_table = 'Attr'
        ordering = ('-update_date',)


# 異常庫(kù)
class Anomaly(models.Model):
    attr = models.ForeignKey(Attr,
                             related_name='ra_anomaly',
                             on_delete=models.CASCADE,
                             verbose_name='指標(biāo)')
    anomaly_time = models.DateTimeField(verbose_name='異常檢測(cè)時(shí)間')
    data_a = models.TextField(verbose_name='當(dāng)天180分鐘數(shù)據(jù)')
    data_b = models.TextField(verbose_name='一天前180分鐘數(shù)據(jù)')
    data_c = models.TextField(verbose_name='一周前180分鐘數(shù)據(jù)')
    mark_flag = models.CharField(max_length=16,
                                 null=True,
                                 blank=True,
                                 verbose_name='標(biāo)注正負(fù)樣本')
    create_user = models.ForeignKey(User,
                                    related_name='ra_anomaly',
                                    on_delete=models.CASCADE,
                                    verbose_name='創(chuàng)建者')
    create_date = models.DateTimeField(auto_now_add=True, verbose_name='新建時(shí)間')
    update_date = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')
    status = models.BooleanField(default=True, verbose_name='狀態(tài)')

    @property
    def username(self):
        return self.create_user.username

    @property
    def attr_name(self):
        return self.attr.attr_name

    @property
    def view_set_name(self):
        return self.attr.view_set.view_name

    def __str__(self):
        return self.attr.attr_name

    class Meta:
        db_table = 'Anomaly'
        ordering = ('-update_date', )

這里主要關(guān)注的是異常庫(kù)Anomaly有外鍵關(guān)聯(lián)Attr指標(biāo)庫(kù)胆萧,而Attr庫(kù)有外鍵關(guān)聯(lián)指標(biāo)集庫(kù)ViewSet庆揩。

三,urls.py里定義路由

path('list/', api_views.AnomalyListView.as_view(), name='list'),
as_view()用于將類方法化跌穗。

四订晌,DRF中的類視圖定義

class AnomalyListView(ListAPIView):
    queryset = Anomaly.objects.all()
    serializer_class = AnomalySerializer
    pagination_class = PNPagination
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filter_class = AnomalyFilter
    ordering_fields = ['id']

    def get(self, request, *args, **kwargs):
        res = super().get(self, request, *args, **kwargs)
        return_dict = build_ret_data(OP_SUCCESS, res.data)
        return render_json(return_dict)

DRF的ListAPIView及其它幾個(gè)generics View,是封裝度合適的蚌吸。相比于API VIEW锈拨,很多方法和變量都已有內(nèi)置實(shí)現(xiàn)。相比于VIEWSET羹唠,又保留了很多清晰明白的自定義實(shí)現(xiàn)功能奕枢。
其中build_ret_data函數(shù),用于統(tǒng)一封裝返回?cái)?shù)據(jù)佩微,定義如下:

def build_ret_data(ret_code, data=''):
    return {'code': ret_code, 'message': ERR_CODE[ret_code], 'data': data}

ERROR_CODE字典定義如下:

# coding:utf-8

OP_SUCCESS = 0
THROW_EXP = 1000
OP_DB_FAILED = 1001
CHECK_PARAM_FAILED = 1002
FILE_FORMAT_ERR = 1003
NOT_POST = 1004
NOT_GET = 1005
CAL_FEATURE_ERR = 2001
READ_FEATURE_FAILED = 2002
TRAIN_ERR = 2003
LACK_SAMPLE = 2004

ERR_CODE = {
    0: '操作成功',
    1000: "拋出異常",
    1001: "數(shù)據(jù)庫(kù)操作失敗",
    1002: "參數(shù)檢查失敗",
    1003: "文件格式有誤",
    1004: "非post請(qǐng)求",
    1005: "非get請(qǐng)求",
    2001: "特征計(jì)算出錯(cuò)",
    2002: "讀取特征數(shù)據(jù)失敗",
    2003: "訓(xùn)練出錯(cuò)",
    2004: "缺少正樣本或負(fù)樣本"
}

其中render_json缝彬,用于將返回?cái)?shù)據(jù)json化,函數(shù)定義如下:

def render_json(dictionary={}):
    response = HttpResponse(json.dumps(dictionary), content_type="application/json")
    response['Access-Control-Allow-Origin'] = '*'
    response["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type"
    response["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
    return response

由于使用了自定義的json返回哺眯,所以DRF的返回谷浅,都要作自定義操作。
這樣或許增加了一些代碼量奶卓,但可以在整個(gè)應(yīng)用內(nèi)實(shí)現(xiàn)返回碼一疯,消息體的統(tǒng)一。

五寝杖,序列化文件

from rest_framework import serializers
from MetisModels.models import Anomaly


class AnomalySerializer(serializers.ModelSerializer):
    class Meta:
        model = Anomaly
        # fields = '__all__'
        fields = ['id', 'attr', 'mark_flag', 'anomaly_time',
                  'data_a', 'data_b', 'data_c',
                  'attr_name', 'view_set_name',
                  'create_user', 'username']
        extra_kwargs = {
            'username': {
                'read_only': True,
            },
            'attr_name': {
                'read_only': True,
            },
            'view_set_name': {
                'read_only': True,
            },
            'create_user': {
                'write_only': True,
            },
        }

在序列化文件违施,read_only用于提示此字段僅作序列化顯示互纯,write_only僅作反序列化寫入瑟幕。

六,分頁(yè)文件

from rest_framework.pagination import LimitOffsetPagination
from rest_framework.pagination import PageNumberPagination


class PNPagination(PageNumberPagination):
    page_size = 10
    max_page_size = 100
    page_size_query_param = 'pageSize'
    page_query_param = 'currentPage'

    '''
    age_query_param:表示url中的頁(yè)碼參數(shù)
    page_size_query_param:表示url中每頁(yè)數(shù)量參數(shù)
    page_size:表示每頁(yè)的默認(rèn)顯示數(shù)量
    max_page_size:表示每頁(yè)最大顯示數(shù)量,做限制使用只盹,避免突然大量的查詢數(shù)據(jù)辣往,數(shù)據(jù)庫(kù)崩潰
    '''


class LOPagination(LimitOffsetPagination):
    default_limit = 10
    max_limit = 100
    limit_query_param = 'pageSize'
    offset_query_param = 'currentPage'

    '''
    default_limit:表示默認(rèn)每頁(yè)顯示幾條數(shù)據(jù)
    limit_query_param:表示url中本頁(yè)需要顯示數(shù)量參數(shù)
    offset_query_param:表示從數(shù)據(jù)庫(kù)中的第幾條數(shù)據(jù)開(kāi)始顯示參數(shù)
    max_limit:表示每頁(yè)最大顯示數(shù)量,做限制使用殖卑,避免突然大量的查詢數(shù)據(jù)站削,數(shù)據(jù)庫(kù)崩潰
    '''

這里,使用了PageNumber的分頁(yè)孵稽,我們可以傳入類似下面的頁(yè)面參數(shù)
/anomaly/list/?pageSize=6&currentPage=1&ordering=-id&mark_flag=all&begin_time=&end_time=&attr=Kafka&view_set=%E4%B8%AD%E9%97%B4
其中的pageSize和currentPage许起,要和定義的參數(shù)對(duì)應(yīng)。

七菩鲜,filters定義文件


class AnomalyFilter(FilterSet):
    attr = filters.CharFilter(field_name='attr__attr_name', lookup_expr='icontains',)
    view_set = filters.CharFilter(field_name='attr__view_set__view_name', lookup_expr='icontains',)
    begin_time = filters.CharFilter(field_name='anomaly_time', lookup_expr='gte',)
    end_time = filters.DateTimeFilter(field_name='anomaly_time', lookup_expr='lte',)
    mark_flag = filters.CharFilter(field_name='mark_flag', method='mark_flag_filter',)

    def mark_flag_filter(self, queryset, field_name, value):
        print(value)
        if value == 'no':
            return queryset.filter(~Q(mark_flag='negative') & ~Q(mark_flag='positive'))
        elif value == 'yes':
            return queryset.filter(Q(mark_flag='negative') | Q(mark_flag='positive'))
        elif value == 'all':
            return queryset
        else:
            return queryset


    class Meta:
        model = Anomaly
        fields = ['attr', 'view_set', 'begin_time', 'end_time',  'mark_flag']

算是django_filters這個(gè)庫(kù)比較在代表性的實(shí)現(xiàn)了园细。
attr字符串,使用了外鍵的關(guān)聯(lián)接校,模糊搜索猛频。
view_set字符串,使用了外鍵的外鍵關(guān)聯(lián)蛛勉,模糊搜索鹿寻。
begin_time和end_time日期時(shí)間,范圍的選擇诽凌。
mark_flag字符串毡熏,使用了自定義過(guò)濾。其中queryset為已過(guò)濾集合皿淋,value為取到的field字段的值招刹。Q是django orm的魔法功能,此處用于包含或不包含窝趣。

八疯暑,前端搜索框定義

<div>
      <a-form-model layout="horizontal" ref="searchKey" :model="pagination.searchKey" @submit="handleSubmit">
        <div class="fold">
          <a-row >
          <a-col :md="8" :sm="24" >
            <a-form-model-item
              label="指標(biāo)集"
              :labelCol="{span: 5}"
              :wrapperCol="{span: 18, offset: 1}"
            >
              <a-input placeholder="請(qǐng)輸入" v-model="pagination.searchKey.viewSet" />
            </a-form-model-item>
          </a-col>
          <a-col :md="8" :sm="24" >
            <a-form-model-item
              label="指標(biāo)"
              :labelCol="{span: 5}"
              :wrapperCol="{span: 18, offset: 1}"
            >
              <a-input placeholder="請(qǐng)輸入" v-model="pagination.searchKey.attr" />
            </a-form-model-item>
          </a-col>
          <a-col :md="8" :sm="24" >
            <a-form-model-item
              label="時(shí)間范圍"
              :labelCol="{span: 5}"
              :wrapperCol="{span: 18, offset: 1}"
            >
              <a-range-picker
                :ranges='timeRange'
                :placeholder="['開(kāi)始時(shí)間', '結(jié)束時(shí)間']"
                @change="createChange"
                style="width: 100%" 
              />
            </a-form-model-item>
          </a-col>
        </a-row>
        </div>
        <span style="float: right; margin-top: 3px;">
          <a-button type="primary" html-type="submit">查詢</a-button>
          <a-button style="margin-left: 8px" html-type="reset" @click="resetForm">重置</a-button>
        </span>
      </a-form-model>
    </div>

v-model作雙向綁定

九,data中的pagination定義

pagination: {
                'total': 0,
                'pageSize': 6,
                'currentPage': 1,
                'ordering': '-id',
                'searchKey': {
                    'markFlag': this.$route.meta.label,
                    'viewSet': '',
                    'attr': '',
                    'beginTime': '',
                    'endTime': ''
                },
                onChange: page => {
                    const pager = { ...this.pagination };
                    pager.currentPage = page;
                    this.pagination = pager;
                    this.fetch(this.pagination);
                },
            },

這個(gè)定義中哑舒,不但包含了分頁(yè)參數(shù)妇拯,還包含了搜索過(guò)濾的參數(shù)。
onChange用于分頁(yè)技巧洗鸵。

十越锈,methods中時(shí)間選擇時(shí)觸發(fā)的變量更新

moment,
        createChange(dates, dateStrings) {
          this.pagination.searchKey.beginTime = dateStrings[0]
            this.pagination.searchKey.endTime = dateStrings[1]
        },

十一,前端向后端請(qǐng)求數(shù)據(jù)時(shí)膘滨,參數(shù)作一次理順甘凭。

import {ANOMALY_LIST, ANOMALY_UPDATE} from '@/services/api'
import {request, METHOD} from '@/utils/request'

/**
 * 獲取所有異常時(shí)序
 */
export async function getAnomalyList(data) {
    const pageSize = data['pageSize']
    const currentPage = data['currentPage']
    const ordering = data['ordering']
    const markFlag = data['searchKey']['markFlag']
    const beginTime = data['searchKey']['beginTime']
    const endTime = data['searchKey']['endTime']
    const viewSet = data['searchKey']['viewSet']
    const attr = data['searchKey']['attr']
  return request(ANOMALY_LIST, METHOD.GET, {
        pageSize,
        currentPage,
        ordering, 
        'mark_flag': markFlag,
        'begin_time': beginTime,
        'end_time': endTime,
        'attr': attr,
        'view_set': viewSet,
        
    })
}

這里面,作了axios的封裝火邓,token丹弱,守衛(wèi)路由這些都有德撬。

十二,一個(gè)請(qǐng)求ajax的demo躲胳。

fetch(params={}) {
            this.loading = true;
            getAnomalyList(params).then(resp => {
                let retData = resp.data
                if (retData.code == 0) {
                    this.dataSource = []
                    this.dataAbc = []
                    this.dataGraphAbc = {}
                    const results = retData.data.results
                    for (let i = 0; i < results.length; i++) {
                        const anomalyTime = `${this.$options.filters.secFormat(results[i].anomaly_time)}`
                        const attrName = results[i].attr_name
                        const view_set_name = results[i].view_set_name
                        this.dataSource.push({
                            key: i,
                            id: results[i].id,
                            attrName: attrName,
                            viewSetName: view_set_name,
                            anomalyTime: anomalyTime,
                            markFlag: results[i].mark_flag,
                            titleSample: `[${view_set_name}-${attrName}]:${anomalyTime}`,
                            dataGraphAbc: dataSeries(anomalyTime, 
                                                                results[i].data_a, 
                                                                results[i].data_b, 
                                                                results[i].data_c),
                            createDate: results[i].create_date,
                            createUser: results[i].username
                        })
                        this.dataAbc.push(dataSeries(anomalyTime, 
                                                                results[i].data_a, 
                                                                results[i].data_b, 
                                                                results[i].data_c))
                        this.titleSamples.push(`[${view_set_name}-${attrName}]:${anomalyTime}`)
                    }
                    const pager = { ...this.pagination };
                    // Read total count from server
                    pager.total = retData.data.count;
                    this.pagination = pager;
                    this.loading = false;
                } else {
                    this.loading = false;
                    this.$message.error(createRes.message, 3)
                }
            })
        },

這里的params={}參數(shù)蜓洪,就是傳的pagination變量。

十三坯苹,完工隆檀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市粹湃,隨后出現(xiàn)的幾起案子恐仑,更是在濱河造成了極大的恐慌,老刑警劉巖为鳄,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菊霜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡济赎,警方通過(guò)查閱死者的電腦和手機(jī)鉴逞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)司训,“玉大人构捡,你說(shuō)我怎么就攤上這事】遣拢” “怎么了勾徽?”我有些...
    開(kāi)封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)统扳。 經(jīng)常有香客問(wèn)我喘帚,道長(zhǎng),這世上最難降的妖魔是什么咒钟? 我笑而不...
    開(kāi)封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任吹由,我火速辦了婚禮,結(jié)果婚禮上朱嘴,老公的妹妹穿的比我還像新娘倾鲫。我一直安慰自己,他們只是感情好萍嬉,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布乌昔。 她就那樣靜靜地躺著,像睡著了一般壤追。 火紅的嫁衣襯著肌膚如雪磕道。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天行冰,我揣著相機(jī)與錄音溺蕉,去河邊找鬼贯卦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛焙贷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贿堰,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辙芍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了羹与?” 一聲冷哼從身側(cè)響起故硅,我...
    開(kāi)封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纵搁,沒(méi)想到半個(gè)月后吃衅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腾誉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年徘层,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利职。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趣效,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猪贪,到底是詐尸還是另有隱情跷敬,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布热押,位于F島的核電站西傀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桶癣。R本人自食惡果不足惜拥褂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牙寞。 院中可真熱鬧肿仑,春花似錦、人聲如沸碎税。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雷蹂。三九已至伟端,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匪煌,已是汗流浹背责蝠。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工党巾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霜医。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓齿拂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肴敛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子署海,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容