Python django中业务逻辑和数据访问的分离
我正在用Django编写一个项目,我看到80%的代码都在文件Python django中业务逻辑和数据访问的分离,python,django,model-view-controller,data-access-layer,business-logic-layer,Python,Django,Model View Controller,Data Access Layer,Business Logic Layer,我正在用Django编写一个项目,我看到80%的代码都在文件models.py中。这段代码令人困惑,经过一段时间后,我不再理解真正发生的事情 以下是困扰我的问题: 我发现我的模型级别(应该是 仅负责处理数据库中的数据)也是 发送电子邮件、在API上漫游到其他服务等 此外,我发现在视图中放置业务逻辑是不可接受的,因为 这样就很难控制了。例如,在我的 应用程序创建新应用程序至少有三种方法 用户的实例,但从技术上讲,它应该统一创建它们 我并不总是注意到方法和 我的模型的属性在发展时变得不确定 副作用
models.py
中。这段代码令人困惑,经过一段时间后,我不再理解真正发生的事情
以下是困扰我的问题:
用户的实例
,但从技术上讲,它应该统一创建它们用户
模型是这样的:
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中实现这种方法的最佳实践是什么?我通常在视图和模型之间实现一个服务层。这就像您的项目的API一样,可以让您从直升机上很好地了解正在发生的事情。我从我的一位同事那里继承了这种做法,他在Java项目(JSF)中大量使用这种分层技术,例如: 型号.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
from library.services import get_books
class BookListView(ListView):
""" simple view, e.g. implement a _build and _apply filters function """
queryset = get_books()
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
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
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
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
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
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
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()
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
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()
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()
# 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)
视图.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
from library.services import get_books
class BookListView(ListView):
""" simple view, e.g. implement a _build and _apply filters function """
queryset = get_books()
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
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
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
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
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
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
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()
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
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()
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()
# 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)
请注意,我通常将模型、视图和服务带到模块级和
根据项目的规模进一步分离
Django设计用于轻松交付网页。如果您对此不满意,也许您应该使用另一种解决方案 我正在模型上编写根操作或公共操作(以具有相同的接口),以及模型控制器上的其他操作。如果我需要其他模型的操作,我将导入其控制器 这种方法对我和我的应用程序的复杂性来说已经足够了 Hedde的响应是一个展示django和python本身灵活性的示例
这个问题很有趣 Django使用了一种稍微修改过的MVC。Django中没有“控制器”的概念。最接近的代理是“视图”,这往往会导致与MVC转换混淆,因为在MVC中,视图更像Django的“模板” 在Django中,“模型”不仅仅是数据库抽象。在某些方面,它与作为MVC控制器的Django的“视图”分担责任。它包含与实例关联的所有行为。如果该实例需要与外部API交互作为其行为的一部分,那么这仍然是模型代码。事实上,模型根本不需要与数据库交互,因此您可以想象模型完全作为外部API的交互层存在。这是一个更加自由的“模型”概念 然后,请注意不要过度设计,有时这只是浪费时间,会让人失去对重要事情的关注。不时地回顾一下这篇文章 查看活动项目
- 更多的人=更需要合理组织
- 它们有一个简单的结构
- 它们有一个straigtforward目录结构
- 这也是一个很好的一看
- 您可以将所有模型放在应用程序/models/logicalgroup.py下
- 您可以将所有模型放在应用程序/models/logicalgroup.py下
- e、 g
,用户
和相关模型可以放在组
你的应用程序/模型/用户.py下
- e、 g
,投票
,问题
。。。可以在你的app/models/polls.py下进入答案
- 在
你的应用程序/模型/\uuuuu init\uuuuuuuuuuupy的
中加载你需要的内容
- 模型是您的数据
- 这包括您的实际数据
- 这还包括会话/cookie/cache/fs/index数据
- 用户与控制器交互以操纵模型
- 这可以是API,也可以是保存/更新数据的视图
- 这可以通过
/request.GET
等进行调整request.POST
- 思考分页或过滤
- 数据将更新视图
- 模板获取数据并相应地格式化
- 甚至不带模板的API都是视图的一部分;e、 g.
或tastype
活塞
- 这也应该考虑中间件
- 如果您需要为每个请求做一些工作,中间件是一种方法。
- e、 g.添加时间戳
- e、 g.更新有关页面点击率的指标
- e、 g.填充缓存
- 如果您有一些代码片段总是在格式化对象时重复出现,那么templatetags很好。
- e、 g.活动选项卡/url面包屑
- 创建
可以进入User
UserManager(models.Manager)
- 实例的详细信息应该在
上models.Model
的详细信息可以在queryset
models.Manager中找到
- 您可能希望一次创建一个
,因此您可能认为它应该位于模型本身上,但在创建对象时,您可能不具备所有详细信息:用户
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()
尽可能使用表格
很多样板代码都可以使用elimi