关于python:Django中业务逻辑和数据访问的分离

Separation of business logic and data access in django

我正在Django编写一个项目,我看到80%的代码都在models.py文件中。这段代码很混乱,过了一段时间,我就不明白到底发生了什么。

以下是困扰我的问题:

  • 我觉得我的模特水平(应该是只负责处理数据库中的数据)发送电子邮件、在API上行走到其他服务等。
  • 此外,我发现将业务逻辑放在视图中是不可接受的,因为这样就很难控制了。例如,在我的应用程序至少有三种方法可以创建新的User的实例,但从技术上讲,它应该统一创建它们。
  • 我并不总是注意到方法和我的模型的属性变得不确定,当它们发展时副作用。
  • 下面是一个简单的例子。起初,User模型是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    class User(db.Models):

        def get_present_name(self):
            return self.name or 'Anonymous'

        def activate(self):
            self.status = 'activated'
            self.save()

    随着时间的推移,它变成了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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实施这种方法的良好实践是什么?


    似乎您在询问数据模型和域模型之间的区别——后者是您可以找到最终用户所感知的业务逻辑和实体的地方,前者是您实际存储数据的地方。好的。

    此外,我将您问题的第三部分解释为:如何注意到未能将这些模型分开。好的。

    这是两个非常不同的概念,总是很难将它们分开。但是,有一些通用的模式和工具可以用于此目的。好的。关于域模型

    您需要认识到的第一件事是,您的域模型实际上并不涉及数据;它涉及诸如"激活此用户"、"停用此用户"、"当前激活了哪些用户"等操作和问题。,以及"此用户的名称是什么?".在经典术语中:它是关于查询和命令的。好的。在命令中思考

    让我们从示例中的命令开始:"激活此用户"和"停用此用户"。关于命令的好处在于,它们可以很容易地用小的给定值来表示,而在这种情况下:好的。

    given an inactive user
    when the admin activates this user
    then the user becomes active
    and a confirmation e-mail is sent to the user
    and an entry is added to the system log
    (etc. etc.)

    Ok.

    这种场景对于了解单个命令如何影响基础结构的不同部分非常有用——在这种情况下,您的数据库(某种"活动"标志)、邮件服务器、系统日志等。好的。

    这样的场景还可以帮助您建立一个测试驱动的开发环境。好的。

    最后,使用命令进行思考确实有助于创建面向任务的应用程序。您的用户将对此表示感谢:—)好的。表达命令

    Django提供了两种简单的命令表达方式;它们都是有效的选项,混合使用这两种方法并不少见。好的。服务层

    @hedde已经描述了服务模块。在这里,您定义一个单独的模块,每个命令都表示为一个函数。好的。

    服务Py好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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表单。我更喜欢这种方法,因为它结合了多个密切相关的方面:好的。

    • 执行命令(它做什么?)
    • 命令参数的验证(它能做到吗?)
    • 命令的表示(我如何才能这样做?)

    表单好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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对象:好的。

    1
    User.objects.filter(active=True)

    当然,只有当数据在数据模型中实际表示时,这才有效。情况并非总是如此。在这些情况下,您可以考虑下面的选项。好的。自定义标记和筛选器

    第一种选择对于仅具有表示性的查询很有用:自定义标记和模板过滤器。好的。

    模板语言好的。

    1
    Welcome, {{ user|friendly_name }}

    模板_tags.py好的。

    1
    2
    3
    @register.filter
    def friendly_name(user):
        return remote_api.get_cached_name(user.id)

    查询方法

    如果您的查询不仅仅是表示性的,您可以将查询添加到services.py(如果您正在使用它),或者引入querys.py模块:好的。

    质询好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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()方法来重写管理器的基本查询集。好的。

    模特儿好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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

    查询模型

    对于本质上很复杂但经常执行的查询,存在查询模型的可能性。查询模型是一种非规范化形式,其中单个查询的相关数据存储在单独的模型中。当然,技巧是使非规范化模型与主模型保持同步。只有当更改完全在您的控制之下时,才能使用查询模型。好的。

    模特儿好的。

    1
    2
    3
    class InactiveUserDistribution(models.Model):
        country = CharField(max_length=200)
        inactive_user_count = IntegerField(default=0)

    第一个选项是在命令中更新这些模型。如果这些模型仅由一个或两个命令更改,那么这非常有用。好的。

    表单好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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()

    更好的选择是使用自定义信号。这些信号当然是由你的命令发出的。信号的优点是可以使多个查询模型与原始模型保持同步。此外,可以使用芹菜或类似的框架将信号处理卸载到后台任务。好的。

    信号:Py好的。

    1
    2
    user_activated = Signal(providing_args = ['user'])
    user_deactivated = Signal(providing_args = ['user'])

    表单好的。

    1
    2
    3
    4
    5
    6
    class ActivateUserForm(forms.Form):
        # see above

        def execute(self):
            # see above
            user_activated.send_robust(sender=self, user=user)

    模特儿好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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),例如:

    模特儿

    1
    2
    3
    4
    5
    6
    class Book:
       author = models.ForeignKey(User)
       title = models.CharField(max_length=125)

       class Meta:
           app_label ="library"

    服务Py

    1
    2
    3
    4
    5
    6
    7
    from library.models import Book

    def get_books(limit=None, **filters):
       """ simple service function for retrieving books can be widely extended"""
        if limit:
            return Book.objects.filter(**filters)[:limit]
        return Book.objects.filter(**filters)

    VIEW

    1
    2
    3
    4
    5
    from library.services import get_books

    class BookListView(ListView):
       """ simple view, e.g. implement a _build and _apply filters function"""
        queryset = get_books()

    Mind you, I usually take models, views and services to module level and
    separate even further depending on the project's size


    首先,不要重复你自己。好的。

    那么,请注意不要过度设计,有时这只是浪费时间,并使某人失去对重要的东西的关注。不时地回顾一下python的禅。好的。

    查看活动项目好的。

    • 更多人=更多人需要正确组织
    • Django存储库有一个简单的结构。
    • PIP存储库有一个strightforward目录结构。
    • 结构存储库也是一个很好的库。好的。

      • 您可以将您的所有模型放在yourapp/models/logicalgroup.py下。
    • UserGroup及相关型号可归入yourapp/models/users.py项下。
    • PollQuestionAnswer…可能在yourapp/models/polls.py之下
    • yourapp/models/__init__.py的内部加载您在__all__中需要的内容。

    更多关于MVC好的。

    • 模型是你的数据
      • 这包括您的实际数据
      • 这还包括会话/cookie/cache/fs/index数据
    • 用户与控制器交互操作模型
      • 这可以是一个API,也可以是一个保存/更新数据的视图。
      • 这可以通过request.GETrequest.POST等进行调整。
      • 也可以考虑分页或过滤。
    • 数据更新视图
      • 模板获取数据并相应地格式化它。
      • API,即使是w/o模板也是视图的一部分;例如,tastypiepiston
      • 这也应该解释中间件。

    利用中间件/模板标记好的。

    • 如果您需要为每个请求做一些工作,中间件是一种方法。
      • 例如,添加时间戳
      • 例如,更新有关页面点击率的指标
      • 例如,填充缓存
    • 如果您有格式化对象时总是重复出现的代码片段,则templateTags很好。
      • 例如,活动选项卡/URL面包屑

    利用模型管理器好的。

    • 创建User可以在UserManager(models.Manager)中进行。
    • 实例的详细信息应该放在models.Model上。
    • 有关queryset的详细信息可以放在models.Manager中。
    • 您可能希望一次创建一个User,因此您可能认为它应该活在模型本身上,但在创建对象时,您可能不知道所有细节:

    例子:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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
    • 将困难的业务逻辑委托给后端
    • 确保在输入/输出上正确设置期望值。
    • 更改业务逻辑和更改设置一样简单:)

    后端示例:好的。

    1
    2
    3
    4
    5
    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'

    可能成为:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    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
    • 代码是业务逻辑的一部分吗?->江户十一〔一〕号
    • 代码是通用工具/libs的一部分吗?->埃多克斯1〔2〕
    • 代码是业务逻辑库的一部分吗?->yourapp.libs.vendoryourapp.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

    或者其他任何有助于您的东西;找到您需要的接口和边界将帮助您。好的。好啊。


    Django采用了一种稍加修改的MVC。Django没有"控制器"的概念。最近的代理是一个"视图",这往往会导致与MVC转换混淆,因为在MVC中,视图更像Django的"模板"。

    在Django中,"模型"不仅仅是数据库抽象。在某些方面,它与Django作为MVC控制者的"观点"共享职责。它保存与实例关联的全部行为。如果该实例需要与外部API交互作为其行为的一部分,那么这仍然是模型代码。事实上,模型根本不需要与数据库交互,因此您可以想象,拥有完全作为外部API交互层存在的模型。这是一个更自由的概念"模型"。


    在Django中,MVC结构正如Chris Pratt所说,不同于其他框架中使用的经典MVC模型,我认为这样做的主要原因是避免了过于严格的应用程序结构,就像在Cakephp等其他MVC框架中一样。

    在Django,MVC的实施方式如下:

    视图层分为两部分。视图应该只用于管理HTTP请求,它们被调用并响应。视图与应用程序的其余部分通信(窗体、模型窗体、自定义类,在简单情况下直接与模型通信)。为了创建接口,我们使用模板。模板是类似于django的字符串,它将上下文映射到模板中,并且该上下文由应用程序(当视图请求时)与视图通信。

    模型层提供了封装、抽象、验证、智能,并使您的数据面向对象(他们说有一天DBMS也会这样做)。这并不意味着你应该制作巨大的models.py文件(事实上,一个非常好的建议是将你的模型分割成不同的文件,将它们放在一个名为"models"的文件夹中,将一个"uuu init_uuuu.py"文件放在这个文件夹中,在这个文件夹中你导入所有模型,最后使用models.model类的属性"app_label"。模型应该从使用数据的操作中抽象出您,它将使您的应用程序更简单。如果需要,还应该为模型创建外部类,如"工具"。还可以在模型中使用Heritage,将模型的元类的"abstract"属性设置为"true"。

    其余的在哪里?嗯,小型Web应用程序通常是一种数据接口,在某些小型程序中,使用视图查询或插入数据就足够了。更常见的情况是使用窗体或模型窗体,它们实际上是"控制器"。这不是一个解决常见问题的实用方法,也是一个非常快速的方法。这是网站用来做的。

    如果表单不适合您,那么您应该创建自己的类来实现这一点,管理应用程序就是一个很好的例子:您可以读取modelamin代码,这实际上是一个控制器。没有一个标准的结构,我建议您检查现有的django应用程序,这取决于每种情况。这就是Django开发人员的意图,您可以添加XML解析器类、API连接器类、为执行任务添加芹菜、为基于reactor的应用程序扭曲、仅使用ORM、生成Web服务、修改管理应用程序等等…你的责任是编写好质量的代码,是否尊重MVC的理念,使它基于模块,并创建你自己的抽象层。它非常灵活。

    我的建议:尽可能多地阅读代码,周围有很多django应用程序,但不要太认真地对待它们。每种情况都是不同的,模式和理论有助于,但并非总是如此,这是一个不精确的科学,Django只是为您提供了好的工具,您可以使用这些工具来减轻一些痛苦(如管理界面、Web表单验证、i18n、观察者模式实现、前面提到的所有以及其他),但好的设计来自经验丰富的设计师。

    ps.:使用auth应用程序中的'user'类(来自标准django),您可以创建例如用户配置文件,或者至少读取其代码,它将对您的案例有用。


    我必须同意你的意见。Django有很多可能性,但最好的开始是回顾Django的设计理念。

  • 从模型属性调用API并不理想,在视图中这样做似乎更有意义,并且可能创建一个服务层来保持事物的干燥。如果对API的调用是非阻塞的,并且调用很昂贵,那么将请求发送给服务工作者(从队列中消费的工作者)可能是有意义的。

  • 根据Django的设计哲学模型,它封装了"对象"的各个方面。因此,与该对象相关的所有业务逻辑都应该存在于该对象中:

  • Include all relevant domain logic

    Models should encapsulate every aspect of an"object," following Martin Fowler’s Active Record design pattern.

  • 您描述的副作用很明显,这里的逻辑可以更好地分解为查询集和管理器。下面是一个例子:

    模特儿

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    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()

    行政管理部门

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 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)

  • 我基本上同意所选的答案(https://stackoverflow.com/a/12857584/871392),但希望在"生成查询"部分添加选项。

    可以为模型定义queryset类,以便进行筛选查询和打开子项。之后,您可以为模型的管理器代理这个查询集类,就像内置管理器和查询集类一样。

    尽管如此,如果您必须查询多个数据模型才能获得一个域模型,那么将其放入前面建议的单独模块中似乎更为合理。


    一个老问题,但我还是想提出我的解决方案。这是基于接受模型对象也需要一些额外的功能,而将其放在models.py中则很难。繁重的业务逻辑可以根据个人喜好单独编写,但我至少喜欢这个模型做与自身相关的所有事情。这个解决方案还支持那些喜欢将所有逻辑放在模型中的人。

    因此,我设计了一个黑客程序,它允许我将逻辑与模型定义分开,并且仍然从我的IDE中得到所有提示。

    优势应该是显而易见的,但下面列出了一些我观察到的:

    • DB定义保持不变-没有附加逻辑"垃圾"。
    • 模型相关逻辑都整齐地放在一个地方
    • 所有服务(窗体、REST、视图)都有一个逻辑访问点。
    • 最重要的是:当我意识到models.py变得过于混乱,不得不将逻辑分离开来时,我不必重写任何代码。分离是平滑和迭代的:我可以在一个时间或者整个类或者整个模型上做一个函数。

    我在Python3.4及更高版本和django1.8及更高版本中使用过这个。

    App/MealthsPy

    1
    2
    3
    4
    5
    6
    ....
    from app.logic.user import UserLogic

    class User(models.Model, UserLogic):
        field1 = models.AnyField(....)
        ... field definitions ...

    应用程序/逻辑/用户.py

    1
    2
    3
    4
    5
    6
    7
    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)认识到用户逻辑实际上是用户模型。但由于这显然是一个黑客行为,我很高兴接受总是为self参数指定类型这一小麻烦。


    Django的设计目的是方便地用于交付网页。如果您对此不满意,也许您应该使用另一种解决方案。

    我正在编写模型上的根或公共操作(具有相同的接口)以及模型控制器上的其他操作。如果我需要其他模型的操作,我就导入它的控制器。

    这种方法对我和我的应用程序的复杂性来说已经足够了。

    Hedde的响应就是一个例子,展示了Django和Python本身的灵活性。

    不管怎样,这个问题很有趣!