曦远 (DealiAxy)一份私人的文字刊物

StarBlog
返回文章列表折页手记阅读全文
Django

Django中「强行」使用MVVM设计模式

扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写。 MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 从名字上看,MVVM比MVC架构中多了一个ViewModel,没错,就是这个ViewModel,他是MVVM相对于MVC改进的核心思想。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量

曦远 (DealiAxy)约 9 分钟
目录5 个章节

扫盲

首先带大家了解一下什么是MVVM模式:

什么是MVVM?MVVM是Model-View-ViewModel的缩写。

MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 从名字上看,MVVM比MVC架构中多了一个ViewModel,没错,就是这个ViewModel,他是MVVM相对于MVC改进的核心思想。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现MVC维护起来有些吃力,首先被人吐槽的最多的就是MVC的简写变成了Massive-View-Controller(意为沉重的Controller)

由于Controller主要用来处理各种逻辑和数据转化,复杂业务逻辑界面的Controller非常庞大,维护困难,所以有人想到把Controller的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是ViewModel,是Model和Controller之间的一座桥梁。当人们去尝试这种方式时,发现Controller中的代码变得非常少,变得易于测试和维护,只需要Controller和ViewModel做数据绑定即可,这也就催生了MVVM的热潮。

引言

大家都知道Django是MVT模式,Model就是View和Template/Interface之间的数据传递的「信使」,这种模式存在一个问题,就是当我们的业务不断扩大之后需要在接口返回出model里不包含的数据时该怎么办?例如一个商店,我们要动态计算它距离我们当前位置有多远,那么这个距离肯定是不包含在Model里面的,数据库也不可能实时存储这类数据。

那么这时候我们就需要在Model上,再加上一层ViewModel,顾名思义,视图模型,是用来在视图里传递和处理数据的模型。

简单实现

在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模型。

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 等可获取相关技术文章和资料,同时有任何问题都可以在公众号后台留言~

_EOF_

Django中「强行」使用MVVM设计模式

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

Django中间件之实现Admin后台IP白名单

前言 Django自带的Admin管理后台很方便,但是实际生产环境真的会有挺多安全问题的,在admin的安全防护这方面,我之前就研究实现了给admin加上登录验证码和限流功能,可以参考这篇文章:给Django Admin添加验证码和多次登录尝试限制 不过就在内部使用的话就好很多,因此我们可以通过Django的中间件来实现IP过滤,所以本文中涉及到两个知识点,一个是Django的中间件,另外一个是P

2022-08-26 · Django · 约 1 分钟

Django探索验证码功能的实现 - DjangoStarter项目模板里的封装

前言 依然是最近在做的这个项目,用Django做后端,App上提交信息的时候需要一个验证码来防止用户乱提交,正好我的「DjangoStarter」项目脚手架也有封装了验证码功能,不过我发现好像里面只是把验证码作为admin后台登录的校验手段,并没有给出前后端分离项目的验证码相关接口。 所以本文介绍验证码功能实现的同时,也对「DjangoStarter」的验证码模块做一层封装,使其更方便使用~ 用哪

2022-04-09 · Django · 约 1 分钟

轻量级消息队列Django-Q轻度体验

前言 最近做的这个项目(基于Django),需要做个功能,实现定时采集车辆定位。 这让我想起来几年前那个OneCat项目,当时我用的是Celery这个很重的组件 Celery实在是太重了,后来我做公众号采集平台的时候,又接触了Django-RQ和Django-Q这俩,前者是对RQ的封装,让RQ和Django更好的结合在一起;后者是一个全新的「多进程任务队列」组件,相比起celery很轻量,当时使用

2022-08-31 · Django · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…