Djangoを読む

はじめに

ようやくマイグレーションが終わりDjangoのモデルを作成、保存できるようになりました。チュートリアルではマイグレーション後、「APIで遊んでみる」と題してモデルクラスが提供するAPIがどのようなものであるか紹介しています。

>>> from polls.models import Question, Choice

# Create a new Question.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

少しずつ見ていきましょうということでとりあえずここまで。

念のため、Questionの定義

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
 
 
 
 
from django.db import models
 
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

django/db/models/base.py

というわけでModelクラスを見ていきます。modelsの__init__.pyはインポートしてるだけで実体はbase.pyに書かれています。

Model.__init__

インスタンスを作成しているのでまずはもちろん__init__メソッドを確認します。長いので、今回関係のあるところだけ抜き出し

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
-
!
 
 
-
!
-
!
 
-
|
!
 
 
-
!
 
 
 
-
!
 
 
 
-
!
 
 
 
-
!
 
 
 
 
    def __init__(self, *args, **kwargs):
        # Set up the storage for instance state
        self._state = ModelState()
 
        if not kwargs:
            # 省略
        else:
            # Slower, kwargs-ready version.
            fields_iter = iter(self._meta.fields)
 
        # Now we're left with the unprocessed fields that *must* come from
        # keywords, or default.
 
        for field in fields_iter:
            is_related_object = False
            # Virtual field
            if field.attname not in kwargs and field.column is None:
                continue
            if kwargs:
                if isinstance(field.remote_field, ForeignObjectRel):
                    # 省略
                else:
                    try:
                        val = kwargs.pop(field.attname)
                    except KeyError:
                        # 省略
            else:
                val = field.get_default()
 
            if is_related_object:
                # 省略
            else:
                if val is not DEFERRED:
                    setattr(self, field.attname, val)
 
        super(Model, self).__init__()

何をしているかというと、

  1. メタ情報として持っている各フィールドについて
  2. キーワード引数から値を取得し
  3. インスタンスの属性として設定

を行っています。普通のクラスでやるような初期化をメタ情報から名前を取ってきて設定している、ということになります。

save

というわけでモデルのインスタンス化ができたので次は保存を行うsaveメソッドです。いろいろやっていますが同じく今回関係のある部分だけ抜き出し

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
 
        using = using or router.db_for_write(self.__class__, instance=self)
 
        self.save_base(using=using, force_insert=force_insert,
                       force_update=force_update, update_fields=update_fields)

普通に使っている分には結局この2行だけになります。usingは、routerは前回も出てきたデータベース振り分けの仕組みですが、振り分けしてなければ"default"のDBを使うということになってsave_baseメソッドに続きます。

save_baseメソッドも例によって関係のあるところだけ抜き出し

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 
 
 
 
 
 
 
-
!
-
!
    def save_base(self, raw=False, force_insert=False,
                  force_update=False, using=None, update_fields=None):
 
        cls = self.__class__
        with transaction.atomic(using=using, savepoint=False):
            updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
 
        # Store the database on which the object was saved
        self._state.db = using
        # Once saved, this is no longer a to-be-added instance.
        self._state.adding = False

_save_tableに続きます。tableという名前が出てきたことからそろそろデータベースとやり取りする層に降りてくると思われます。

_save_table

重要そうなのでセクションを変えて、_save_tableメソッドです。いつも通りに今回通らないところは省略

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 
-
|
|
!
 
 
 
 
 
 
 
 
-
!
 
 
 
 
 
 
 
 
 
    def _save_table(self, raw=False, cls=None, force_insert=False,
                    force_update=False, using=None, update_fields=None):
        """
        Does the heavy-lifting involved in saving. Updates or inserts the data
        for a single table.
        """
        meta = cls._meta
 
        pk_val = self._get_pk_val(meta)
        if pk_val is None:
            pk_val = meta.pk.get_pk_value_on_save(self)
            setattr(self, meta.pk.attname, pk_val)
        pk_set = pk_val is not None
        updated = False
        # If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
        if not updated:
            fields = meta.local_concrete_fields
            if not pk_set:
                fields = [f for f in fields if not isinstance(f, AutoField)]
 
            update_pk = bool(meta.has_auto_field and not pk_set)
            result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
            if update_pk:
                setattr(self, meta.pk.attname, result)
        return updated

