返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Django

"Forcibly" Using the MVVM Design Pattern in Django

A Primer First, let me walk you through what the MVVM pattern is: What is MVVM? MVVM is an abbreviation for Model-View-ViewModel. MVVM is an enhanced version of MVC; there is essentially no fundamental difference from MVC—it's just that the code lives in different places From the name, MVVM has one extra ViewModel compared to the MVC architecture. That's right—this ViewModel is the core idea behind how MVVM improves on MVC. During development, as requirements change or get added, projects grow more and more complex and the amount of code

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

A Primer

First, let me walk you through what the MVVM pattern is:

What is MVVM? MVVM is an abbreviation for Model-View-ViewModel.

MVVM is an enhanced version of MVC; there is essentially no fundamental difference from MVC—it's just that the code lives in different places From the name, MVVM has one extra ViewModel compared to the MVC architecture. That's right—this ViewModel is the core idea behind how MVVM improves on MVC. During development, as requirements change or get added, projects grow more and more complex and the amount of code gets larger and larger. At that point we find MVC somewhat hard to maintain. The complaint people make most often is that MVC's acronym has become Massive-View-Controller (meaning a bloated Controller)

Because the Controller is mainly used to handle various kinds of logic and data transformation, Controllers for screens with complex business logic become huge and hard to maintain. So someone thought of extracting the data and logic-handling parts from the Controller and managing them with a dedicated object. That object is the ViewModel, a bridge between the Model and the Controller. When people tried this approach, they found that the code in the Controller became very little, easier to test and maintain—you only need the Controller and the ViewModel to do data binding. This is what gave rise to the MVVM boom.

Introduction

Everyone knows Django uses the MVT pattern. The Model is the "messenger" that passes data between the View and the Template/Interface. This pattern has a problem: what do we do when our business keeps expanding and we need an API to return data that is not contained in the model? For example, a store—we need to dynamically calculate how far it is from our current location. That distance is definitely not contained in the Model, and the database cannot possibly store this kind of data in real time.

So at this point we need to add another layer of ViewModel on top of the Model. As the name suggests, a view model is a model used to pass and process data in the view.

A Simple Implementation

Create a view_models file under the App package, with the following contents:

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

The code above defines a store view model. Besides our Model object, the constructor also has a distance parameter that is not included in the Model, plus a request used to pass the request context. This is very important in DRF; if you don't handle context passing properly, DRF will drop the domain prefix when serializing some file or URL-type fields.

Next, look at the serialize_data property. What it does is simple: it passes the Model object to the serializer, then stores our extra parameters distance and request in the context.

Now let's see how the serializer needs to be adapted to fit the ViewModel pattern.

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)

Here you can see that in the serializer, I treat the extra distance field as a SerializerMethodField, then implement it in the get_distance method. Through the self.context attribute we can get the context we passed in from the ViewModel, and that is how extra parameters get serialized.

Finally, let's look at the View, i.e. the controller, and see how to combine the ViewModel with the original pagination, permissions, and other features.

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)

The code above currently runs well in the development environment. I've already written detailed comments. You can see that the ViewModel pattern can be combined quite well with the original ViewSet, including features like pagination, which can all be used normally.

Recap

In the title I used the word "forcibly" because I feel this implementation of mine doesn't seem very elegant, but it's not quite a hack either, because the requirement is simple—as long as it works, that's enough. I haven't gone looking for other solutions yet. In this article I presented my solution for combining ViewModel with Django. If anyone has a better solution, feel free to leave a comment and we can discuss it together~

Welcome to Discuss

I've put together a series of technical articles and materials. Reply with linux, flutter, c#, netcore, android, java, python, and so on in the backend of the WeChat official account "Programming Lab" to get related technical articles and materials. If you have any questions, you can also leave a message in the official account backend~

_EOF_

"Forcibly" Using the MVVM Design Pattern in Django

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

相关文章

DjangoRestFramework框架三种分页功能的实现 - 在DjangoStarter项目模板中封装

前言 继续Django后端开发系列文章。刚好遇到一个分页的需求,就记录一下。 Django作为一个“全家桶”型的框架,本身啥都有,分页组件也是有的,但默认的分页组件没有对API开发做优化,所以DjangoRestFramework这个专门写API的框架又把Django的分页组件包装了一层,集成在viewsets里的时候会更方便。 不过我们不可能一直用viewsets,有一部分API还是要用自由度更

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

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

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

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

讨论

共 0 条

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

加载评论中…