我正在用 Django 编写一个项目,我看到 80% 的代码都在文件中。这段代码令人困惑,一段时间后,我不再了解真正发生的事情。models.py
以下是困扰我的地方:
User
下面是一个简单的示例。起初,模型是这样的:User
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 提供了两种简单的命令表达方式;它们都是有效的选择,混合使用这两种方法并不罕见。
@Hedde已经描述了服务模块。在这里,您定义一个单独的模块,每个命令都表示为一个函数。
services.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
另一种方法是为每个命令使用 Django 表单。我更喜欢这种方法,因为它结合了多个密切相关的方面:
forms.py
class ActivateUserForm(forms.Form):
user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
# the username select widget is not a standard Django widget, I just made it up
def clean_user_id(self):
user_id = self.cleaned_data['user_id']
if User.objects.get(pk=user_id).active:
raise ValidationError("This user cannot be activated")
# you can also check authorizations etc.
return user_id
def execute(self):
"""
This is not a standard method in the forms API; it is intended to replace the
'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.
"""
user_id = self.cleaned_data['user_id']
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
您的示例不包含任何查询,因此我冒昧地编造了一些有用的查询。我更喜欢使用术语“问题”,但查询是经典术语。有趣的查询包括:“此用户的名称是什么?”,“此用户可以登录吗?”,“显示已停用用户的列表”和“已停用用户的地理分布是什么?”
在开始回答这些问题之前,您应该始终问自己这个问题,是这样的:
表示查询只是为了改进用户界面。业务逻辑查询的答案直接影响命令的执行。报告查询仅用于分析目的,并且具有较宽松的时间限制。这些类别并不相互排斥。
另一个问题是:“我能完全控制答案吗?例如,在查询用户名(在此上下文中)时,我们无法控制结果,因为我们依赖于外部API。
Django 中最基本的查询是使用 Manager 对象:
User.objects.filter(active=True)
当然,这仅在数据模型中实际表示时才有效。情况并非总是如此。在这些情况下,您可以考虑以下选项。
第一种替代方法对于仅表示性的查询很有用:自定义标记和模板筛选器。
模板.html
<h1>Welcome, {{ user|friendly_name }}</h1>
template_tags.py
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
如果您的查询不仅仅是表示性的,则可以将查询添加到 services.py(如果您正在使用它),或者引入 queries.py 模块:
queries.py
def inactive_users():
return User.objects.filter(active=False)
def users_called_publysher():
for user in User.objects.all():
if remote_api.get_cached_name(user.id) == "publysher":
yield user
代理模型在业务逻辑和报告的上下文中非常有用。您基本上定义了模型的增强子集。您可以通过覆盖管理器的基本查询集来覆盖该方法。Manager.get_queryset()
models.py
class InactiveUserManager(models.Manager):
def get_queryset(self):
query_set = super(InactiveUserManager, self).get_queryset()
return query_set.filter(active=False)
class InactiveUser(User):
"""
>>> for user in InactiveUser.objects.all():
… assert user.active is False
"""
objects = InactiveUserManager()
class Meta:
proxy = True
对于本质上很复杂但经常执行的查询,存在查询模型的可能性。查询模型是一种非规范化形式,其中单个查询的相关数据存储在单独的模型中。当然,诀窍是使非规范化模型与主模型保持同步。仅当更改完全在您的控制之下时,才能使用查询模型。
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
第一个选项是在命令中更新这些模型。如果这些模型仅通过一个或两个命令进行更改,这将非常有用。
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
更好的选择是使用自定义信号。这些信号当然是由您的命令发出的。信号的优点是可以使多个查询模型与原始模型保持同步。此外,信号处理可以使用 Celery 或类似框架卸载到后台任务。
signals.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
models.py
class InactiveUserDistribution(models.Model):
# see above
@receiver(user_activated)
def on_user_activated(sender, **kwargs):
user = kwargs['user']
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
使用此方法时,确定代码是否保持干净变得非常容易。只需遵循以下准则:
视图也是如此(因为视图经常遇到相同的问题)。
Django 文档:代理模型
Django 文档:信号
架构:领域驱动设计
我通常在视图和模型之间实现一个服务层。这就像您项目的 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()
请注意,我通常会将模型、视图和服务带到模块级别,并根据
项目的大小进一步分离
首先,不要重复自己。
然后,请注意不要过度设计,有时这只是浪费时间,使某人失去对重要事情的关注。不时回顾蟒蛇的禅意。
查看活动项目
yourapp/models/logicalgroup.py
User
Group
yourapp/models/users.py
Poll
Question
Answer
yourapp/models/polls.py
__all__
yourapp/models/__init__.py
更多关于 MVC
request.GET
request.POST
tastypie
piston
利用中间件/模板标签
利用模型管理器
User
UserManager(models.Manager)
models.Model
queryset
models.Manager
User
例:
class UserManager(models.Manager):
def create_user(self, username, ...):
# plain create
def create_superuser(self, username, ...):
# may set is_superuser field.
def activate(self, username):
# may use save() and send_mail()
def activate_in_bulk(self, queryset):
# may use queryset.update() instead of save()
# may use send_mass_mail() instead of send_mail()
尽可能使用表格
如果您有映射到模型的窗体,则可以消除许多样板代码。这是相当不错的。如果您有很多自定义(或者有时避免循环导入错误以用于更高级的用途),则表单代码与模型代码分开可能会很好。ModelForm documentation
尽可能使用管理命令
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
如果你有业务逻辑,你可以把它分离出来
django.contrib.auth
使用后端,就像 DB 有一个后端一样……等。setting
AUTHENTICATION_BACKENDS
)django.contrib.auth.backends.RemoteUserBackend
yourapp.backends.remote_api.RemoteUserBackend
yourapp.backends.memcached.RemoteUserBackend
后端示例:
class User(db.Models):
def get_present_name(self):
# property became not deterministic in terms of database
# data is taken from another service by api
return remote_api.request_user_name(self.uid) or 'Anonymous'
可以变成:
class User(db.Models):
def get_present_name(self):
for backend in get_backends():
try:
return backend.get_present_name(self)
except: # make pylint happy.
pass
return None
有关设计模式的更多信息
有关接口边界的更多信息
yourapp.models
yourapp.vendor
yourapp.libs
yourapp.libs.vendor
yourapp.vendor.libs
简而言之,你可以拥有
yourapp/core/backends.py
yourapp/core/models/__init__.py
yourapp/core/models/users.py
yourapp/core/models/questions.py
yourapp/core/backends.py
yourapp/core/forms.py
yourapp/core/handlers.py
yourapp/core/management/commands/__init__.py
yourapp/core/management/commands/closepolls.py
yourapp/core/management/commands/removeduplicates.py
yourapp/core/middleware.py
yourapp/core/signals.py
yourapp/core/templatetags/__init__.py
yourapp/core/templatetags/polls_extras.py
yourapp/core/views/__init__.py
yourapp/core/views/users.py
yourapp/core/views/questions.py
yourapp/core/signals.py
yourapp/lib/utils.py
yourapp/lib/textanalysis.py
yourapp/lib/ratings.py
yourapp/vendor/backends.py
yourapp/vendor/morebusinesslogic.py
yourapp/vendor/handlers.py
yourapp/vendor/middleware.py
yourapp/vendor/signals.py
yourapp/tests/test_polls.py
yourapp/tests/test_questions.py
yourapp/tests/test_duplicates.py
yourapp/tests/test_ratings.py
或任何其他对您有帮助的东西;找到您需要的接口和边界将对您有所帮助。
模板简介:该模板名称为【Python 在 django 中分离业务逻辑和数据访问】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【Python】栏目查找您需要的精美模板。