検索
ホームページバックエンド開発Python チュートリアル高レベルのクエリ API の構築: Django ORM の正しい使用方法


要約

この記事では、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 ツールを構築しています。これに基づいて、抽象化レベルを高め、対話型コードを減らします。この利点は、コードがより読みやすく、再利用可能で、堅牢になることです。

これは別の例で実行しており、以下のデータベース情報を取得する例で使用します。

この方法を説明するために、簡単なアプリ (todo リスト) を使用して説明します。

注: これは一例です。少量のコードで実際の例を示すのは難しいためです。 ToDo リスト自体の継承についてはあまり心配せず、このメソッドを実行する方法に焦点を当ててください。

以下は 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

このデータを渡し、現在のユーザーの不完全で優先度の高い Todo を表示するビューを作成すると想像してください。コードは次のとおりです:

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) として記述できます。しかし、これは単なる実験です。

なぜこのように書くのは良くないのでしょうか?

まず第一に、コードは冗長です。完了するには 7 行のコードが必要ですが、正式なプロジェクトではさらに複雑になります。

次に、実装の詳細をリークします。たとえば、コード内の is_done は BooleanField です。その型が変更されると、コードは機能しなくなります。

そうすると意図が不明確でわかりにくいです。

最後に、重複使用が発生します。例: cron を通じてすべてのユーザーに毎週 todo リストを送信するコマンド行を作成する必要があります。この場合、7 行のコードをコピーして貼り付ける必要があります。これは DRY に沿ったものではありません (繰り返さないでください)

大胆に推測してみましょう: 低レベルの ORM コードを直接使用するのはアンチパターンです。

どうすれば改善できますか?

マネージャーとクエリセットの使用

まず、概念を理解しましょう。

Django には、テーブルレベルの操作に関連する 2 つの密接に関連した構成要素があります: マネージャーとクエリセット

manager (django.db.models.manager.Manager のインスタンス) は、「データベースをクエリすることによって Django に提供されるプラグイン」として説明されています「。」 Manager は、テーブル レベルの機能のための ORM へのゲートウェイです。各モデルには、オブジェクトと呼ばれるデフォルトのマネージャーがあります。

Quesyset (django.db.models.query.QuerySet) は「データベース内のオブジェクトのコレクション」です。これは本質的には SELECT クエリであり、フィルタリング、並べ替えなど (フィルター処理、並べ替え) を使用して、クエリされたデータを制限または変更することもできます。 django.db.models.sql.query.Query インスタンスを作成または操作し、データベース バックエンド経由で実際の SQL でクエリを実行するために使用されます。


え?まだわかりませんか?

ORM をゆっくりと深く理解すると、Manager と QuerySet の違いが理解できるようになります。

よく知られているマネージャー インターフェイスは見た目とは異なるため、人々は混乱するでしょう。

マネージャーインターフェースは嘘です。

QuerySet メソッドはチェーン可能です。 QuerySet メソッド (フィルターなど) が呼び出されるたびに、コピーされたクエリセットが返され、次の呼び出しを待ちます。これは、Django ORM の滑らかな美しさの一部です。

しかし、Model.objects がマネージャーである場合、問題が発生します。開始点としてオブジェクトを呼び出し、結果の QuerySet にリンクする必要があります。

では、Django はそれをどのように解決するのでしょうか?

インターフェイスの嘘がここで暴露されます。すべての QuerySet メソッドは Manager に基づいています。このメソッドでは、

を介してすぐにtodoリストに戻り、クエリインターフェースの問題を解決しましょう。 Django が推奨する方法は、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...


複数のマネージャーをモデルに追加したり、オブジェクトを再定義したり、単一のマネージャーを維持してカスタム メソッドを追加したりすることもできます。


次の方法を試してみましょう:


方法 1: 複数のマネージャー

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()

This方法 いくつかの質問があります。

まず、この実装方法はかなり面倒です。クエリカスタム関数ごとにクラスを定義する必要があります。

