我正在用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中实现这样一种方法有哪些好的实践呢?


当前回答

我基本上同意所选的答案(https://stackoverflow.com/a/12857584/871392),但想在制作查询部分添加选项。

可以为make过滤器查询等模型定义QuerySet类。之后,您可以为模型的管理器代理这个queryset类,就像内置的manager和queryset类一样。

尽管如此,如果您必须查询多个数据模型才能得到一个领域模型,那么对我来说,像之前建议的那样将其放在单独的模块中似乎更合理。

其他回答

首先,不要重复你自己。

然后,请注意不要过度设计,有时只是浪费时间,让人失去对重要事情的关注。时不时复习一下python的禅意。

看一看活跃的项目

more people = more need to organize properly the django repository they have a straightforward structure. the pip repository they have a straigtforward directory structure. the fabric repository is also a good one to look at. you can place all your models under yourapp/models/logicalgroup.py e.g User, Group and related models can go under yourapp/models/users.py e.g Poll, Question, Answer ... could go under yourapp/models/polls.py load what you need in __all__ inside of yourapp/models/__init__.py

更多关于MVC

model is your data this includes your actual data this also includes your session / cookie / cache / fs / index data user interacts with controller to manipulate the model this could be an API, or a view that saves/updates your data this can be tuned with request.GET / request.POST ...etc think paging or filtering too. the data updates the view the templates take the data and format it accordingly APIs even w/o templates are part of the view; e.g. tastypie or piston this should also account for the middleware.

利用中间件/模板标签

如果您需要为每个请求做一些工作,中间件是一种方法。 例如,添加时间戳 例如,更新页面点击率的指标 例如,填充缓存 如果你的代码片段总是在格式化对象时重复出现,那么templatetags是很好的选择。 例如,active TAB / url面包屑

利用模型管理器

创建用户可以进入用户管理器(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 /管理/命令/ createsuperuser.py 例如yourapp /管理/命令/ activateinbulk.py

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

Django.contrib.auth使用后端,就像db有后端一样…等等。 为您的业务逻辑添加一个设置(例如AUTHENTICATION_BACKENDS) 你可以使用django.contrib.auth.backends.RemoteUserBackend 你可以使用yourapp.backend .remote_api. remoteuserbackend 你可以使用yourapp.backend .memcache . 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的后端明显使用了委托设计模式。

关于接口边界的更多信息

Is the code you want to use really part of the models? -> yourapp.models Is the code part of business logic? -> yourapp.vendor Is the code part of generic tools / libs? -> yourapp.libs Is the code part of business logic libs? -> yourapp.libs.vendor or yourapp.vendor.libs Here is a good one: can you test your code independently? yes, good :) no, you may have an interface problem when there is clear separation, unittest should be a breeze with the use of mocking Is the separation logical? yes, good :) no, you may have trouble testing those logical concepts separately. Do you think you will need to refactor when you get 10x more code? yes, no good, no bueno, refactor could be a lot of work no, that's just awesome!

简而言之,你本可以

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

或者其他对你有帮助的东西;找到你需要的接口和界限会对你有帮助。

我基本上同意所选的答案(https://stackoverflow.com/a/12857584/871392),但想在制作查询部分添加选项。

可以为make过滤器查询等模型定义QuerySet类。之后,您可以为模型的管理器代理这个queryset类,就像内置的manager和queryset类一样。

尽管如此,如果您必须查询多个数据模型才能得到一个领域模型,那么对我来说,像之前建议的那样将其放在单独的模块中似乎更合理。

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

请注意,我通常将模型、视图和服务放在模块级别 根据项目的大小,可以进一步分开

Django使用了一种稍微修改过的MVC。在Django中没有“控制器”的概念。最接近的代理是一个“视图”,它容易引起MVC转换的混淆,因为在MVC中,视图更像是Django的“模板”。

In Django, a "model" is not merely a database abstraction. In some respects, it shares duty with the Django's "view" as the controller of MVC. It holds the entirety of behavior associated with an instance. If that instance needs to interact with an external API as part of it's behavior, then that's still model code. In fact, models aren't required to interact with the database at all, so you could conceivable have models that entirely exist as an interactive layer to an external API. It's a much more free concept of a "model".

我不得不同意你的看法。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)