Python後端平常操做之在Django中「強行」使用MVVM設計模式

掃盲

首先帶你們瞭解一下什麼是MVVM模式:java

什麼是MVVM?MVVM是Model-View-ViewModel的縮寫。python

MVVM是MVC的加強版,實質上和MVC沒有本質區別,只是代碼的位置變更而已
從名字上看,MVVM比MVC架構中多了一個ViewModel,沒錯,就是這個ViewModel,他是MVVM相對於MVC改進的核心思想。在開發過程當中,因爲需求的變動或添加,項目的複雜度愈來愈高,代碼量愈來愈大,此時咱們會發現MVC維護起來有些吃力,首先被人吐槽的最多的就是MVC的簡寫變成了Massive-View-Controller(意爲沉重的Controller)linux

因爲Controller主要用來處理各類邏輯和數據轉化,複雜業務邏輯界面的Controller很是龐大,維護困難,因此有人想到把Controller的數據和邏輯處理部分從中抽離出來,用一個專門的對象去管理,這個對象就是ViewModel,是Model和Controller之間的一座橋樑。當人們去嘗試這種方式時,發現Controller中的代碼變得很是少,變得易於測試和維護,只須要Controller和ViewModel作數據綁定便可,這也就催生了MVVM的熱潮。android

引言

你們都知道Django是MVT模式,Model就是View和Template/Interface之間的數據傳遞的「信使」,這種模式存在一個問題,就是當咱們的業務不斷擴大以後須要在接口返回出model裏不包含的數據時該怎麼辦?例如一個商店,咱們要動態計算它距離咱們當前位置有多遠,那麼這個距離確定是不包含在Model裏面的,數據庫也不可能實時存儲這類數據。數據庫

那麼這時候咱們就須要在Model上,再加上一層ViewModel,顧名思義,視圖模型,是用來在視圖裏傳遞和處理數據的模型。c#

簡單實現

在App包下面建立一個view_models文件,內容以下:架構

from rest_framework.request import Request
from core.models import Store
from core.serializers import StoreSerializer

class StoreViewModel:
    def __init__(self, store: Store, distance=0.0, request: Request = None):
        self.store = store
        self.distance = distance
        self.request = request

    @property
    def serialize_data(self):
        return StoreSerializer(self.store, context={
            'distance': self.distance,
            'request': self.request,
        }).data

上面的代碼定義了一個商店的視圖模型,構造方法中除了咱們的Model對象,還有Model中不包括的distance參數,還有一個request用來傳遞請求的context,這個在Drf中是很重要的,若是不處理好context的傳遞,會致使Drf在序列化一些文件或者連接類字段的時候丟失前半部分的域名。測試

接下來看看serialize_data這個屬性,它作的工做很簡單,就是把Model對象傳給序列化器,而後在context中存入咱們的額外參數distance和request。設計

再來看看序列化器要如何改造以適應ViewModel模型。rest

class StoreSerializer(serializers.ModelSerializer):
    distance = serializers.SerializerMethodField()

    class Meta:
        model = models.Store
        fields = '__all__'

    def get_distance(self, obj: models.Store):
        return self.context.get('distance', 0)

這裏能夠看到序列化器中,我是把額外的distance字段處理成SerializerMethodField,而後在get_distance方法中實現,經過self.context屬性能夠獲取到咱們在ViewModel中傳入的context,這樣就實現額外參數的序列化。

最後咱們在看看在View,也就是控制器,看看如何將ViewModel和本來的分頁,權限各種功能結合在一塊兒。

class StoreViewSet(viewsets.ReadOnlyModelViewSet):
    """商家相關功能"""
    serializer_class = serializers.StoreSerializer
    queryset = models.Store.objects.all()

    @action(detail=False)
    def location(self, request):
        """根據地理位置篩選商家"""
        city = request.GET.get('city')
        town = request.GET.get('town')
        lat = request.GET.get('lat')
        lng = request.GET.get('lng')
        
        # 根據城市、區鎮篩選商店
        queryset = models.Store.objects.filter(city=city, town=town)
        
        # 調用接口計算全部商店距離當前位置的距離,該接口返回ViewModel
        store_view_models = tencent_map.stores_distance(from_lat=lat, from_lng=lng, queryset=queryset, request=request)

        # 對ViewModelSet進行排序,按照距離
        store_view_models.sort(key=lambda store_view_model: store_view_model.distance)
        
        # 使用列表生成器,對每一個ViewModel進行序列化
        stores_data = [store_vm.serialize_data for store_vm in store_view_models]
        
        # 對結果數據進行分頁
        page = self.paginate_queryset(stores_data)
        return self.get_paginated_response(page)

上面的代碼目前在開發環境運行良好,我已經寫了詳細的註釋了,能夠看到用ViewModel模式是能夠和本來的ViewSet很好的結合在一塊兒的,包括分頁這些功能均可以正常使用。

小結

標題中我用了「強行」這個詞,就是以爲我這樣實現好像很不優雅,但又不至於hack,由於這個需求很簡單,只要實現了就行,我也尚未去搜索其餘的解決方案,在本文中提出了個人ViewModel與Django結合解決方案,若是你們有更好的解決方案能夠留言一塊兒探討~

歡迎交流

我整理了一系列的技術文章和資料,在公衆號「程序設計實驗室」後臺回覆 linux、flutter、c#、netcore、android、java、python 等可獲取相關技術文章和資料,同時有任何問題均可以在公衆號後臺留言~

相關文章
相關標籤/搜索