_get_pk_valメソッドを呼び出してpk、主キーを取得しています。新規レコードなので当然結果はNoneです。次に、フィールドクラスのget_pk_value_on_saveメソッドが呼ばれていますが通常はNoneが返される、というわけで_do_insertが呼ばれてINSERTが行われます(上のコードでは省略していますが、pkの値があるならUPDATEが行われるようになっています)

_do_insertメソッドはシンプル、というかmanagerへの移譲が行われているだけです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
-
|
|
!
 
 
    def _do_insert(self, manager, using, fields, update_pk, raw):
        """
        Do an INSERT. If update_pk is defined then this method should return
        the new pk for the model.
        """
        return manager._insert([self], fields=fields, return_id=update_pk,
                               using=using, raw=raw)

ModelBase._base_manager

ところで、manager、より正確には_base_managerとは何者なのでしょうか。メタクラスのModelBaseを見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
 
 
 
    @property
    def _base_manager(cls):
        return cls._meta.base_manager

_meta、optionsモジュールのOptionsクラスに続く。base_managerプロパティはいろいろやっていますが関係ないとこ省くと以下のようになります。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
    @cached_property
    def base_manager(self):
        manager = Manager()
        manager.name = '_base_manager'
        manager.model = self.model
        manager.auto_created = True
        return manager

というわけで、managerモジュールのManagerクラスのインスタンスが返されます。

ところで、managerという単語は前にも出てきた気がします。具体的には、モデルのインポートを読んだ時のModelBase._prepare

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
-
!
 
 
 
 
    def _prepare(cls):
        # 省略
 
        if not opts.managers or cls._requires_legacy_default_manager():
            manager = Manager()
            manager.auto_created = True
            cls.add_to_class('objects', manager)

base_managerプロパティを見たとき、「ややこしい処理には行かないはずだからこの最後に書いてあるのだろ」と思ったのですが、「いや待て、Managerインスタンスってもういるはずだよな」とこちらの処理を思い出しadd_to_class以降に進んだのですが、結局、INSERTの処理をする時はこの'objects'は使われないということがわかりました。Managerが2ついるのはどうにも気持ち悪いですが事実そうなっているようです。

django/db/models/manager.py

さて、Managerクラスです。

Everything is expanded.Everything is shortened.
  1
  2
 
 
class Manager(BaseManager.from_queryset(QuerySet)):
    pass

・・・。BaseManagerのfrom_querysetメソッド

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
 
 
 
 
 
    @classmethod
    def from_queryset(cls, queryset_class, class_name=None):
        if class_name is None:
            class_name = '%sFrom%s' % (cls.__name__, queryset_class.__name__)
        class_dict = {
            '_queryset_class': queryset_class,
        }
        class_dict.update(cls._get_queryset_methods(queryset_class))
        return type(class_name, (cls,), class_dict)

_get_queryset_methodsでQuerySetからメソッドを取得し、新しいクラスを作成しています。で、_get_queryset_methods

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 
 
 
 
 
 
 
 
 
-
!
 
-
!
 
-
!
 
 
-
!
 
    @classmethod
    def _get_queryset_methods(cls, queryset_class):
        def create_method(name, method):
            def manager_method(self, *args, **kwargs):
                return getattr(self.get_queryset(), name)(*args, **kwargs)
            manager_method.__name__ = method.__name__
            manager_method.__doc__ = method.__doc__
            return manager_method
 
        new_methods = {}
        # Refs http://bugs.python.org/issue1785.
        predicate = inspect.isfunction if six.PY3 else inspect.ismethod
        for name, method in inspect.getmembers(queryset_class, predicate=predicate):
            # Only copy missing methods.
            if hasattr(cls, name):
                continue
            # Only copy public methods or methods with the attribute `queryset_only=False`.
            queryset_only = getattr(method, 'queryset_only', None)
            if queryset_only or (queryset_only is None and name.startswith('_')):
                continue
            # Copy the method onto the manager.
            new_methods[name] = create_method(name, method)
        return new_methods

