Django中的业务逻辑和数据访问分离

609

我正在使用Django编写项目,我注意到80%的代码都在models.py文件中。这段代码让人感到困惑,时间久了我就不明白究竟发生了什么。

以下是让我感到困扰的问题:

  1. 我的模型层(原本应该只用于与数据库中的数据交互)也执行发送电子邮件、调用API连接其他服务等操作,这让我觉得很丑陋。
  2. 将业务逻辑放在视图中也是不可接受的,因为这样会变得难以控制。例如,在我的应用程序中至少有三种方法可以创建新的User实例,但从技术上讲,它们应该是统一的。
  3. 当我的模型中的方法和属性变得不确定时,我并不总是能够及时察觉到它们是否产生了副作用。

以下是一个简单的例子。起初,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])

我想要的是将代码中的实体分离:

  1. 数据库级别的实体,即数据库级别逻辑:我的应用程序存储什么数据?
  2. 应用程序级别的实体,即业务级别逻辑:我的应用程序做什么?

在Django中实现这种方法的好的实践是什么?


17
了解信号的含义。 - Konstant
1
你已经移除了标签,但是你可以使用DCI来实现系统功能和数据/领域模型的分离。 - Rune FS
5
您建议将所有业务逻辑都实现在信号回调中吗?不幸的是,我的应用程序并非所有部分都可以与数据库中的事件相关联。 - defuz
1
Rune FS,我尝试使用DCI,但似乎它对我的项目并不需要太多:上下文、角色定义为对象的mixin等。有没有更简单的方法来分离“做”和“是”?你能给一个最小的例子吗? - defuz
9个回答

778
看起来你在问数据模型和领域模型的区别 - 后者是你的最终用户所感知的业务逻辑和实体的位置,前者是你实际存储数据的位置。
此外,我把你问题的第三部分解释为:如何发现未能将这些模型分开的失败。
这是两个非常不同的概念,很难将它们分开。然而,有一些常见的模式和工具可以用于此目的。
关于领域模型
首先,你需要认识到你的领域模型实际上并不是关于数据的;它关乎“动作”和“问题”,例如“激活这个用户”、“停用这个用户”、“哪些用户当前处于激活状态?”以及“这个用户的名字是什么?”。从经典角度来看:它关乎查询和命令。
思考命令
让我们从你的例子中看看命令:“激活这个用户”和“停用这个用户”。命令的好处是它们可以通过小的给定-当-然场景轻松地表达:

假设有一个未激活的用户
管理员激活此用户时
那么该用户将变为活跃状态
并且还会向该用户发送确认电子邮件
以及系统日志中添加一条记录
(等等等等)

这种情景非常有用,可以看到单个命令如何影响基础架构的不同部分 - 在这种情况下是您的数据库(某种“活动”标志),您的邮件服务器,您的系统日志等。

这种情景也真正帮助您设置面向测试驱动开发的环境。

最后,思考命令确实有助于创建面向任务的应用程序。 您的用户会感激这点:-)

表达命令

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)

当然,这仅在数据实际上被表示在数据模型中时才有效。并非总是如此。在这种情况下,您可以考虑以下选项。
自定义标签和过滤器
第一个选择对于仅呈现查询很有用:自定义标签和模板过滤器。

template.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()方法,你可以覆盖Manager的基本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文档:信号

架构:领域驱动设计


23
看到一个回答将DDD融入到与Django相关的问题中很不错。尽管Django使用ActiveRecord进行持久化,但这并不意味着关注点分离应该被忽视。回答很棒。 - Scott Coates
7
在删除对象之前,如果我想验证已登录的用户是该对象的所有者,应该在视图(view)中还是在表单/服务(service)模块中进行检查? - Ivan
7
@Ivan:两者都需要。它必须以表单/服务模块的形式存在,因为这是您业务限制的一部分。同时,它也应该在视图中出现,因为您只应呈现用户实际可执行的操作。 - publysher
7
自定义管理器方法是实现查询的好方法:User.objects.inactive_users()。但我认为这里的代理模型示例会导致不正确的语义:u = InactiveUser.objects.all()[0]; u.active = True; u.save(),但 isinstance(u, InactiveUser) == True。此外,我还想提到,在许多情况下,维护查询模型的有效方法是使用数据库视图。 - Aryeh Leib Taurog
2
@adnanmuttaleb 这是正确的。请注意,答案本身只使用了“领域模型”一词。我包含DDD的链接并不是因为我的答案是DDD,而是因为那本书在帮助你思考领域模型方面做得非常好。 - publysher
显示剩余6条评论

186

我通常在视图和模型之间实现一个服务层。这就像你项目的 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()