第二に、これにより名前空間が台無しになります。 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()

   

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

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

   

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

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

   

方法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

Pythonを1日2時間学ぶだけで十分ですか?それはあなたの目標と学習方法に依存します。 1)明確な学習計画を策定し、2)適切な学習リソースと方法を選択します。3)実践的な実践とレビューとレビューと統合を練習および統合し、統合すると、この期間中にPythonの基本的な知識と高度な機能を徐々に習得できます。

Web開発用のPython:主要なアプリケーションWeb開発用のPython:主要なアプリケーションApr 18, 2025 am 12:20 AM

Web開発におけるPythonの主要なアプリケーションには、DjangoおよびFlaskフレームワークの使用、API開発、データ分析と視覚化、機械学習とAI、およびパフォーマンスの最適化が含まれます。 1。DjangoandFlask Framework:Djangoは、複雑な用途の迅速な発展に適しており、Flaskは小規模または高度にカスタマイズされたプロジェクトに適しています。 2。API開発:フラスコまたはdjangorestFrameworkを使用して、Restfulapiを構築します。 3。データ分析と視覚化:Pythonを使用してデータを処理し、Webインターフェイスを介して表示します。 4。機械学習とAI:Pythonは、インテリジェントWebアプリケーションを構築するために使用されます。 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の実際のアプリケーションには、データ分析、Web開発、人工知能、自動化が含まれます。 1)データ分析では、PythonはPandasとMatplotlibを使用してデータを処理および視覚化します。 2)Web開発では、DjangoおよびFlask FrameworksがWebアプリケーションの作成を簡素化します。 3)人工知能の分野では、TensorflowとPytorchがモデルの構築と訓練に使用されます。 4)自動化に関しては、ファイルのコピーなどのタスクにPythonスクリプトを使用できます。

Pythonの主な用途:包括的な概要Pythonの主な用途:包括的な概要Apr 18, 2025 am 12:18 AM

Pythonは、データサイエンス、Web開発、自動化スクリプトフィールドで広く使用されています。 1)データサイエンスでは、PythonはNumpyやPandasなどのライブラリを介してデータ処理と分析を簡素化します。 2)Web開発では、DjangoおよびFlask Frameworksにより、開発者はアプリケーションを迅速に構築できます。 3)自動化されたスクリプトでは、Pythonのシンプルさと標準ライブラリが理想的になります。

Pythonの主な目的:柔軟性と使いやすさPythonの主な目的:柔軟性と使いやすさApr 17, 2025 am 12:14 AM

Pythonの柔軟性は、マルチパラダイムサポートと動的タイプシステムに反映されていますが、使いやすさはシンプルな構文とリッチ標準ライブラリに由来しています。 1。柔軟性:オブジェクト指向、機能的および手続き的プログラミングをサポートし、動的タイプシステムは開発効率を向上させます。 2。使いやすさ:文法は自然言語に近く、標準的なライブラリは幅広い機能をカバーし、開発プロセスを簡素化します。

Python:汎用性の高いプログラミングの力Python:汎用性の高いプログラミングの力Apr 17, 2025 am 12:09 AM

Pythonは、初心者から上級開発者までのすべてのニーズに適した、そのシンプルさとパワーに非常に好まれています。その汎用性は、次のことに反映されています。1)学習と使用が簡単、シンプルな構文。 2)Numpy、Pandasなどの豊富なライブラリとフレームワーク。 3)さまざまなオペレーティングシステムで実行できるクロスプラットフォームサポート。 4)作業効率を向上させるためのスクリプトおよび自動化タスクに適しています。

1日2時間でPythonを学ぶ:実用的なガイド1日2時間でPythonを学ぶ:実用的なガイドApr 17, 2025 am 12:05 AM

はい、1日2時間でPythonを学びます。 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ヘンタイを無料で生成します。

ホットツール

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

Dreamweaver Mac版

Dreamweaver Mac版

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)