うーん、関数内関数内関数なんて初めて見た(笑)

get_querysetメソッド。なんで毎回別のオブジェクト作ってるんだろ、と思いましたが、確かにINSERTはともかく他のだと検索情報を個別に記録しないといけませんね。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
-
|
|
!
 
    def get_queryset(self):
        """
        Returns a new QuerySet object.  Subclasses can override this method to
        easily customize the behavior of the Manager.
        """
        return self._queryset_class(model=self.model, using=self._db, hints=self._hints)

django/db/models/query.py

さて、話を戻して、_insertメソッドです。上で見てきたように各種のメソッドはManagerに直接書かれているわけではなくqueryモジュールのQuerySetクラスに書かれています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 
-
|
|
!
 
 
 
 
 
 
 
 
    def _insert(self, objs, fields, return_id=False, raw=False, using=None):
        """
        Inserts a new record for the given model. This provides an interface to
        the InsertQuery class and is how Model.save() is implemented.
        """
        self._for_write = True
        if using is None:
            using = self.db
        query = sql.InsertQuery(self.model)
        query.insert_values(fields, objs, raw=raw)
        return query.get_compiler(using=using).execute_sql(return_id)
    _insert.alters_data = True
    _insert.queryset_only = False

Pythonってメソッドに属性を設定できるんですね。まあ普通使うことはないと思いますが。

django/db/models/sql

sqlモジュールに移りましょう。まずは__init__。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
 
 
 
 
from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.query import *  # NOQA
from django.db.models.sql.subqueries import *  # NOQA
from django.db.models.sql.where import AND, OR
 
__all__ = ['Query', 'AND', 'OR', 'EmptyResultSet']

InsertQueryはqueryモジュールに書かれていそうです。 ・・・と思ったらsubqueriesの方に書いてありました。このsubというのは「SQLの副問い合わせ」という意味じゃなくて「Queryクラスのサブクラス」という意味のようですね。もっともinsert_valuesではSQLの構築は行われずインスタンスへの設定だけが行われるようです。

django.db.models.sql.query.Query.get_compiler

というわけでまずget_compilerから見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
    def get_compiler(self, using=None, connection=None):
        if using:
            connection = connections[using]
        return connection.ops.compiler(self.compiler)(self, connection, using)

出てきましたconnections。ここで前回も見てきた各DBMSに対応したDatabaseWrapperに処理が移ります。 といっても、ops、sqlite3の方のDatabaseOperationsインスタンスにはcompilerメソッドは定義されておらず、基底クラスのBaseDatabaseOperationsに定義されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 
 
 
-
|
|
|
!
 
 
 
    compiler_module = "django.db.models.sql.compiler"
 
    def compiler(self, compiler_name):
        """
        Returns the SQLCompiler class corresponding to the given name,
        in the namespace corresponding to the `compiler_module` attribute
        on this backend.
        """
        if self._cache is None:
            self._cache = import_module(self.compiler_module)
        return getattr(self._cache, compiler_name)

compiler_name、その元Queryインスタンスのcompiler、はInsertQueryの場合、SQLInsertCompilerになります。つまり、django.db.models.sql.compiler.SQLInsertCompilerが返されることになります。

django.db.models.sql.compiler.SQLInsertCompiler.execute_sql

残りはexecute_sqlメソッドです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 
 
 
 
 
 
 
 
 
 
 
 
 
    def execute_sql(self, return_id=False):
        self.return_id = return_id
        with self.connection.cursor() as cursor:
            for sql, params in self.as_sql():
                cursor.execute(sql, params)
            if self.connection.features.can_return_ids_from_bulk_insert and len(self.query.objs) > 1:
                return self.connection.ops.fetch_returned_insert_ids(cursor)
            if self.connection.features.can_return_id_from_insert:
                assert len(self.query.objs) == 1
                return self.connection.ops.fetch_returned_insert_id(cursor)
            return self.connection.ops.last_insert_id(
                cursor, self.query.get_meta().db_table, self.query.get_meta().pk.column
            )