注意,通常我会将模型、视图和服务拆分到模块级别,并根据项目规模进一步分离。


13
我喜欢这种总体方法,但据我理解,你的具体示例通常会作为管理器实现。 - arie
11
@arie 不一定。也许更好的例子是,对于一个网店服务,会包括生成购物车会话、异步任务如产品评分计算、创建和发送电子邮件等等。 - Hedde van der Heide
4
我也喜欢这种方法,因为我以前从事Java开发。我对Python还不熟悉,你会如何测试views.py?如果服务层需要模拟(例如,服务进行一些远程API调用),你会如何进行mocking? - Teimuraz
好的。services.py模块是数据访问对象模式的类似应用。 - ImportError

85

首先,不要重复自己

然后,请注意不要过度工程化,有时会浪费时间,让人失去关注重点。定期查看Python 之禅

查看活跃项目

  • 更多的人=更需要适当组织
  • django 存储库拥有直观的结构。
  • pip 存储库具有直观的目录结构。
  • fabric 存储库也是一个好的参考。

    • 您可以将所有模型放在 yourapp/models/logicalgroup.py 下面
  • 例如,UserGroup 和相关模型可以放在 yourapp/models/users.py 下面
  • 例如,PollQuestionAnswer 等可以放在 yourapp/models/polls.py 下面
  • yourapp/models/__init__.py 中使用 __all__ 加载所需内容

更多关于 MVC

  • 模型是您的数据
    • 这包括您的实际数据
    • 这还包括您的会话 / cookie / 缓存 / fs / 索引数据
  • 用户与控制器交互以操作模型
    • 这可能是一个 API,或者是保存/更新数据的视图
    • 可以使用 request.GET / request.POST 等对其进行调整
    • 也可以考虑分页筛选
  • 数据更新视图
    • 模板按照数据进行格式化
    • 即使没有模板,API 也是视图的一部分;例如 tastypiepiston
    • 这还应考虑中间件。

利用 中间件 / 模板标签

  • 如果你需要在每个请求时执行一些工作,可以使用中间件实现。
    • 例如:添加时间戳
    • 例如:更新页面点击量的指标
    • 例如:填充缓存
  • 如果你有一些代码片段经常用于格式化对象,则可以使用模板标签。
    • 例如:活动选项卡/网址面包屑

利用 模型管理器

  • 创建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文档相当不错。如果您有很多自定义需求(或者有时避免更高级用法中的循环导入错误),则将表单代码与模型代码分离可能是不错的选择。

尽可能使用管理命令

  • 例如,yourapp/management/commands/createsuperuser.py
  • 例如,yourapp/management/commands/activateinbulk.py

如果您有业务逻辑,可以将其分离出来

  • django.contrib.auth 使用后端,就像数据库有后端一样...等等
  • 为您的业务逻辑添加一个设置(例如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

关于设计模式的更多内容

  • 关于设计模式已经有一个很好的问题了。
  • 一部非常好的关于实用设计模式的视频
  • Django的后端明显使用了委托设计模式。

关于接口边界的更多内容

  • 你想使用的代码是否真的是模型的一部分?-> yourapp.models
  • 代码是否属于业务逻辑?-> yourapp.vendor
  • 代码是否属于通用工具/库?-> yourapp.libs
  • 代码是否属于业务逻辑库?-> yourapp.libs.vendor 或者 yourapp.vendor.libs
  • 这里有一个好问题:你能独立测试你的代码吗?
  • 分离是否具有逻辑性?
    • 是,太好了 :)
    • 否,你可能会在单独测试这些逻辑概念时遇到麻烦。
  • 你认为当你拥有10倍的代码时需要重构吗?
    • 是,不好,重构可能是一项艰巨的工作
    • 否,那真是太棒了!

简而言之,你可以有

  • 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
or anything else that helps you; finding the interfaces you need and the boundaries will help you.

31

Django采用了略微修改过的MVC模式。在Django中没有所谓的“控制器”概念,最接近的代理是“视图”,这容易引起MVC转换者的困惑,因为在MVC中,视图更像Django的“模板”。

在Django中,“模型”不仅仅是数据库抽象化。在某些方面,它与Django的“视图”共同承担了MVC中的控制器职责。它包含与实例关联的所有行为。如果该实例需要作为其行为的一部分与外部API进行交互,则这仍然是模型代码。实际上,模型根本不需要与数据库交互,因此您可以将模型完全存在于与外部API的交互层中。这是一个更自由的“模型”概念。


对于那些感兴趣的人,Django文档中有一个关于MVC的常见问题解答 - djvg

