찾다
백엔드 개발파이썬 튜토리얼利用Python的Django框架中的ORM建立查询API

 摘要

在这篇文章里,我将以反模式的角度来直接讨论Django的低级ORM查询方法的使用。作为一种替代方式,我们需要在包含业务逻辑的模型层建立与特定领域相关的查询API,这些在Django中做起来不是非常容易,但通过深入地了解ORM的内容原理,我将告诉你一些简捷的方式来达到这个目的。

概览

当编写Django应用程序时,我们已经习惯通过添加方法到模型里以此达到封装业务逻辑并隐藏实现细节。这种方法看起来是非常的自然,而且实际上它也用在Django的内建应用中。
 

>>> from django.contrib.auth.models import User
>>> user = User.objects.get(pk=5)
>>> user.set_password('super-sekrit')
>>> user.save()

这里的set_password就是一个定义在django.contrib.auth.models.User模型中的方法,它隐藏了对密码进行哈希操作的具体实现。相应的代码看起来应该是这样:
 

from django.contrib.auth.hashers import make_password
 
class User(models.Model):
 
  # fields go here..
 
  def set_password(self, raw_password):
    self.password = make_password(raw_password)


我们正在使用Django,建立一个特定领域的顶部通用接口,低等级的ORM工具。在此基础上,增加抽象等级,减少交互代码。这样做的好处是使代码更具可读性、重用性和健壮性。

我们已经在单独的例子中这样做了,下面将会把它用在获取数据库信息的例子中。

为了描述这个方法,我们使用了一个简单的app(todo list)来说明。

注意:这是一个例子。因为很难用少量的代码展示一个真实的例子。不要过多的关心todo list继承他自己,而要把重点放在如何让这个方法运行。
下面就是models.py文件:
 

from django.db import models
 
