我正在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中应用的这种方法有哪些好的做法?
答案 0 :(得分:127)
我通常在视图和模型之间实现服务层。这就像您的项目的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 """
if limit:
return Book.objects.filter(**filters)[:limit]
return Book.objects.filter(**filters)
<强> 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()
请注意,我通常会将模型,视图和服务提供给模块级别和 根据项目的规模进一步分离
答案 1 :(得分:60)
然后,请注意不要过度工程,有时这只是浪费时间,让人失去对重要事项的关注。不时查看zen of python。
查看有效项目
fabric repository也是一个很好的选择。
yourapp/models/logicalgroup.py
User
,Group
和相关模型可以归yourapp/models/users.py
Poll
,Question
,Answer
...可以归yourapp/models/polls.py
__all__
yourapp/models/__init__.py
内加载您需要的内容
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
非常好。如果您有大量的自定义(或者有时为了更高级的用途而避免循环导入错误),那么从模型代码中分离表单代码可能会很好。
尽可能使用management commands
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
如果您有业务逻辑,则可以将其分开
django.contrib.auth
uses backends,就像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
或其他任何可以帮助你的事情;找到您需要的接口和边界将对您有所帮助。
答案 2 :(得分:24)
Django采用了一种稍微修改过的MVC。 Django中没有“控制器”的概念。最接近的代理是“视图”,这往往会导致与MVC转换混淆,因为在MVC中,视图更像是Django的“模板”。
在Django中,“模型”不仅仅是数据库抽象。在某些方面,它与Django作为MVC控制者的“观点”共享义务。它包含与实例关联的整个行为。如果该实例需要与外部API交互作为其行为的一部分,那么这仍然是模型代码。事实上,模型根本不需要与数据库交互,因此您可以想象将模型完全作为外部API的交互层存在。这是一个更“自由”的“模型”概念。
答案 3 :(得分:5)
在Django中,MVC结构就像Chris Pratt所说的那样,与其他框架中使用的经典MVC模型不同,我认为这样做的主要原因是避免了过于严格的应用程序结构,就像CakePHP等其他MVC框架一样。 / p>
在Django中,MVC以下列方式实现:
视图图层分为两部分。视图应仅用于管理HTTP请求,它们被调用并响应它们。视图与应用程序的其余部分(表单,模型,自定义类,简单情况下直接与模型)进行通信。 要创建界面,我们使用模板。模板与Django类似,它将上下文映射到它们中,并且应用程序将此上下文传递给视图(当视图请求时)。
模型层提供封装,抽象,验证,智能并使您的数据面向对象(他们有朝一日会说DBMS也会)。这并不意味着你应该制作巨大的models.py文件(事实上,一个非常好的建议是将模型分成不同的文件,将它们放入一个名为'models'的文件夹中,将'__init__.py'文件放入此文件中导入所有模型的文件夹,最后使用models.Model类的属性'app_label'。模型应该使您从数据操作中抽象出来,它将使您的应用程序更简单。如果需要,您还应该为模型创建外部类,例如“工具”。您还可以在模型中使用遗产,将模型的Meta类的“abstract”属性设置为“True”。
其余的在哪里?好吧,小型Web应用程序通常是一种数据接口,在一些使用视图查询或插入数据的小程序案例就足够了。更常见的情况是使用Forms或ModelForms,它们实际上是“控制器”。这不是解决常见问题的实际解决方案,而是一个非常快速的问题。这是网站用来做的事情。
如果Forms不适合你,那么你应该创建自己的类来实现魔术,这是一个很好的例子就是管理应用程序:你可以读取ModelAmin代码,这实际上是作为一个控制器。没有标准的结构,我建议你检查现有的Django应用程序,它取决于每种情况。这是Django开发人员的意图,您可以添加xml解析器类,API连接器类,添加Celery以执行任务,为基于反应器的应用程序加以扭曲,仅使用ORM,创建Web服务,修改管理应用程序等等。 ..你有责任制作高质量的代码,尊重MVC理念与否,使其基于模块并创建自己的抽象层。它非常灵活。
我的建议:尽可能多地阅读代码,有很多django应用程序,但不要认真对待它们。每种情况都不同,模式和理论有所帮助,但并非总是如此,这是一种不精确的科学,django只是为您提供了很好的工具,可以用来解决一些痛苦(如管理界面,Web表单验证,i18n,观察者模式实现,所有前面提到的和其他的),但好的设计来自经验丰富的设计师。
PS:使用auth应用程序中的'User'类(来自标准django),你可以制作例如用户配置文件,或者至少阅读它的代码,它对你的情况很有用。
答案 4 :(得分:0)
我大多同意所选答案(https://stackoverflow.com/a/12857584/871392),但想在“制作查询”部分添加选项。
可以为make过滤器查询和son on的模型定义QuerySet类。之后,您可以为模型管理器代理此查询集类,就像内置管理器和QuerySet类一样。
虽然,如果你不得不查询几个数据模型来获得一个域模型,那么把它放在像之前建议的单独模块中似乎更合理。
答案 5 :(得分:0)
我必须同意你的看法。 django有很多可能性,但是最好的起点是查看Django's design philosophy。
从模型属性调用API是不理想的,在视图中执行类似的操作并可能创建一个服务层以保持状态干燥似乎更有意义。如果对API的调用是非阻塞的,并且调用成本很高,则可以将请求发送给服务工作者(从队列中消耗资源的工作者)。
按照Django的设计理念,模型封装了“对象”的各个方面。因此,与该对象相关的所有业务逻辑都应存在于此:
包括所有相关的域逻辑
模型应遵循Martin Fowler的Active Record设计模式来封装“对象”的各个方面。
您所描述的副作用是显而易见的,这里的逻辑可以更好地分解为Queryset和管理器。这是一个示例:
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)
答案 6 :(得分:0)
关于优缺点的各种选择的最全面的文章:
来源: https://sunscrapers.com/blog/where-to-put-business-logic-django/
答案 7 :(得分:-1)
一个老问题,但无论如何我想提供我的解决方案。它基于接受模型对象也需要一些额外的功能,而将它放在 models.py 中是不方便的。繁重的商业逻辑可以根据个人品味单独编写,但我至少喜欢模型来做与自身相关的所有事情。该解决方案还支持那些喜欢将所有逻辑放在模型中的人。
因此,我设计了 hack ,它允许我将逻辑与模型定义分开,并且仍然可以从我的IDE中获得所有暗示。
优点应该是显而易见的,但这列出了我观察到的一些优点:
我一直在使用Python 3.4及更高版本和Django 1.8及更高版本。
应用程序/ models.py
....
from app.logic.user import UserLogic
class User(models.Model, UserLogic):
field1 = models.AnyField(....)
... field definitions ...
应用程序/逻辑/ 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实际上是用户模型。但由于这显然是一个黑客攻击,我很高兴接受总是为self
参数指定类型的麻烦。
答案 8 :(得分:-3)
Django旨在轻松用于提供网页。如果您对此不满意,也许您应该使用另一种解决方案。
我正在模型的根目录或常用操作(具有相同的接口)和模型的控制器上的其他操作。如果我需要其他模型的操作,我导入它的控制器。
这种方法对我和我的应用程序的复杂性来说已经足够了。
Hedde的回应是一个展示django和python本身灵活性的例子。
无论如何都是非常有趣的问题!