11
在Django中,MVC模型与其他框架中使用的经典MVC模型不同,正如Chris Pratt所说,我认为这样做的主要原因是避免过于严格的应用程序结构,就像其他MVC框架(如CakePHP)中出现的情况一样。
在Django中,MVC是这样实现的:
视图层分成两个部分。视图只应该用来处理HTTP请求,并对它们进行响应。视图与应用程序的其余部分进行通信(表单、模型表单、自定义类,或者在简单情况下直接与模型进行通信)。 为了创建界面,我们使用模板。对于Django来说,模板类似于字符串,它将上下文映射到它们,并且应用程序(当视图请求时)向视图传递此上下文。
模型层提供了封装、抽象、验证、智能化,并使您的数据面向对象化(有人说某天DBMS也会这样)。这并不意味着您应该制作巨大的models.py文件(实际上,非常好的建议是将您的模型拆分为不同的文件,将它们放入名为“models”的文件夹中,在此文件夹中制作一个“__init__.py”文件,其中导入所有模型,最后使用models.Model类的属性“app_label”)。模型应该使您从操作数据中抽象出来,这将使您的应用程序更简单。如果需要,您还应该创建外部类,比如为您的模型创建“工具”。您也可以在模型中使用继承,将模型的Meta类的“abstract”属性设置为“True”。
剩下的在哪里?嗯,小型Web应用程序通常是一种数据接口,在某些小程序情况下,仅使用视图来查询或插入数据就足够了。更常见的情况是使用表单或模型表单,它们实际上是“控制器”。这不过是一个常见问题的实际解决方案,而且非常快速。这就是网站所使用的。如果表单不够用,那么你应该创建自己的类来实现功能,一个非常好的例子是管理应用程序: 你可以阅读 ModelAdmin 代码,它实际上充当控制器。没有标准结构,我建议您检查现有的 Django 应用程序,这取决于每个案例。这就是 Django 开发人员的意图,你可以添加 xml 解析器类、API 连接器类、添加 Celery 执行任务、twisted 用于反应堆式应用程序、仅使用 ORM、制作 Web 服务、修改管理应用程序等等... 这是你负责编写高质量代码,是否遵守 MVC 哲学、模块化和创建自己的抽象层次。它非常灵活。
我的建议:尽可能多地阅读代码,有很多 Django 应用程序,但不要对它们过度认真。每种情况都不同,模式和理论有所帮助,但并非总是如此,这是一门不精确的科学,django 只是提供了一些好的工具,你可以用来缓解一些痛点(如管理界面,Web 表单验证,i18n、观察者模式实现,前面提到的所有其他内容),但好的设计来自于经验丰富的设计师。
提示:使用身份验证应用程序中的 'User' 类(来自标准 Django),你可以制作用户配置文件,或者至少阅读其代码,它将对你的案例有用。

1
一个老问题,但我仍然想提供我的解决方案。它基于接受模型对象也需要一些额外功能的事实,而将其放置在 models.py 中会很尴尬。重业务逻辑可以根据个人喜好单独编写,但至少我希望模型能够处理与自身相关的所有内容。这个解决方案还支持那些喜欢将所有逻辑都放在模型本身中的人。
因此,我设计了一个黑科技,使我能够将逻辑从模型定义中分离出来,同时仍然获得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 ...

app/logic/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实际上是User模型。但既然这显然是一个hack,我很乐意接受始终为self参数指定类型的小麻烦。

实际上,我认为这是一种易于使用的方法。但我会将最终模型移动到另一个文件中,而不是在models.py中继承。这就像service.py,其中冲突了userlogic+model。 - Maks

1

我同意你的观点。在Django中有很多可能性,但最好的起点是查看Django的设计哲学

  1. 从模型属性调用API并不理想,似乎在视图中这样做更有意义,并可能创建一个服务层以保持代码DRY(Don't Repeat Yourself)。如果对API的调用是非阻塞的且调用是昂贵的,则将请求发送到服务工作者(从队列中消费的工作者)可能是有意义的。

  2. 根据Django的设计哲学,模型应该封装“对象”的每个方面。因此,与该对象相关的所有业务逻辑都应该存在于其中:

包含所有相关的领域逻辑

模型应该封装一个“对象”的每个方面,遵循Martin Fowler的Active Record设计模式。

  1. 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)
    

0

1
你应该添加一些解释。 - m02ph3u5

0

我大部分同意所选答案(https://dev59.com/ymcs5IYBdhLWcg3wym4h#12857584),但想在“制作查询”部分添加选项。

可以为模型定义QuerySet类以进行过滤查询等操作。之后,您可以代理此QuerySet类以用于模型的管理器,就像内置的Manager和QuerySet类所做的那样。

尽管如此,如果您必须查询多个数据模型以获取一个域模型,则我认为将其放在单独的模块中更为合理,就像之前建议的那样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接