PRIORITY_CHOICES = [(1, 'High'), (2, 'Low')]
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  is_done = models.BooleanField(default=False)
  owner = models.ForeignKey('auth.User')
  priority = models.IntegerField(choices=PRIORITY_CHOICES, default=1


想像一下,我们将要传递这些数据,建立一个view,来为当前用户展示不完整的,高优先级的 Todos。这里是代码: 
 

def dashboard(request):
 
  todos = Todo.objects.filter(
    owner=request.user
  ).filter(
    is_done=False
  ).filter(
    priority=1
  )
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

注意:这里可以写成request.user.todo_set.filter(is_done=False, priority=1)。但是这里只是一个实验。

为什么这样写不好呢?

首先,代码冗长。七行代码才能完成,正式的项目中,将会更加复杂。

其次,泄露实现细节。比如代码中的is_done是BooleanField,如果改变了他的类型,代码就不能用了。

然后就是,意图不清晰,很难理解。

最后,使用中会有重复。例:你需要写一行命令,通过cron,每周发送给所有用户一个todo list,这时候你就需要复制-粘贴着七行代码。这不符合DRY(do not repeat yourself)


让我们大胆的猜测一下:直接使用低等级的ORM代码是反模式的。
如何改进呢?

使用 Managers 和 QuerySets
首先,让我们先了解一下概念。

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中查询。

啊?你还不明白?

随着你慢慢深入的了解ORM,你就会明白Manager和QuerySet之间的区别了。


人们会被所熟知的Manager接口搞糊涂,因为他并不是看上去那样。

Manager接口就是个谎言。

QuerySet方法是可链接的。每一次调用QuerySet的方法(如:filter)都会返回一个复制的queryset等待下一次的调用。这也是Django ORM 流畅之美的一部分。

但是当Model.objects 是一个 Manager时,就出现问题了。我们需要调用objects作为开始,然后链接到结果的QuerySet上去。

那么Django又是如何解决呢?

接口的谎言由此暴露,所有的QuerySet 方法基于Manager。在这个方法中,通过self.get_query_set()的代理,重新创建一个

QuerySet。
 
class Manager(object):
 
  # SNIP some housekeeping stuff..
 
  def get_query_set(self):
    return QuerySet(self.model, using=self._db)
 
  def all(self):
    return self.get_query_set()
 
  def count(self):
    return self.get_query_set().count()
 
  def filter(self, *args, **kwargs):
    return self.get_query_set().filter(*args, **kwargs)
 
  # and so on for 100+ lines...

更多代码,请参照Manager的资源文件。

让我们立刻回到todo list ,解决query接口的问题。Django推荐的方法是自定义Manager子类,并加在models中。

你也可以在model中增加多个managers,或者重新定义objects,也可以维持单个的manager,增加自定义方法。

下面让我们实验一下这几种方法:

方法1:多managers

 

class IncompleteTodoManager(models.Manager):
  def get_query_set(self):
    return super(TodoManager, self).get_query_set().filter(is_done=False)
 
class HighPriorityTodoManager(models.Manager):
  def get_query_set(self):
    return super(TodoManager, self).get_query_set().filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = models.Manager() # the default manager
 
  # attach our custom managers:
  incomplete = models.IncompleteTodoManager()
  high_priority = models.HighPriorityTodoManager()

这个接口将以这样的方式展现:
 

>>> Todo.incomplete.all()
>>> Todo.high_priority.all()

这个方法有几个问题。


第一,这种实现方式比较啰嗦。你要为每一个query自定义功能定义一个class。

第二,这将会弄乱你的命名空间。Django开发者吧Model.objects看做表的入口。这样做会破坏命名规则。

第三,不可链接的。这样做不能将managers组合在一起,获得不完整,高优先级的todos,还是回到低等级的ORM代码:Todo.incomplete.filter(priority=1) 或Todo.high_priority.filter(is_done=False)
综上,使用多managers的方法,不是最优选择。


方法2: Manager 方法

现在,我们试下其他Django允许的方法:在单个自定义Manager中的多个方法
 

class TodoManager(models.Manager):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = TodoManager()

我们的API 现在看起来是这样:
 

>>> Todo.objects.incomplete()
>>> Todo.objects.high_priority()

这个方法显然更好。它没有太多累赘(只有一个Manager类)并且这种查询方法很好地在对象后预留命名空间。(译注:可以很形象、方便地添加更多的方法)
不过这还不够全面。 Todo.objects.incomplete() 返回一个普通查询,但我们无法使用 Todo.objects.incomplete().high_priority() 。我们卡在 Todo.objects.incomplete().filter(is_done=False),没有使用。


方法3:自定义QuerySet

现在我们已进入Django尚未开放的领域,Django文档中找不到这些内容。。。
 

class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class TodoManager(models.Manager):
  def get_query_set(self):
    return TodoQuerySet(self.model, using=self._db)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = TodoManager()

我们从以下调用的视图代码中可以看出端倪:

 

>>> Todo.objects.get_query_set().incomplete()
>>> Todo.objects.get_query_set().high_priority()
>>> # (or)
>>> Todo.objects.all().incomplete()
>>> Todo.objects.all().high_priority()

差不多完成了!这并没有比第2个方法多多少累赘,却得到方法2同样的好处,和额外的效果(来点鼓声吧...),它终于可链式查询了!
 

>>> Todo.objects.all().incomplete().high_priority()

然而它还不够完美。这个自定义的Manager仅仅是一个样板而已,而且 all() 还有瑕疵,在使用时不好把握,而更重要的是不兼容,它让我们的代码看起来有点怪异。


方法3a:复制Django,代理做所有事

现在我们让以上”假冒Manager API“讨论变得有用:我们知道如何解决这个问题。我们简单地在Manager中重新定义所有QuerySet方法,然后代理它们返回我们自定义QuerySet:
 

class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class TodoManager(models.Manager):
  def get_query_set(self):
    return TodoQuerySet(self.model, using=self._db)
 
  def incomplete(self):
    return self.get_query_set().incomplete()
 
  def high_priority(self):
    return self.get_query_set().high_priority()

这个能更好地提供我们想要的API:
 

>>> Todo.objects.incomplete().high_priority() # yay!

除上面那些输入部分、且非常不DRY,每次你新增一个文件到QuerySet,或是更改现有的方法标记,你必须记住在你的Manager中做相同的更改,否则它可能不会正常工作。这是配置的问题
方法3b: django-model-utils

Python 是一种动态语言。 我们就一定能避免所有模块?一个名叫Django-model-utils的第三方应用带来的一点小忙,就会有点不受控制了。先运行 pip install django-model-utils ,然后……
 

from model_utils.managers import PassThroughManager
 
class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = PassThroughManager.for_queryset_class(TodoQuerySet)()

这要好多了。我们只是象之前一样 简单地定义了自定义QuerySet子类,然后通过django-model-utils提供的PassThroughManager类附加这些QuerySet到我们的model中。

PassThroughManager 是由__getattr__ 实现的,它能阻止访问到django定义的“不存在的方法”,并且自动代理它们到QuerySet。这里需要小心一点,检查确认我们没有在一些特性中没有无限递归(这是我为什么推荐使用django-model-utils所提供的用不断尝试测试的方法,而不是自己手工重复写)。

做这些有什么帮助?

记得上面早些定义的视图代码么?
 

def dashboard(request):
 
  todos = Todo.objects.filter(
    owner=request.user
  ).filter(
    is_done=False
  ).filter(
    priority=1
  )
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

加点小改动,我们让它看起来象这样:
 

def dashboard(request):
 
  todos = Todo.objects.for_user(
    request.user
  ).incomplete().high_priority()
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

希望你也能同意第二个版本比第一个更简便,清晰并且更有可读性。
Django能帮忙么?


让这整个事情更容易的方法,已经在django开发邮件列表中讨论过,并且得到一个相关票据(译注:associated ticket叫啥名更好?)。Zachary Voase则建议如下:
 

class TodoManager(models.Manager):
 
  @models.querymethod
  def incomplete(query):
    return query.filter(is_done=False)

通过这个简单的装饰方法的定义,让Manager和QuerySet都能使不可用的方法神奇地变为可用。

我个人并不完全赞同使用基于装饰方法。它略过了详细的信息,感觉有点“嘻哈”。我感觉好的方法,增加一个QuerSet子类(而不是Manager子类)是更好,更简单的途径。
或者我们更进一步思考。退回到在争议中重新审视Django的API设计决定时,也许我们能得到真实更深的改进。能不再争吵Managers和QuerySet的区别吗(至少澄清一下)?


我很确信,不管以前是否曾经有过这么大的重构工作,这个功能必然要在Django 2.0 甚至更后的版本中。

因此,简单概括一下:

在视图和其他高级应用中使用源生的ORM查询代码不是很好的主意。而是用django-model-utils中的PassThroughManager将我们新加的自定义QuerySet API加进你的模型中,这能给你以下好处:

  •     啰嗦代码少,并且更健壮。
  •     增加DRY,增强抽象级别。
  •    将所属的业务逻辑推送至对应的域模型层。

感谢阅读!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
Python 학습 : 2 시간의 일일 연구가 충분합니까?Python 학습 : 2 시간의 일일 연구가 충분합니까?Apr 18, 2025 am 12:22 AM

하루에 2 시간 동안 파이썬을 배우는 것으로 충분합니까? 목표와 학습 방법에 따라 다릅니다. 1) 명확한 학습 계획을 개발, 2) 적절한 학습 자원 및 방법을 선택하고 3) 실습 연습 및 검토 및 통합 연습 및 검토 및 통합,이 기간 동안 Python의 기본 지식과 고급 기능을 점차적으로 마스터 할 수 있습니다.

