Without good layering, a Web project will eventually collapse.
The reason
Django projects are typically segmented by APP, and each APP has a similar structure, with everyone “minding their own business,” a bit like a microservice. But there are a lot of people who write Django code, and there are no rules. It is even common to see a naked call to userModel.objects.filter from the Controller layer. However, we found that many of the operations on the database were common, and it became necessary to isolate a layer.
Reference object
How to organize and design our layer? We don’t have to do it ourselves. We can look at what mature projects do. Java Spring is the object I refer to. The general Spring project has a very clear hierarchical structure. Although it needs to write more code in the early stage, it really brings a lot of convenience to the later code maintenance.
It is generally divided into the following levels:
Controller Service Repository(DAO) (Mapper, optional, if using Mybatis) ModelCopy the code
Django’s Manager layer (xxModel.objects) corresponds to the DAO layer, but we call it something different.
We will call the extracted individual layer DAO, which, as we will see later, is simply a combination of Manager layer apis to provide some common operations.
How to write
Before we start writing, let’s start with some practical experience: What general apis should we provide? Here’s what I learned from my own experience:
- save(obj)
- delete(obj)
- update(obj)
- findOne/findAll
So by what means? Thanks to Python’s powerful language features, we can write code that is not as tedious as Java. Here are my steps:
- I’m going to encapsulate a base superclass, and I’m going to put all the generic operations into it, and I’m going to call it
BaseDAO
. - The rest of us inherit from this parent class.
- Use at the Controller or Service layer
Here is the code snippet:
# Code based on Python 3.5, if you want to put in Python 2, you can drop the Type Hint
from .BaseModel import BaseModel A typical project encapsulates a base class Model
class BaseDAO:
Subclasses must override this
MODEL_CLASS = BaseModel
SAVE_BATCH_SIZE = 1000
def save(self, obj):
"""insert one :param obj: :return: """
if not obj:
return False
obj.save()
return True
def save_batch(self, objs, *, batch_size=SAVE_BATCH_SIZE):
"""insert batch :type objs: list[BaseModel] :param objs: :return: """
if not objs:
return False
self.MODEL_CLASS.objects.bulk_create(objs, batch_size=batch_size)
return True
def delete(self, obj):
if not obj:
return False
obj.delete()
return True
def delete_batch(self, objs):
if not objs:
return False
for obj in objs:
self.delete(obj)
return True
def delete_batch_by_query(self, filter_kw: dict, exclude_kw: dict):
"""批量删除
"""
self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).delete()
return True
def delete_by_fake(self, obj):
""" fake delete/fake delete ""
if obj is None:
return False
obj.is_deleted = True
obj.save()
return True
def update(self, obj):
if not obj:
return False
obj.save()
return True
def update_batch(self, objs):
if not objs:
return False
for obj in objs:
self.update(obj)
return True
def update_batch_by_query(self, query_kwargs: dict, exclude_kw: dict, newattrs_kwargs: dict):
self.MODEL_CLASS.objects.filter(**query_kwargs).exclude(**exclude_kw).update(**newattrs_kwargs)
def find_one(self, filter_kw: dict, exclude_kw: dict, order_bys: list):
""" :param query_kwargs: :rtype: BaseModel | None :return: """
qs = self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw)
if order_bys:
qs = qs.order_by(*order_bys)
return qs.first()
def find_queryset(self, filter_kw: dict, exclude_kw: dict, order_bys: list):
""" :param filter_kw: :return: """
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw)
def find_all_model_objs(self, filter_kw: dict, exclude_kw: dict, order_bys: list) -> list:
return self.find_queryset(filter_kw, exclude_kw, order_bys).all()
def is_exists(self, filter_kw:dict, exclude_kw:dict) -> bool:
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).exists()
def get_count(self, filter_kw:dict, exclude_kw:dict) -> int:
return self.MODEL_CLASS.objects.filter(**filter_kw).exclude(**exclude_kw).count()
Copy the code
How to use
For example, in a Django APP:
Py tests.py dao/ (you can also put it in a separate dao. I prefer to have a directory and a class for each py file, which keeps the Java style) goodsdao.py models.py goodsdao.py content from.. models import Goods from common_base import BaseDAO class GoodsDao(BaseDAO): MODEL_CLASS = GoodsCopy the code
Upper use: basic can be very free use. These are generic CURD operations that don’t change much, and no longer need to write lengthy xxmodel.objects.filter
extension
From the above summary, we can see that it does bring a good encapsulation, although the initial need to write more code, but later code maintenance is more comfortable. Another question is: should we get rid of goods.objects.filter?
I don’t think so. The goods.objects.filter is still free to use, but in cases where the DAO can’t handle it (and you don’t want to wrap it anymore because it’s low frequency), it comes in. They should complement each other and blend with each other, each with its own usage scenarios. The original notation applies to “lower frequency, temporary CURD operations,” while DAO applies to “higher frequency, general purpose CURD operations.”
Also, Django ORM isn’t the only popular ORM in the Python world. For example, SQLAlchemy, you can also encapsulate a similar DAO layer to make your code more comfortable.