先に言っておくと、sqlite3の場合、can_return系のfeaturesはTrueにはならず最後のlast_insert_idの方法でINSERTされたIDが返されるようです。

as_sql

as_sqlメソッドではDBMSのfeaturesも考慮してINSERT文が構築されます。一部省略して載せると、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
-
|
!
 
 
 
 
 
 
 
 
 
 
 
 
 
-
!
-
|
|
|
!
 
 
 
 
-
!
 
 
 
 
    def as_sql(self):
        # We don't need quote_name_unless_alias() here, since these are all
        # going to be column names (so we can avoid the extra overhead).
        qn = self.connection.ops.quote_name
        opts = self.query.get_meta()
        result = ['INSERT INTO %s' % qn(opts.db_table)]
 
        has_fields = bool(self.query.fields)
        fields = self.query.fields if has_fields else [opts.pk]
        result.append('(%s)' % ', '.join(qn(f.column) for f in fields))
 
        if has_fields:
            value_rows = [
                [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
                for obj in self.query.objs
            ]
        else:
            # 省略
 
        # Currently the backends just accept values when generating bulk
        # queries and generate their own placeholders. Doing that isn't
        # necessary and it should be possible to use placeholders and
        # expressions in bulk inserts too.
        can_bulk = (not self.return_id and self.connection.features.has_bulk_insert)
 
        placeholder_rows, param_rows = self.assemble_as_sql(fields, value_rows)
 
        if can_bulk:
            # 省略
        else:
            return [
                (" ".join(result + ["VALUES (%s)" % ", ".join(p)]), vals)
                for p, vals in zip(placeholder_rows, param_rows)
            ]

pre_save_valメソッドはコメントにあるようにFieldクラスを呼び出して時間を「保存した時」にするなどの処理を行っていますが通常は単にgetattrで値を取得しているだけです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
 
-
|
|
!
 
 
 
    def pre_save_val(self, field, obj):
        """
        Get the given field's value off the given obj. pre_save() is used for
        things like auto_now on DateTimeField. Skip it if this is a raw query.
        """
        if self.query.raw:
            return getattr(obj, field.attname)
        return field.pre_save(obj, add=True)

prepare_value、assemble_as_sqlについて見ていきます。

prepare_value

prepare_valueメソッド。resolve_expressionは設定されていないはずなのでFieldクラスのget_db_prep_saveメソッドが実行されます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
-
|
|
!
 
-
!
 
 
    def prepare_value(self, field, value):
        """
        Prepare a value to be used in a query by resolving it if it is an
        expression and otherwise calling the field's get_db_prep_save().
        """
        if hasattr(value, 'resolve_expression'):
            # 省略
        else:
            value = field.get_db_prep_save(value, connection=self.connection)
        return value

Fieldのget_db_prep_saveメソッド。は、get_db_prep_valueメソッドを呼んでるだけです。さらにget_db_prep_valueではget_prep_valueメソッドを呼び出しています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 
-
|
!
 
 
 
 
-
|
|
|
!
 
 
 
    def get_db_prep_save(self, value, connection):
        """
        Returns field's value prepared for saving into a database.
        """
        return self.get_db_prep_value(value, connection=connection,
                                      prepared=False)
 
    def get_db_prep_value(self, value, connection, prepared=False):
        """Returns field's value prepared for interacting with the database
        backend.
 
        Used by the default implementations of get_db_prep_save().
        """
        if not prepared:
            value = self.get_prep_value(value)
        return value

get_prep_valueメソッドはFieldクラスの各サブクラスでオーバーライドされているようです。以下はDateTimeFieldのget_prep_value。to_pythonメソッドは省略しますがvalueをdatetimeオブジェクトにする処理をしています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
 
 
-
!
    def get_prep_value(self, value):
        value = super(DateTimeField, self).get_prep_value(value)
        value = self.to_python(value)
        if value is not None and settings.USE_TZ and timezone.is_naive(value):
            # 省略
        return value

assemble_as_sql

SQLInsertCompilerクラスに戻って、assemble_as_sqlメソッドです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
-
|
|
|
|
|
|
|
|
|
!
 
 
 
-
|
!
 
 
 
 
-
|
!
 
-
|
!
 
-
!
 
 
    def assemble_as_sql(self, fields, value_rows):
        """
        Take a sequence of N fields and a sequence of M rows of values,
        generate placeholder SQL and parameters for each field and value, and
        return a pair containing:
         * a sequence of M rows of N SQL placeholder strings, and
         * a sequence of M rows of corresponding parameter values.
 
        Each placeholder string may contain any number of '%s' interpolation
        strings, and each parameter row will contain exactly as many params
        as the total number of '%s's in the corresponding placeholder row.
        """
        if not value_rows:
            return [], []
 
        # list of (sql, [params]) tuples for each object to be saved
        # Shape: [n_objs][n_fields][2]
        rows_of_fields_as_sql = (
            (self.field_as_sql(field, v) for field, v in zip(fields, row))
            for row in value_rows
        )
 
        # tuple like ([sqls], [[params]s]) for each object to be saved
        # Shape: [n_objs][2][n_fields]
        sql_and_param_pair_rows = (zip(*row) for row in rows_of_fields_as_sql)
 
        # Extract separate lists for placeholders and params.
        # Each of these has shape [n_objs][n_fields]
        placeholder_rows, param_rows = zip(*sql_and_param_pair_rows)
 
        # Params for each field are still lists, and need to be flattened.
        param_rows = [[p for ps in row for p in ps] for row in param_rows]
 
        return placeholder_rows, param_rows

見た感じ、何回も形式変換を行っています。とりあえず、field_as_sqlを見てみましょう。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
-
|
|
|
|
|
|
!
 
-
!
-
!
-
!
-
!
 
 
    def field_as_sql(self, field, val):
        """
        Take a field and a value intended to be saved on that field, and
        return placeholder SQL and accompanying params. Checks for raw values,
        expressions and fields with get_placeholder() defined in that order.
 
        When field is None, the value is considered raw and is used as the
        placeholder, with no corresponding parameters returned.
        """
        if field is None:
            # 省略
        elif hasattr(val, 'as_sql'):
            # 省略
        elif hasattr(field, 'get_placeholder'):
            # 省略
        else:
            # Return the common case for the placeholder
            sql, params = '%s', [val]
 
        return sql, params

というわけでまずrows_of_fields_as_sqlは、

 ('%s', [挿入する値])

のタプルのリスト(正確にはジェネレータ)になります。さらにそれがオブジェクト分(今回は1個)のジェネレータになっています。

sql_and_param_pair_rowsは各オブジェクト(テーブルの一行)の(フィールド, 値)タプルのzipオブジェクトのジェネレータです。

さらにzipを通すことで、placeholder_rowsとparam_rowsに分離しています。すなわち、それぞれ以下のような形状をしています。

placeholder_rows

 (('%s', '%s'),)

param_rows

 (([値], [値]),)

うーん、だんだんよくわからなくなってきた(上の形状は確認として打ってみた結果です)。最後に、param_rowsで値が1要素のリストになっているのをflattenしています。結果、

param_rows

 [[値, 値]]

というリストに変換されます。このようにして返されたフィールドと値のうち、フィールドはSQLの構築に利用、値は最終的にカーソルオブジェクトに渡されてデータベースに送られることになります。

おわりに

今回はモデル処理の手始めとしてモデルの保存について見てきました。Managerオブジェクトが登場し、QuerySetと絡んで複雑な処理が行われていました。INSERTの場合は個別のQuerySetは要らないので冗長に感じますが今後なぜこのような構造が必要なのかが出てくると思われます。

また、SQLを組み立てるのにCompilerというオブジェクトが出てきました。コンパイラと言ってもSQLを受け取って構文解析するコンパイラではなく、SQLを組み立てるコンパイラです。これもINSERTだと冗長な感じでしたが複雑な検索処理が設定された場合に力を発揮するのでしょう。

というわけで引き続き、「APIで遊んでみる」を見ていきましょう。


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-07-15 (土) 23:29:39 (2470d)