웹 개발을위한 파이썬 : 주요 응용 프로그램웹 개발을위한 파이썬 : 주요 응용 프로그램Apr 18, 2025 am 12:20 AM

웹 개발에서 Python의 주요 응용 프로그램에는 Django 및 Flask 프레임 워크 사용, API 개발, 데이터 분석 및 시각화, 머신 러닝 및 AI 및 성능 최적화가 포함됩니다. 1. Django 및 Flask 프레임 워크 : Django는 복잡한 응용 분야의 빠른 개발에 적합하며 플라스크는 소형 또는 고도로 맞춤형 프로젝트에 적합합니다. 2. API 개발 : Flask 또는 DjangorestFramework를 사용하여 RESTFULAPI를 구축하십시오. 3. 데이터 분석 및 시각화 : Python을 사용하여 데이터를 처리하고 웹 인터페이스를 통해 표시합니다. 4. 머신 러닝 및 AI : 파이썬은 지능형 웹 애플리케이션을 구축하는 데 사용됩니다. 5. 성능 최적화 : 비동기 프로그래밍, 캐싱 및 코드를 통해 최적화

Python vs. C : 성능과 효율성 탐색Python vs. C : 성능과 효율성 탐색Apr 18, 2025 am 12:20 AM

Python은 개발 효율에서 C보다 낫지 만 C는 실행 성능이 높습니다. 1. Python의 간결한 구문 및 풍부한 라이브러리는 개발 효율성을 향상시킵니다. 2.C의 컴파일 유형 특성 및 하드웨어 제어는 실행 성능을 향상시킵니다. 선택할 때는 프로젝트 요구에 따라 개발 속도 및 실행 효율성을 평가해야합니다.

