我正在用Django写一个项目,我看到80%的代码都在models.py文件中。这段代码令人困惑,过了一段时间后,我就不明白到底发生了什么。
这是困扰我的问题:
I find it ugly that my model level (which was supposed to be
responsible only for the work with data from a database) is also
sending email, walking on API to other services, etc.
Also, I find it unacceptable to place business logic in the view, because
this way it becomes difficult to control. For example, in my
application there are at least three ways to create new
instances of User, but technically it should create them uniformly.
I do not always notice when the methods and
properties of my models become non-deterministic and when they develop
side effects.
这里有一个简单的例子。一开始,用户模型是这样的:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
随着时间的推移,它变成了这样:
class User(db.Models):
def get_present_name(self):
# property became non-deterministic in terms of database
# data is taken from another service by api
return remote_api.request_user_name(self.uid) or 'Anonymous'
def activate(self):
# method now has a side effect (send message to user)
self.status = 'activated'
self.save()
send_mail('Your account is activated!', '…', [self.email])
我想要的是在代码中分离实体:
数据库级实体,即数据库级逻辑:我的应用程序存储什么样的数据?
应用程序级实体,即业务级逻辑:我的应用程序做什么?
在Django中实现这样一种方法有哪些好的实践呢?
在Django中,MVC结构正如Chris Pratt所说,与其他框架中使用的经典MVC模型不同,我认为这样做的主要原因是避免了过于严格的应用程序结构,就像在其他MVC框架(如CakePHP)中发生的那样。
在Django中,MVC是这样实现的:
视图层被分成两部分。视图应该只用于管理HTTP请求,它们被调用并响应它们。视图与应用程序的其余部分(表单、模型表单、自定义类,在简单情况下直接与模型通信)通信。
为了创建接口,我们使用模板。对于Django来说,模板是类似字符串的,它将一个上下文映射到模板中,这个上下文由应用程序传递给视图(当视图请求时)。
Model layer gives encapsulation, abstraction, validation, intelligence and makes your data object-oriented (they say someday DBMS will also). This doesn't means that you should make huge models.py files (in fact a very good advice is to split your models in different files, put them into a folder called 'models', make an '__init__.py' file into this folder where you import all your models and finally use the attribute 'app_label' of models.Model class). Model should abstract you from operating with data, it will make your application simpler. You should also, if required, create external classes, like "tools" for your models.You can also use heritage in models, setting the 'abstract' attribute of your model's Meta class to 'True'.
剩下的在哪里?小型web应用程序通常是一种数据接口,在一些小型程序中,使用视图来查询或插入数据就足够了。更常见的情况是使用form或modelform,它们实际上是“控制器”。这是一个常见问题的实际解决方案,而且是一个非常快速的解决方案。这就是网站的作用。
If Forms are not enogh for you, then you should create your own classes to do the magic, a very good example of this is admin application: you can read ModelAmin code, this actually works as a controller. There is not a standard structure, I suggest you to examine existing Django apps, it depends on each case. This is what Django developers intended, you can add xml parser class, an API connector class, add Celery for performing tasks, twisted for a reactor-based application, use only the ORM, make a web service, modify the admin application and more... It's your responsability to make good quality code, respect MVC philosophy or not, make it module based and creating your own abstraction layers. It's very flexible.
我的建议是:尽可能多地阅读代码,周围有很多django应用程序,但不要太把它们当回事。每个案例都是不同的,模式和理论会有所帮助,但并不总是如此,这是一门不精确的科学,django只是为你提供了一些好的工具,你可以用它们来减轻一些痛苦(比如管理界面、web表单验证、i18n、观察者模式实现,以及前面提到的所有这些),但是好的设计来自有经验的设计师。
PS.:使用'User'类从auth应用程序(从标准django),你可以创建示例用户配置文件,或至少阅读它的代码,这将对你的情况很有用。
在Django中,MVC结构正如Chris Pratt所说,与其他框架中使用的经典MVC模型不同,我认为这样做的主要原因是避免了过于严格的应用程序结构,就像在其他MVC框架(如CakePHP)中发生的那样。
在Django中,MVC是这样实现的:
视图层被分成两部分。视图应该只用于管理HTTP请求,它们被调用并响应它们。视图与应用程序的其余部分(表单、模型表单、自定义类,在简单情况下直接与模型通信)通信。
为了创建接口,我们使用模板。对于Django来说,模板是类似字符串的,它将一个上下文映射到模板中,这个上下文由应用程序传递给视图(当视图请求时)。
Model layer gives encapsulation, abstraction, validation, intelligence and makes your data object-oriented (they say someday DBMS will also). This doesn't means that you should make huge models.py files (in fact a very good advice is to split your models in different files, put them into a folder called 'models', make an '__init__.py' file into this folder where you import all your models and finally use the attribute 'app_label' of models.Model class). Model should abstract you from operating with data, it will make your application simpler. You should also, if required, create external classes, like "tools" for your models.You can also use heritage in models, setting the 'abstract' attribute of your model's Meta class to 'True'.
剩下的在哪里?小型web应用程序通常是一种数据接口,在一些小型程序中,使用视图来查询或插入数据就足够了。更常见的情况是使用form或modelform,它们实际上是“控制器”。这是一个常见问题的实际解决方案,而且是一个非常快速的解决方案。这就是网站的作用。
If Forms are not enogh for you, then you should create your own classes to do the magic, a very good example of this is admin application: you can read ModelAmin code, this actually works as a controller. There is not a standard structure, I suggest you to examine existing Django apps, it depends on each case. This is what Django developers intended, you can add xml parser class, an API connector class, add Celery for performing tasks, twisted for a reactor-based application, use only the ORM, make a web service, modify the admin application and more... It's your responsability to make good quality code, respect MVC philosophy or not, make it module based and creating your own abstraction layers. It's very flexible.
我的建议是:尽可能多地阅读代码,周围有很多django应用程序,但不要太把它们当回事。每个案例都是不同的,模式和理论会有所帮助,但并不总是如此,这是一门不精确的科学,django只是为你提供了一些好的工具,你可以用它们来减轻一些痛苦(比如管理界面、web表单验证、i18n、观察者模式实现,以及前面提到的所有这些),但是好的设计来自有经验的设计师。
PS.:使用'User'类从auth应用程序(从标准django),你可以创建示例用户配置文件,或至少阅读它的代码,这将对你的情况很有用。
这是一个老问题,但我还是想提出我的解决方案。它是基于这样一种接受:模型对象也需要一些额外的功能,而把它放在models.py中是很尴尬的。重业务逻辑可以根据个人喜好单独编写,但我至少喜欢模型做与自身相关的所有事情。该解决方案还支持那些喜欢将所有逻辑置于模型本身中的人。
因此,我设计了一个hack,允许我将逻辑从模型定义中分离出来,并且仍然可以从我的IDE中得到所有的提示。
优点应该是显而易见的,但下面列出了我观察到的一些优点:
DB定义仍然是那样-没有逻辑“垃圾”附加
与模型相关的逻辑都整齐地放在一个地方
所有服务(表单、REST、视图)都有一个逻辑访问点
最棒的是:当我意识到我的models.py变得太杂乱,不得不分离逻辑时,我不需要重写任何代码。分离是平滑和迭代的:我可以一次处理一个函数,也可以一次处理整个类或整个models.py。
我一直在使用Python 3.4及更高版本和Django 1.8及更高版本。
app / models.py
....
from app.logic.user import UserLogic
class User(models.Model, UserLogic):
field1 = models.AnyField(....)
... field definitions ...
应用程序/逻辑/ user.py
if False:
# This allows the IDE to know about the User model and its member fields
from main.models import User
class UserLogic(object):
def logic_function(self: 'User'):
... code with hinting working normally ...
我唯一想不明白的是如何让我的IDE(在这种情况下是PyCharm)识别UserLogic实际上是用户模型。但由于这显然是一种hack,我很乐意接受总是为self参数指定类型的小麻烦。
我不得不同意你的看法。django中有很多可能性,但最好的起点是回顾django的设计理念。
Calling an API from a model property would not be ideal, it seems like it would make more sense to do something like this in the view and possibly create a service layer to keep things dry. If the call to the API is non-blocking and the call is an expensive one, sending the request to a service worker (a worker that consumes from a queue) might make sense.
As per Django's design philosophy models encapsulate every aspect of an "object". So all business logic related to that object should live there:
包括所有相关的领域逻辑
模型应该封装“对象”的每个方面,遵循Martin Fowler的活动记录设计模式。
The side effects you describe are apparent, the logic here could be better broken down into Querysets and managers. Here is an example:
models.py
import datetime
from djongo import models
from django.db.models.query import QuerySet
from django.contrib import admin
from django.db import transaction
class MyUser(models.Model):
present_name = models.TextField(null=False, blank=True)
status = models.TextField(null=False, blank=True)
last_active = models.DateTimeField(auto_now=True, editable=False)
# As mentioned you could put this in a template tag to pull it
# from cache there. Depending on how it is used, it could be
# retrieved from within the admin view or from a custom view
# if that is the only place you will use it.
#def get_present_name(self):
# # property became non-deterministic in terms of database
# # data is taken from another service by api
# return remote_api.request_user_name(self.uid) or 'Anonymous'
# Moved to admin as an action
# def activate(self):
# # method now has a side effect (send message to user)
# self.status = 'activated'
# self.save()
# # send email via email service
# #send_mail('Your account is activated!', '…', [self.email])
class Meta:
ordering = ['-id'] # Needed for DRF pagination
def __unicode__(self):
return '{}'.format(self.pk)
class MyUserRegistrationQuerySet(QuerySet):
def for_inactive_users(self):
new_date = datetime.datetime.now() - datetime.timedelta(days=3*365) # 3 Years ago
return self.filter(last_active__lte=new_date.year)
def by_user_id(self, user_ids):
return self.filter(id__in=user_ids)
class MyUserRegistrationManager(models.Manager):
def get_query_set(self):
return MyUserRegistrationQuerySet(self.model, using=self._db)
def with_no_activity(self):
return self.get_query_set().for_inactive_users()
admin.py
# Then in model admin
class MyUserRegistrationAdmin(admin.ModelAdmin):
actions = (
'send_welcome_emails',
)
def send_activate_emails(self, request, queryset):
rows_affected = 0
for obj in queryset:
with transaction.commit_on_success():
# send_email('welcome_email', request, obj) # send email via email service
obj.status = 'activated'
obj.save()
rows_affected += 1
self.message_user(request, 'sent %d' % rows_affected)
admin.site.register(MyUser, MyUserRegistrationAdmin)
我通常在视图和模型之间实现一个服务层。这就像你的项目的API,给你一个良好的直升机视图正在发生什么。我从我的一个同事那里继承了这个实践,他在Java项目(JSF)中经常使用这种分层技术,例如:
models.py
class Book:
author = models.ForeignKey(User)
title = models.CharField(max_length=125)
class Meta:
app_label = "library"
services.py
from library.models import Book
def get_books(limit=None, **filters):
""" simple service function for retrieving books can be widely extended """
return Book.objects.filter(**filters)[:limit] # list[:None] will return the entire list
views.py
from library.services import get_books
class BookListView(ListView):
""" simple view, e.g. implement a _build and _apply filters function """
queryset = get_books()
请注意,我通常将模型、视图和服务放在模块级别
根据项目的大小,可以进一步分开