django-model-utils
一个普通例子:
todos
=
Todo.objects.
filter
(
owner
=
request.user
).
filter
(
is_done
=
False
).
filter
(
priority
=
1
)
弊端:
首先,代码冗长,正式的项目中,将会更加复杂。
其次,泄露实现细节。比如代码中的is_done是BooleanField,如果改变了他的类型,代码就不能用了。
或者就是,意图不清晰,很难理解。
最后,使用中会有重复。
Django 有两个关系密切的与表级别操作相关的构图:managers 和 querysets
manager(django.db.models.manager.Manager的一个实例)被描述成 “通过查询数据库提供给Django的插件”。Manager是表级别功能的通往ORM大门。每一个model都有一个默认的manager,叫做objects。
Quesyset (django.db.models.query.QuerySet) 是“数据库中objects的集合”。本质上是一个SELECT查询,也可以使用过滤,排序等(filtered,ordered),来限制或者修改查询到的数据。用来 创建或操纵 django.db.models.sql.query.Query实例,然后通过数据库后端在真正的SQL中查询。
Manager接口就是个谎言。
QuerySet方法是可链接的。每一次调用QuerySet的方法(如:filter)都会返回一个复制的queryset等待下一次的调用。这也是Django ORM 流畅之美的一部分。
但是当Model.objects 是一个 Manager时,就出现问题了。我们需要调用objects作为开始,然后链接到结果的QuerySet上去。
那么Django又是如何解决呢?
接口的谎言由此暴露,所有的QuerySet 方法基于Manager。
更多:http://www.oschina.net/translate/higher-level-query-api-django-orm
models.py:
from model_utils.managers import PassThroughManager
class NewsQuerySet(models.query.QuerySet):
def display(self):
return self.filter(status=1).order_by('-create_time')
def exece(self, question_id):
return self.exclude(pk=question_id).display()
class News(models.Model):
STATUS = (
(0, u'不展示'),
(1, u'展示'),
)
title = models.CharField(max_length=20)
author = models.CharField(max_length=20)
img = models.ImageField(upload_to='upload/news/%Y/%m/%d', blank=True, null=True)
content = models.TextField(blank=True, null=True)
create_time = models.DateTimeField(auto_now_add=True)
source = models.CharField(max_length=20)
status = models.IntegerField(default=1, null=True, blank=True, choices=STATUS)
objects = PassThroughManager.for_queryset_class(NewsQuerySet)()
views.py:
原先:
q = News.objects.all().exclude(id=news_id).filter(status=1).order_by('-create_time')
现在:
q = News.objects.exece(news_id)
在视图和其他高级应用中使用源生的ORM查询代码不是很好的主意。而是用django-model-utils中的PassThroughManager将我们新加的自定义QuerySet API加进你的模型中,这能给你以下好处:
啰嗦代码少,并且更健壮。
增加DRY,增强抽象级别。
将所属的业务逻辑推送至对应的域模型层。
2015.1.6
StatusField
from model_utils.fields import StatusField from model_utils import Choices class Article(models.Model): STATUS = Choices('draft', 'published') # ... status = StatusField() from model_utils.fields import StatusField from model_utils import Choices class Article(models.Model): ANOTHER_CHOICES = Choices('draft', 'published') # ... another_field = StatusField(choices_name='ANOTHER_CHOICES')
MonitorField
from model_utils.fields import MonitorField, StatusField class Article(models.Model): STATUS = Choices('draft', 'published') status = StatusField() status_changed = MonitorField(monitor='status') from model_utils.fields import MonitorField, StatusField class Article(models.Model): STATUS = Choices('draft', 'published') status = StatusField() published_at = MonitorField(monitor='status', when=['published'])
SplitField
from django.db import models from model_utils.fields import SplitField class Article(models.Model): title = models.CharField(max_length=100) body = SplitField() content: The full field contents. excerpt: The excerpt of content (read-only). has_more: True if the excerpt and content are different, False otherwise. >>> a = Article.objects.all()[0] >>> a.body.content u'some text\n\n<!-- split -->\n\nmore text' >>> a.body.excerpt u'some text\n' >>> unicode(a.body) u'some text\n\n<!-- split -->\n\nmore text' By default, SplitField looks for the marker <!-- split --> alone on a line and takes everything before that marker as the excerpt. This marker can be customized by setting the SPLIT_MARKER setting. If no marker is found in the content, the first two paragraphs (where paragraphs are blocks of text separated by a blank line) are taken to be the excerpt. This number can be customized by setting the SPLIT_DEFAULT_PARAGRAPHS setting.
Choices
from model_utils import Choices GENERIC_CHOICES = Choices((0, 'draft', _('draft')), (1, 'published', _('published'))) class Article(models.Model): STATUS = GENERIC_CHOICES + [(2, 'featured', _('featured'))] status = models.IntegerField(choices=STATUS, default=STATUS.draft)
Field Tracker
from django.db import models from model_utils import FieldTracker class Post(models.Model): title = models.CharField(max_length=100) body = models.TextField() tracker = FieldTracker() >>> a = Post.objects.create(title='First Post') >>> a.title = 'Welcome' >>> a.tracker.previous('title') >>> a = Post.objects.create(title='First Post') >>> a.title = 'Welcome' >>> a.tracker.has_changed('title') True >>> a.tracker.has_changed('body') False >>> a = Post.objects.create(title='First Post') >>> a.title = 'Welcome' >>> a.body = 'First post!' >>> a.tracker.changed() {'title': 'First Post', 'body': ''}
from django.db import models from model_utils import FieldTracker class Post(models.Model): title = models.CharField(max_length=100) body = models.TextField() title_tracker = FieldTracker(fields=['title']) >>> a = Post.objects.create(title='First Post') >>> a.body = 'First post!' >>> a.title_tracker.changed() {'title': None}
https://django-model-utils.readthedocs.org/en/latest/models.html