Python in Action : 실제 예제Python in Action : 실제 예제Apr 18, 2025 am 12:18 AM

Python의 실제 응용 프로그램에는 데이터 분석, 웹 개발, 인공 지능 및 자동화가 포함됩니다. 1) 데이터 분석에서 Python은 Pandas 및 Matplotlib를 사용하여 데이터를 처리하고 시각화합니다. 2) 웹 개발에서 Django 및 Flask 프레임 워크는 웹 응용 프로그램 생성을 단순화합니다. 3) 인공 지능 분야에서 Tensorflow와 Pytorch는 모델을 구축하고 훈련시키는 데 사용됩니다. 4) 자동화 측면에서 파이썬 스크립트는 파일 복사와 같은 작업에 사용할 수 있습니다.

Python의 주요 용도 : 포괄적 인 개요Python의 주요 용도 : 포괄적 인 개요Apr 18, 2025 am 12:18 AM

Python은 데이터 과학, 웹 개발 및 자동화 스크립팅 필드에 널리 사용됩니다. 1) 데이터 과학에서 Python은 Numpy 및 Pandas와 같은 라이브러리를 통해 데이터 처리 및 분석을 단순화합니다. 2) 웹 개발에서 Django 및 Flask 프레임 워크를 통해 개발자는 응용 프로그램을 신속하게 구축 할 수 있습니다. 3) 자동 스크립트에서 Python의 단순성과 표준 라이브러리가 이상적입니다.

파이썬의 주요 목적 : 유연성과 사용 편의성파이썬의 주요 목적 : 유연성과 사용 편의성Apr 17, 2025 am 12:14 AM

Python의 유연성은 다중 파리가 지원 및 동적 유형 시스템에 반영되며, 사용 편의성은 간단한 구문 및 풍부한 표준 라이브러리에서 나옵니다. 유연성 : 객체 지향, 기능 및 절차 프로그래밍을 지원하며 동적 유형 시스템은 개발 효율성을 향상시킵니다. 2. 사용 편의성 : 문법은 자연 언어에 가깝고 표준 라이브러리는 광범위한 기능을 다루며 개발 프로세스를 단순화합니다.

파이썬 : 다목적 프로그래밍의 힘파이썬 : 다목적 프로그래밍의 힘Apr 17, 2025 am 12:09 AM

Python은 초보자부터 고급 개발자에 이르기까지 모든 요구에 적합한 단순성과 힘에 호의적입니다. 다목적 성은 다음과 같이 반영됩니다. 1) 배우고 사용하기 쉽고 간단한 구문; 2) Numpy, Pandas 등과 같은 풍부한 라이브러리 및 프레임 워크; 3) 다양한 운영 체제에서 실행할 수있는 크로스 플랫폼 지원; 4) 작업 효율성을 향상시키기위한 스크립팅 및 자동화 작업에 적합합니다.

하루 2 시간 안에 파이썬 학습 : 실용 가이드하루 2 시간 안에 파이썬 학습 : 실용 가이드Apr 17, 2025 am 12:05 AM

예, 하루에 2 시간 후에 파이썬을 배우십시오. 1. 합리적인 학습 계획 개발, 2. 올바른 학습 자원을 선택하십시오. 3. 실습을 통해 학습 된 지식을 통합하십시오. 이 단계는 짧은 시간 안에 Python을 마스터하는 데 도움이 될 수 있습니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

Dreamweaver Mac版

Dreamweaver Mac版

시각적 웹 개발 도구

PhpStorm 맥 버전

PhpStorm 맥 버전

최신(2018.2.1) 전문 PHP 통합 개발 도구

맨티스BT

맨티스BT

Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse용 SAP NetWeaver 서버 어댑터

Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.

WebStorm Mac 버전

WebStorm Mac 버전

유용한 JavaScript 개발 도구