Django - ORM源码分析
- Django - ORM分析
- 一 Django ORM
- 二 Manager分析
- 三 Qeuryset分析
- 3.1
QuerySet
求值的场景 - 3.2 queryset的方法
- 3.2.1 delete()
- 3.2.2 update()
- 3.2.3
filter(**kwargs)
、exclude(**kwargs)
、get(**kwargs)
- 3.2.4 SQL其它关键字在django中的实现
- F类(无对应SQL关键字)
- Q类(对应
and/or/not
) annotate
(无对应SQL关键字)order_by
——对应order by
distinct
——对应distinct
values()和values_list()
——对应select 某几个字段
select_related()
——对应返回关联记录实体prefetch_related(*field)
——对应返回关联记录实体的集合extra()
——实现复杂的where
子句aggregate(*args, **kwargs)
——对应聚合函数exists()、count()、len()
contains/startswith/endswith
——对应like
in
——对应in
exclude(field__in=iterable)
——对应not in
gt/gte/lt/lte
——对应于`>,>=,<,<=range
——对应于between and
isnull
——对应于is null
QuerySet
切片——对应于limit
- 3.2.5 经验
- 3.1
Django - ORM分析
一 Django ORM
Django ORM用到三个类:Manager
、QuerySet
、Model
。
Manager
定义表级方法(表级方法就是影响一条或多条记录的方法),我们可以以models.Manager
为父类,定义自己的 manager
,增加表级方法;
QuerySet
:Manager
类的一些方法会返回QuerySet
实例,QuerySet
是一个可遍历结构,包含一个或多个元素,每个元素都是一个Model 实例,它里面的方法也是表级方法,前面说了,Django给我们提供了增加表级方法的途径,那就是自定义manager
类,而不是自定义QuerySet
类,一般的我们没有自定义QuerySet
类的必要;
django.db.models
模块中的Model
类,我们定义表的model
时,就是继承它,它的功能很强大,通过自定义model
的instance
可以获取外键实体等,它的方法都是记录级方法(都是实例方法,貌似无类方法),不要在里面定义类方法,比如计算记录的总数,查看所有记录,这些应该放在自定义的manager
类中。以Django1.6为基础。
每个Model
都有一个默认的manager
实例,名为objects
。
二 Manager分析
在django中常常见到 model.objects.using(db).all()
或者 model.objects.get(pk=xx)
, 又或者 model.objects.first()
等等关于对数据库的操作,下面以mysql
为代表,简要分析一下上面的种种操作,背后到底做了什么?(先重点提示一下,关注_fetch_all
函数)
2.1 model
中的manager
从哪儿来--ModelBase
首先,model.objects
中的 objects
是 model
的 manager
,manager
是 Manager
类,俗称管理器。他是继承了一个以 BaseManager
为基础的类,这个类通过BaseManager.get_queryset(QuerySet)
,将QuerySet
类的所有方法都添加到了这个manager
中,所以model.objects.using(db).all()
其实就是model.objects.queryset.using(db).all()
;model.objects.get(pk=xx)
就等于model.objects.queryset.get()
。
那么每一个model
中的manager
从哪儿来的呢?见ModelBase
中的__new__()
方法最后调用的_prepare()
方法,_prepare
方法最后创建Manager
属性,_prepare
的源代码如下:
def _prepare(cls):
"""
Creates some methods once self._meta has been populated.
"""
opts = cls._meta
opts._prepare(cls)
if opts.order_with_respect_to:
cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True)
cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False)
# Defer creating accessors on the foreign class until it has been
# created and registered. If remote_field is None, we're ordering
# with respect to a GenericForeignKey and don't know what the
# foreign class is - we'll add those accessors later in
# contribute_to_class().
if opts.order_with_respect_to.remote_field:
wrt = opts.order_with_respect_to
remote = wrt.remote_field.model
lazy_related_operation(make_foreign_order_accessors, cls, remote)
# Give the class a docstring -- its definition.
if cls.__doc__ is None:
cls.__doc__ = "%s(%s)" % (cls.__name__, ", ".join(f.name for f in opts.fields))
get_absolute_url_override = settings.ABSOLUTE_URL_OVERRIDES.get(opts.label_lower)
if get_absolute_url_override:
setattr(cls, 'get_absolute_url', get_absolute_url_override)
if not opts.managers or cls._requires_legacy_default_manager():
if any(f.name == 'objects' for f in opts.fields):
raise ValueError(
"Model %s must specify a custom Manager, because it has a "
"field named 'objects'." % cls.__name__
)
manager = Manager() # 重点关注这里
manager.auto_created = True
cls.add_to_class('objects', manager)
signals.class_prepared.send(sender=cls)
所以是每一个model
类,都有自己的Manager
。
2.2 Manager
而对Manager
类,从源码django/db/models/manager.py
中可以得到:
class Manager(BaseManager.from_queryset(QuerySet)):
pass
Manager
是一个继承自BaseManager.from_queryset(QuerySet)
类的类,到底是一个怎样的呢?同样在django/db/models/manager.py
中可以找到答案
@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) # 重点关注
他返回的是一个用元类type
定义的一个新的类,这个类继承自Manager
类,同时把QuerySet
类中的多有方法等属性也添加到了这个用type
新建的类,具体体现就在cls._get_queryset_methods(queryset_class)
。同样,在django/db/models/manager.py
中找到_get_queryset_methods
函数,就会明白:
@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
2.3 以models.objects.all()
为例分析
2.3.1 从QuerySet
的all
方法开始
由前面的分析可以知道,这里的all
其实也就是QuerySet
中的all
方法:
# django/db/models/query.py
def all(self):
"""
Returns a new QuerySet that is a copy of the current one. This allows a
QuerySet to proxy for a model manager in some cases.
"""
return self._clone()
在django中,我们一般拿到all
的返回值都是进行for循环读取每一个值,然后处理,所以,看看self._clone()
在for循环后得到了什么呢?,首先_clone
方法返回的
def _clone(self, **kwargs):
query = self.query.clone()
if self._sticky_filter:
query.filter_is_sticky = True
clone = self.__class__(model=self.model, query=query, using=self._db, hints=self._hints)
clone._for_write = self._for_write
clone._prefetch_related_lookups = self._prefetch_related_lookups[:]
clone._known_related_objects = self._known_related_objects
clone._iterable_class = self._iterable_class
clone._fields = self._fields
clone.__dict__.update(kwargs)
return clone
其实还是一个QuerySet
实例,根据python中的魔法特性,对于一个类/对象来说,for循环会去调用他的__iter__
方法,那么直接看QuerySet
类中的__iter__
方法就好了:
def __iter__(self):
"""
The queryset iterator protocol uses three nested iterators in the
default case:
1. sql.compiler:execute_sql()
- Returns 100 rows at time (constants.GET_ITERATOR_CHUNK_SIZE)
using cursor.fetchmany(). This part is responsible for
doing some column masking, and returning the rows in chunks.
2. sql/compiler.results_iter()
- Returns one row at time. At this point the rows are still just
tuples. In some cases the return values are converted to
Python values at this location.
3. self.iterator()
- Responsible for turning the rows into model objects.
"""
self._fetch_all() # 重点关注
return iter(self._result_cache)
这里调用了_fetch_all()
方法:
def _fetch_all(self):
if self._result_cache is None:
self._result_cache = list(self.iterator()) # 重点关注这个
if self._prefetch_related_lookups and not self._prefetch_done:
self._prefetch_related_objects()
所以看iterator()
方法就好:
def iterator(self):
"""
An iterator over the results from applying this QuerySet to the
database.
"""
return iter(self._iterable_class(self))
在QuerySet
中的__init__
方法中有self._iterable_class = ModelIterable
,那么看ModelIterable
类
2.3.2 在ModelIterable
处获得obj
class ModelIterable(BaseIterable):
"""
Iterable that yields a model instance for each row.
"""
def __iter__(self):
queryset = self.queryset
db = queryset.db
compiler = queryset.query.get_compiler(using=db) # 重点关注
# Execute the query. This will also fill compiler.select, klass_info,
# and annotations.
results = compiler.execute_sql() # 重点关注
select, klass_info, annotation_col_map = (compiler.select, compiler.klass_info,
compiler.annotation_col_map)
if klass_info is None:
return
model_cls = klass_info['model'] # models.py中的model类,详见django/db/models/sql/compiler.py中的 get_select函数(165行)
select_fields = klass_info['select_fields'] #
model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
init_list = [f[0].target.attname
for f in select[model_fields_start:model_fields_end]]
related_populators = get_related_populators(klass_info, select, db)
for row in compiler.results_iter(results):
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end]) #重点注意
if related_populators:
for rel_populator in related_populators:
rel_populator.populate(row, obj)
if annotation_col_map:
for attr_name, col_pos in annotation_col_map.items():
setattr(obj, attr_name, row[col_pos])
# Add the known related objects to the model, if there are any
if queryset._known_related_objects:
for field, rel_objs in queryset._known_related_objects.items():
# Avoid overwriting objects loaded e.g. by select_related
if hasattr(obj, field.get_cache_name()):
continue
pk = getattr(obj, field.get_attname())
try:
rel_obj = rel_objs[pk]
except KeyError:
pass # may happen in qs1 | qs2 scenarios
else:
setattr(obj, field.name, rel_obj)
yield obj
所以,其实我们在for 循环获取all
的结果的时候,最后获得的就是obj,所以关注obj对象就好了,但是在这个过程中,我们貌似还不知道django是怎么确认到底要操作哪一个数据库的,他是怎么和数据库建立连接的呢?obj到底是怎么来的呢?
2.3.3 哪来的obj
其实对于django
的mysql
部分,在class ModelIterable(BaseIterable)
的源码中,重点关注一下queryset.query.get_compiler(using=db)
方法,也就是django/db/models/sql/query.py
中的 get_compiler
方法,这就是指定从哪个数据库中获取数据的入口。
get_compiler
方法
def get_compiler(self, using=None, connection=None):
if using is None and connection is None:
raise ValueError("Need either using or connection")
if using:
# 这里的connections是全局变量,见django/db/__init__.py中的connections = ConnectionHandler(),
# 所以这里的connections[using]就会触发ConnectionHandler类的__getitem__方法(python的魔法函数之一)
connection = connections[using]
return connection.ops.compiler(self.compiler)(self, connection, using)
那再看看ConnectionHandler
的__getitem__
方法:
def __getitem__(self, alias):
if hasattr(self._connections, alias):
return getattr(self._connections, alias)
self.ensure_defaults(alias)
self.prepare_test_settings(alias)
# self.databases就是settings中指定的settings.DATABASES,是一个字典
db = self.databases[alias]
# 加载指定的数据库后端引擎
backend = load_backend(db['ENGINE'])
# 参见django/db/backends/mysql/base.py的中的DatabaseWrapper
conn = backend.DatabaseWrapper(db, alias)
setattr(self._connections, alias, conn)
return conn
所以上面get_compiler
函数中的connection
变量是一个DatabaseWrapper
类的实例。那 connection.ops.compiler(self.compiler)(self, connection, using)
到底是个啥呢?先看看DatabaseWrapper
类的ops
是啥。
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = 'mysql'
#中间省略
Database = Database
SchemaEditorClass = DatabaseSchemaEditor
def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
self.features = DatabaseFeatures(self)
self.ops = DatabaseOperations(self) # 重点关注
self.client = DatabaseClient(self)
self.creation = DatabaseCreation(self)
self.introspection = DatabaseIntrospection(self)
self.validation = DatabaseValidation(self)
connection.ops
其实是一个DatabaseOperations
类的对象,对于DatabaseOperations
类,他的源码,目前只需要关注
class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.mysql.compiler"
所以接下来只要看看DatabaseOperations
中的compiler
方法了,DatabaseOperations
中是没有compiler
方法的,但是它继承自BaseDatabaseOperations
,所以看看django/db/backends/base/operations.py
中BaseDatabaseOperations
的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)
对于mysql而言,从DatabaseOperations
类的源码中可以看出,self.compiler_module
就是 compiler_module = "django.db.backends.mysql.compiler"
,所以connection.ops.compiler(self.compiler)
,就是根据self.compiler
从django/db/backends/mysql/compiler
模块中返回一个SQLcompiler
类或者其子类(根据query
类型,也就是self.compiler
)的实例(django/db/models/sql/compiler.py
),SQLcompiler
中的execute_sql
方法中调用cursor = self.connection.cursor()
def execute_sql(self, result_type=MULTI):
"""
Run the query against the database and returns the result(s). The
return value is a single data item if result_type is SINGLE, or an
iterator over the results if the result_type is MULTI.
result_type is either MULTI (use fetchmany() to retrieve all rows),
SINGLE (only retrieve a single row), or None. In this last case, the
cursor is returned if any query is executed, since it's used by
subclasses such as InsertQuery). It's possible, however, that no query
is needed, as the filters describe an empty set. In that case, None is
returned, to avoid any unnecessary database interaction.
"""
if not result_type:
result_type = NO_RESULTS
try:
sql, params = self.as_sql()
if not sql:
raise EmptyResultSet
except EmptyResultSet:
if result_type == MULTI:
return iter([])
else:
return
cursor = self.connection.cursor() #重点注意
try:
cursor.execute(sql, params)
except Exception:
cursor.close()
raise
if result_type == CURSOR:
# Caller didn't specify a result_type, so just give them back the
# cursor to process (and close).
return cursor
if result_type == SINGLE:
try:
val = cursor.fetchone()
if val:
return val[0:self.col_count]
return val
finally:
# done with the cursor
cursor.close()
if result_type == NO_RESULTS:
cursor.close()
return
result = cursor_iter(
cursor, self.connection.features.empty_fetchmany_value,
self.col_count
)
if not self.connection.features.can_use_chunked_reads:
try:
# If we are using non-chunked reads, we return the same data
# structure as normally, but ensure it is all read into memory
# before going any further.
return list(result)
finally:
# done with the cursor
cursor.close()
return result
因为在SQLCompiler
中的__init__
函数中有self.connection = connection
,这里的connection
就是DatabaseWrapper
实例,所以cursor = self.connection.cursor()
即是DatabaseWrapper
中的cursor
方法
def cursor(self):
"""
Creates a cursor, opening a connection if necessary.
"""
self.validate_thread_sharing()
if self.queries_logged:
cursor = self.make_debug_cursor(self._cursor())
else:
cursor = self.make_cursor(self._cursor())
return cursor
这里重点关注self._cursor()
方法,即django/db/backends/base/base.py
中的_corsor
方法。
def _cursor(self):
self.ensure_connection() # 重点关注
with self.wrap_database_errors:
return self.create_cursor()
重点关注self.ensure_connection()
方法
def ensure_connection(self):
"""
Guarantees that a connection to the database is established.
"""
if self.connection is None:
with self.wrap_database_errors:
self.connect()
self.connection
在BaseDatabaseWrapper
的__init__
函数中为None
,所以执行self.connect()
方法
Database
就是MySQLdb
def connect(self):
"""Connects to the database. Assumes that the connection is closed."""
# Check for invalid configurations.
self.check_settings()
# In case the previous connection was closed while in an atomic block
self.in_atomic_block = False
self.savepoint_ids = []
self.needs_rollback = False
# Reset parameters defining when to close the connection
max_age = self.settings_dict['CONN_MAX_AGE']
self.close_at = None if max_age is None else time.time() + max_age
self.closed_in_transaction = False
self.errors_occurred = False
# Establish the connection
conn_params = self.get_connection_params()
self.connection = self.get_new_connection(conn_params) # 重点关注
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
self.init_connection_state()
connection_created.send(sender=self.__class__, connection=self)
self.run_on_commit = []
这里重点关注self.get_new_connection
,这个函数其实是在django/db/backends/mysql/base.py
中的146行的DatabaseWrapper
中的get_new_connection
被重写,所以也就是
def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
conn.encoders[SafeText] = conn.encoders[six.text_type]
conn.encoders[SafeBytes] = conn.encoders[bytes]
return conn
Database
为import MySQLdb as Database
,哈哈其实发现到最后django
的ORM
是调用第三方的库,connect
方法就是MySQLdb/__init__.py
中的Connect
方法,那么最后连接到底是哪个mysql
呢,最终就是看conn_params
,在connect
方法中可以发现conn_params = get_connection_params()
,那么看看get_connection_params
函数:
def get_connection_params(self):
kwargs = {
'conv': django_conversions,
'charset': 'utf8',
}
if six.PY2:
kwargs['use_unicode'] = True
settings_dict = self.settings_dict
if settings_dict['USER']:
kwargs['user'] = settings_dict['USER']
if settings_dict['NAME']:
kwargs['db'] = settings_dict['NAME']
if settings_dict['PASSWORD']:
kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST'].startswith('/'):
kwargs['unix_socket'] = settings_dict['HOST']
elif settings_dict['HOST']:
kwargs['host'] = settings_dict['HOST']
if settings_dict['PORT']:
kwargs['port'] = int(settings_dict['PORT'])
# We need the number of potentially affected rows after an
# "UPDATE", not the number of changed rows.
kwargs['client_flag'] = CLIENT.FOUND_ROWS
kwargs.update(settings_dict['OPTIONS'])
return kwargs
全都来自settings_dict
,我们再回到ConnectionHandler
的__getitem__
方法,
def __getitem__(self, alias):
if hasattr(self._connections, alias):
return getattr(self._connections, alias)
self.ensure_defaults(alias)
self.prepare_test_settings(alias)
db = self.databases[alias]
backend = load_backend(db['ENGINE'])
conn = backend.DatabaseWrapper(db, alias)
setattr(self._connections, alias, conn)
return conn
回过头来从上面可以看出,其实settings_dict = db = self.databases[alias]
,就是settings
中DATABASES
中定义alias
的字典的值,alias
默认为default
,所以也就是类似下面的一个东东。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dbname',
'USER': 'dbuser',
'PASSWORD': 'dbpasswd',
'HOST': 'dbhost',
'PORT': 'dbport',
}
}
所以SQLcompiler
中的execute_sql
方法中调用cursor = self.connection.cursor()
中的cursor
就是django/db/backends/base/base.py
中cursor
方法的返回值,
def cursor(self):
"""
Creates a cursor, opening a connection if necessary.
"""
self.validate_thread_sharing()
if self.queries_logged:
cursor = self.make_debug_cursor(self._cursor())
else:
cursor = self.make_cursor(self._cursor())
return cursor
那么cursor
到底是啥呢,其实make_cursor
只是对self._cursor()
做简单的封装,真正的执行时的cursor
还是self._cursor()
的返回值。
def _cursor(self):
self.ensure_connection()
with self.wrap_database_errors:
return self.create_cursor()
所以关注create_cursor
方法就好。create_cursor
是django/db/backends/mysql/base.py
中的DatabaseWrapper
中的create_cursor
方法。
def create_cursor(self):
cursor = self.connection.cursor()
return CursorWrapper(cursor)
所以从上面的分析中可以看出,self.connection
是self.connect
函数中创建的connection
,也就是MySQLdb
中的connection
,所以cursor
就是MySQLdb.connection.cursor
,也就是根据setting.py
中的DATABASES
值指定的mysql
数据库,建立的连接和游标。
对于django
中的mysql
来说,执行流程是,SQLcompliter
或者其子类执行sql语句时,首先用cursor = self.connection.cursor()
获取游标cursor
,因为self.connection
是DatabaseWrapper
类,所以调用DatabaseWrapper
的cursor
函数,这个函数会根据传进来的db
信息(settings
中的DATABASES
信息),首先去调用MySQLdb
中的库新建一个connection
连接,获取游标cursor
,然后借用cursor
去执行sql
。其实mysql
的查询的过程主要从query.get_compiler
开始的,也就是queryset.query.get_compiler(using)
。最后我们回到obj
上来。
obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
,关注from_db
方法
def from_db(cls, db, field_names, values):
if len(values) != len(cls._meta.concrete_fields):
values = list(values)
values.reverse()
values = [values.pop() if f.attname in field_names else DEFERRED for f in cls._meta.concrete_fields]
new = cls(*values)
new._state.adding = False
new._state.db = db
return new
特喵的,到头来还是一个model
对象,对象中的值来源于values
,values
从哪儿来的呢?values = row[model_fields_start:model_fields_end]
,而row
从哪儿来的?for row in compiler.results_iter(results):
,也就是最终来源于results
,results = compiler.execute_sql()
,execute_sql
就是去连接的数据库去查询结果,我们再看一遍execute_sql
;
def execute_sql(self, result_type=MULTI):
"""
Run the query against the database and returns the result(s). The
return value is a single data item if result_type is SINGLE, or an
iterator over the results if the result_type is MULTI.
result_type is either MULTI (use fetchmany() to retrieve all rows),
SINGLE (only retrieve a single row), or None. In this last case, the
cursor is returned if any query is executed, since it's used by
subclasses such as InsertQuery). It's possible, however, that no query
is needed, as the filters describe an empty set. In that case, None is
returned, to avoid any unnecessary database interaction.
"""
if not result_type:
result_type = NO_RESULTS
try:
sql, params = self.as_sql()
if not sql:
raise EmptyResultSet
except EmptyResultSet:
if result_type == MULTI:
return iter([])
else:
return
#重点关注
cursor = self.connection.cursor()
try:
cursor.execute(sql, params)
except Exception:
cursor.close()
raise
if result_type == CURSOR:
# Caller didn't specify a result_type, so just give them back the
# cursor to process (and close).
return cursor
if result_type == SINGLE:
try:
val = cursor.fetchone()
if val:
return val[0:self.col_count]
return val
finally:
# done with the cursor
cursor.close()
if result_type == NO_RESULTS:
cursor.close()
return
#重点关注
result = cursor_iter(
cursor, self.connection.features.empty_fetchmany_value,
self.col_count
)
if not self.connection.features.can_use_chunked_reads:
try:
# If we are using non-chunked reads, we return the same data
# structure as normally, but ensure it is all read into memory
# before going any further.
return list(result)
finally:
# done with the cursor
cursor.close()
return result
这里我们只要重点关注下面这段就好了。
result = cursor_iter(
cursor, self.connection.features.empty_fetchmany_value,
self.col_count
)
那么cursor_iter
方法做了什么呢?
def cursor_iter(cursor, sentinel, col_count):
"""
Yields blocks of rows from a cursor and ensures the cursor is closed when
done.
"""
try:
for rows in iter((lambda: cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)),
sentinel):
yield [r[0:col_count] for r in rows]
finally:
cursor.close()
哈哈,看到这里还不明白的话,建议用MySQLdb
库调用一下 cursor.fetchmany
试试就知道了。cursor.fetchmany
返回的就是从mysql
中查询获取的结果。所以,其实也就是将从mysql
中获取到的数据封装成对应的model
对象。
2.4 以models.objects.first()
为例分析
上面我们只分析常用的model.obejcts.all
这一种情况,那么对于model.obejcts.first()
,model.obejcts.get()
等其他情况呢?
先看model.obejcts.first()
,也就是看first
函数:
def first(self):
"""
Returns the first object of a query, returns None if no match is found.
"""
objects = list((self if self.ordered else self.order_by('pk'))[:1])
if objects:
return objects[0]
return None
这里重点关注objects[0]
,其实对于objects
不管是self
,还是self.order_by
的返回值,他们都是一个QuerySet
类,那么objects[0]
就会调用django/db/models/query.py
中159行的QuerySet
的__getitem__
函数:
def __getitem__(self, k):
"""
Retrieves an item or slice from the set of results.
"""
if not isinstance(k, (slice,) + six.integer_types):#k为 int,所以这里一定通过
raise TypeError
assert ((not isinstance(k, slice) and (k >= 0)) or
(isinstance(k, slice) and (k.start is None or k.start >= 0) and
(k.stop is None or k.stop >= 0))), \
"Negative indexing is not supported."
if self._result_cache is not None:
return self._result_cache[k]
if isinstance(k, slice):
qs = self._clone()
if k.start is not None:
start = int(k.start)
else:
start = None
if k.stop is not None:
stop = int(k.stop)
else:
stop = None
qs.query.set_limits(start, stop)
return list(qs)[::k.step] if k.step else qs
qs = self._clone()
qs.query.set_limits(k, k + 1)
return list(qs)[0]
当self._result_cache
不为空的时候,这部分就不在细说,当self._result_cache
为None
的时候,那么list(qs)[0]
中的list()
也会去调用qs
(即QuerySet
类),这里有体现了一个python的内置魔法,那就是list()
会去调用qs
对象中的__iter__
函数;那么就看看QuerySet
的__iter__()
函数:
def __iter__(self):
"""
The queryset iterator protocol uses three nested iterators in the
default case:
1. sql.compiler:execute_sql()
- Returns 100 rows at time (constants.GET_ITERATOR_CHUNK_SIZE)
using cursor.fetchmany(). This part is responsible for
doing some column masking, and returning the rows in chunks.
2. sql/compiler.results_iter()
- Returns one row at time. At this point the rows are still just
tuples. In some cases the return values are converted to
Python values at this location.
3. self.iterator()
- Responsible for turning the rows into model objects.
"""
self._fetch_all()
return iter(self._result_cache)
其实又回到for循环model.objects.all
的常规用法了,后续就不再赘述。
2.5 以models.objects.get()
为例分析
关于model.obejcts.get()
这一类的操作,其实也是看get()
函数,
def get(self, *args, **kwargs):
"""
Performs the query and returns a single object matching the given
keyword arguments.
"""
clone = self.filter(*args, **kwargs)
if self.query.can_filter() and not self.query.distinct_fields:
clone = clone.order_by()
num = len(clone) #重点关注
if num == 1:
return clone._result_cache[0]
if not num:
raise self.model.DoesNotExist(
"%s matching query does not exist." %
self.model._meta.object_name
)
raise self.model.MultipleObjectsReturned(
"get() returned more than one %s -- it returned %s!" %
(self.model._meta.object_name, num)
)
这里关注 num = len(clone)
,len()
也会触发另外一个python魔法,那就是len()
回去调用__len__()
函数,所以看看def __len__(self)
函数的源码:
def __len__(self):
self._fetch_all() #重点关注
return len(self._result_cache)
到了这里又跟上面的一样啦,因为get
返回的是clone._result_cache[0]
,这里的_result_cache
只不过是被self.filter
函数给过滤后的结果。其实这里面所有的操作,只要关注self._fetch_all()
被谁调用了,都能反向推到获得。
其实这里还有一点不明白,每次执行相同的execute_sql
函数,他怎么知道是增删查改中的哪一种呢?对应的SQL
语句从而来呢?其实看看execute_sql
中调用的self.as_sql
函数就好了。每一个继承自SQLCompiler
的不同的类的as_sql
返回的SQL
语句都是特定的,只是要替换其中的表名等参数,例如:
class SQLDeleteCompiler(SQLCompiler):
def as_sql(self):
"""
Creates the SQL for this query. Returns the SQL string and list of
parameters.
"""
assert len([t for t in self.query.tables if self.query.alias_refcount[t] > 0]) == 1, \
"Can only delete from one table at a time."
qn = self.quote_name_unless_alias
result = ['DELETE FROM %s' % qn(self.query.tables[0])]
where, params = self.compile(self.query.where)
if where:
result.append('WHERE %s' % where)
return ' '.join(result), tuple(params)
class SQLUpdateCompiler(SQLCompiler):
def as_sql(self):
"""
Creates the SQL for this query. Returns the SQL string and list of
parameters.
"""
self.pre_sql_setup()
if not self.query.values:
return '', ()
qn = self.quote_name_unless_alias
values, update_params = [], []
for field, model, val in self.query.values:
if hasattr(val, 'resolve_expression'):
val = val.resolve_expression(self.query, allow_joins=False, for_save=True)
if val.contains_aggregate:
raise FieldError("Aggregate functions are not allowed in this query")
elif hasattr(val, 'prepare_database_save'):
if field.remote_field:
val = field.get_db_prep_save(
val.prepare_database_save(field),
connection=self.connection,
)
else:
raise TypeError(
"Tried to update field %s with a model instance, %r. "
"Use a value compatible with %s."
% (field, val, field.__class__.__name__)
)
else:
val = field.get_db_prep_save(val, connection=self.connection)
# Getting the placeholder for the field.
if hasattr(field, 'get_placeholder'):
placeholder = field.get_placeholder(val, self, self.connection)
else:
placeholder = '%s'
name = field.column
if hasattr(val, 'as_sql'):
sql, params = self.compile(val)
values.append('%s = %s' % (qn(name), sql))
update_params.extend(params)
elif val is not None:
values.append('%s = %s' % (qn(name), placeholder))
update_params.append(val)
else:
values.append('%s = NULL' % qn(name))
if not values:
return '', ()
table = self.query.tables[0]
result = [
'UPDATE %s SET' % qn(table),
', '.join(values),
]
where, params = self.compile(self.query.where)
if where:
result.append('WHERE %s' % where)
return ' '.join(result), tuple(update_params + params)
什么时候调用那些不同的继承自SQLCompiler
的类呢?在QuerySet
中我们看update
函数
def update(self, **kwargs):
"""
Updates all elements in the current QuerySet, setting all the given
fields to the appropriate values.
"""
assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken."
self._for_write = True
query = self.query.clone(sql.UpdateQuery) #重点注意
query.add_update_values(kwargs)
with transaction.atomic(using=self.db, savepoint=False):
rows = query.get_compiler(self.db).execute_sql(CURSOR)
self._result_cache = None
return rows
update.alters_data = True
最后调用的是SQLUpdateCompiler
类,所以query.get_compiler(self.db)
其实是根据query
的类型,Query
的类型在django/db/models/sql/subquerys.py
有定义。所以其实也就是query
的类型决定了SQL
语句,query
的类型在那儿被决定的呢,就是在model.obejcts.get(pk).update
,采用UpdateQuery
,model.obejcts.get(pk).delete
,其他同理。他只不过是将普通的Query
类进行clone
,然后更改自身的__class__
属性。
2.6 小结
所以,整个django中mysql操作的关键是query.get_compiler(self.using).execute_sql()
;首先query
在上面被对应的操作(update,delete, insert等)
所决定,self.using
决定操作的数据库,query
类型决定execute_sql
执行的SQL
语句,所以整个就被决定下来了。
执行完操作的最后一步就是save()
函数了,save()
函数在django/db/models/base.py
第718行,暂时觉得没啥需要将分析的,就不做过多的分析了。
其实ORM
就是用某一种编程语言,基于该语言对应的数据库的库进行封装。
三 Qeuryset分析
每个Model
都有一个默认的manager
实例,名为objects
。QuerySet
有两种来源:通过manager
的方法得到、通过QuerySet
的方法得到。
mananger
的方法和QuerySet
的方法大部分同名,同意思,如filter()
,update()
等,但也有些不同,如manager
有create()
、get_or_create()
,而QuerySet
有delete()
等,看源码就可以很容易的清楚Manager
类与Queryset
类的关系,Manager
类的绝大部分方法是基于Queryset
的。一个QuerySet
包含一个或多个model instance
。QuerySet
类似于Python中的list,list的一些方法QuerySet
也有,比如切片,遍历。
>>> from userex.models import UserEx
>>> type(UserEx.objects)
<class ‘django.db.models.manager.Manager’>
>>> a = UserEx.objects.all()
>>> type(a)
<class ‘django.db.models.query.QuerySet’>
QuerySet
是延迟获取的,只有当用到这个QuerySet
时,才会查询数据库求值。也就是懒加载!
另外,查询到的QuerySet
又是缓存的,当再次使用同一个QuerySet
时,并不会再查询数据库,而是直接从缓存获取(不过,有一些特殊情况)。一般而言,当对一个没有求值的QuerySet
进行的运算,返回的是QuerySet
、ValuesQuerySet
、ValuesListQuerySet
、Model
实例时,一般不会立即查询数据库;反之,当返回的不是这些类型时,会查询数据库。下面介绍几种(并非全部)对QuerySet
求值的场景。
3.1 QuerySet
求值的场景
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
遍历queryset
a = Entry.objects.all()
for e in a:
print (e.headline)
当遍历一开始时,先从数据库执行查询select * from Entry
得到a
,然后再遍历a
。注意:这里只是查询Entry
表,返回的a的每条记录只包含Entry
表的字段值,不管Entry
的model
中是否有onetoone
、onetomany
、manytomany
字段,都不会关联查询。这遵循的是数据库最少读写原则。我们修改一下代码,如下,遍历一开始也是先执行查询得到a
,但当执行print (e.blog.name)
时,还需要再次查询数据库获取blog
实体。
from django.db import connection
l = connection.queries #l是一个列表,记录SQL语句
a = Entry.objects.all()
for e in a:
print (e.blog.name)
len(l)
遍历时,每次都要查询数据库,l
长度每次增1,Django提供了方法可以在查询时返回关联表实体,如果是onetoone
或onetomany
,那用select_related
,不过对于onetomany
,只能在主表(定义onetomany
关系的那个表)的manager
中使用select_related
方法,即通过select_related
获取的关联对象是model instance
,而不能是QuerySet
,如下,e.blog
就是model instance
。对于onetomany
的反向和manytomany
,要用prefetch_related
,它返回的是多条关联记录,是QuerySet
。
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
len(l)
可以看到从开始到结束,l的长度只增加1。另外,通过查询connection.queries[-1]
可以看到Sql
语句用了join
。
queryset切片
切片不会立即执行,除非显示指定了步长,如:
a= Entry.objects.all()[0:10:2]
步长为2。
序列化,即Pickling
序列化QuerySet
很少用。
repr()
和str()
功能相似,将对象转为字符串,很少用。
len()
计算QuerySet
元素的数量,并不推荐使用len()
,除非QuerySet
是求过值的(即evaluated
),否则,用QuerySet.count()
获取元素数量,这个效率要高。
list()
将QuerySet
转为list
。list(qs)
bool()
判断qs
是否为空。
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
同样不建议这种方法判断是否为空,而应该使用QuerySet.exists()
,查询效率高。
3.2 queryset的方法
数据库的常用操作就四种:增、删、改、查
,QuerySet的方法涉及删、改、查
。后面还会讲model
对象的方法,model
方法主要是增、删、改
、还有调用model实例
的字段。
3.2.1 delete()
原型:delete()
返回:None
相当于delete-from-where, delete-from-join-where。先filter,然后对得到的QuerySet执行delete()方法就行了,它会同时删除关联它的那些记录,比如我删除记录表1中的A记录,表2中的B记录中有A的外键,那同时也会删除B记录,那ManyToMany关系呢?对于ManyToMany,删除其中一方的记录时,会同时删除中间表的记录,即删除双方的关联关系。由于有些数据库,如Sqlite不支持delete与limit连用,所以在这些数据库对QuerySet的切片执行delete()会出错。
>>> a = UserEx.objects.filter(is_active=False)
>>> b = a[:3]
>>> b.delete() #执行时会报错
解决:UserEx.objects.filter(pk__in=b).delete()
我们在用django开发项目的的时候,经常要和数据库打交道,而django操作数据库非常的方便,有很多非常简便的方法让你能够快速的从数据库里获得你想要的数据。今天我就介绍给大家一个很好用的方法,那就是django in操作了我们经常查数据库的时候要把几个符合条件的记录都给查出来,那就要用到sql语句的in操作,那django怎么来执行数据库的in操作呢?接着看下面把。有2个方法可以很好的实现:1直接用filter语句里的方法来实现2用到extra方法比如我们要执行:
select * from table where id in (3, 4, 5, 20)
用上面2个方法分别怎么操作呢?
django filter:Blog.objects.filter(pk__in=[3,4,5,20])
django extra:Blog.objects.extra(where=['id IN (3, 4, 5, 20)'])
3.2.2 update()
批量修改,返回修改的记录数。不过update()中的键值对的键只能是主表中的字段,不能是关联表字段,如下:
Entry.objects.update(blog__name='foo') #错误,无法修改关联表字段,只能修改Entry表的字段
Entry.objects.filter(blog__name='foo').update(comments_on=False) #正确
最好的方法是先filter,查询出QuerySet,然后再执行QuerySet.update()。
由于有些数据库,不支持update与limit连用,所以在这些数据库对QuerySet的切片执行update()会出错。
3.2.3 filter(**kwargs)
、exclude(**kwargs)
、get(**kwargs)
相当于select-from-where
,select-from-join-where
,很多网站读数据库操作最多。可以看到,filter()
的参数是变个数的键值对,而不会出现>,<,!=
等符号,这些符号分别用__gt
,__lt
,~Q
或exclude()
,不过对于!=
,建议使用Q
查询,更不容易出错。可以使用双下划线对OneToOne、OneToMany、ManyToMany进行关联查询和反向关联查询,而且方法都是一样的,如:
>>> Entry.objects.filter(blog__name='Beatles Blog') #限定外键表的字段
#下面是反向连接,不过要注意,这里不是entry_set,entry_set是Blog instance的一个属性,代表某个Blog object
#的关联的所有entry,而QuerySet的方法中反向连接是直接用model的小写,不要把两者搞混。It works backwards,
#too. To refer to a “reverse” relationship, just use the lowercase name of the model.
>>> Blog.objects.filter(entry__headline__contains='Lennon')
>>> Blog.objects.filter(entry__authors__name='Lennon') #ManyToMany关系,反向连接
>>> myblog = Blog.objects.get(id=1)
>>> Entry.objects.filter(blog=myblog) #正向连接。与下面一句等价,既可以用实体,也可以用
#实体的主键,其实即使用实体,也是只用实体的主键而已。这两种方式对OneToOne、
#OneToMany、ManyToMany的正向、反向连接都适用。
>>> Entry.objects.filter(blog=1) #我个人不建议这样用,对于create(),不支持这种用法
>>> myentry = Entry.objects.get(id=1)
>>> Blog.objects.filter(entry=myentry) #ManyToMany反向连接。与下面两种方法等价
>>> Blog.objects.filter(entry=1)
>>> Blog.objects.filter(entry_id=1) #适用于OneToOne和OneToMany的正向连接
OneToOne的关系也是这样关联查询,可以看到,Django对OneToOne、OneToMany、ManyToMany关联查询及其反向关联查询提供了相同的方式,真是牛逼啊。对于OneToOne、OneToMany的主表,也可以使用下面的方式
Entry.objects.filter(blog_id=1),因为blog_id是数据库表Entry的一个字段, 这条语句与Entry.objects.filter(blog=blog1)生成的SQL是完全相同的。
与filter类似的还有exclude(**kwargs)
方法,这个方法是剔除,相当于select-from-where not
。可以使用双下划线对OneToOne、OneToMany、ManyToMany
进行关联查询和反向关联查询,方法与filter()
中的使用方法相同。
>>> Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
转为SQL为
SELECT *
FROM Entry
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
3.2.4 SQL其它关键字在django中的实现
在SQL中,很多关键词在删、改、查时都是可以用的,如order by、 like、in、join、union、and、or、not等等,我们以查询为例,说一下django如何映射SQL的这些关键字的(查、删、改中这些关键字的使用方法基本相同)。
F类(无对应SQL关键字)
前面提到的filter/exclude中的查询参数值都是常量,如果我们想比较model的两个字段怎么办呢?Django也提供了方法,F类,F类实例化时,参数也可以用双下划线,也可以逻辑运算,如下:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
>>> Entry.objects.filter(authors__name=F('blog__name'))
Q类(对应and/or/not
)
如果有or
等逻辑关系呢,那就用Q类,filter中的条件可以是Q对象与非Q查询混和使用,但不建议这样做,因为混和查询时Q对象要放前面,这样就有难免忘记顺序而出错,所以如果使用Q对象,那就全部用Q对象。Q对象也很简单,就是把原来filter中的各个条件分别放在一个Q()即可,不过我们还可以使用或与非,分别对应符号为”|”和”&”和”~”
,而且这些逻辑操作返回的还是一个Q对象,另外,逗号是各组条件的基本连接符,也是与的关系,其实可以用&代替(在python manage.py shell
测试过,&代替逗号,执行的SQL是一样的),不过那样的话可读性会很差,这与我们直接写SQL时,各组条件and时用换行一样,逻辑清晰:
from django.db.models import Q
>>> Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who') #正确,但不要这样混用
>>> Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
Q(question__startswith='Who')) #推荐,全部是Q对象
>>> Poll.objects.get( (Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))&
Q(question__startswith='Who')) #与上面语句同意,&代替”,”,可读性差
Q类中时应该可以用F类,待测试。
annotate
(无对应SQL关键字)
函数原型annotate(*args, **kwargs)
返回QuerySet
往每个QuerySet
的model instance
中加入一个或多个字段,字段值只能是聚合函数,因为使用annotate时,会用group by
,所以只能用聚合函数。聚合函数可以像filter
那样关联表,即在聚合函数中,Django对OneToOne
、OneToMany
、ManyToMany
关联查询及其反向关联提供了相同的方式,见下面例子。
>>> from django.contrib.auth.models import User
>>> from django.db.models import Count
#计算每个用户的userjob数量,字段命名为ut_num,返回的QuerySet中的每个object都有
#这个字段。在UserJob中定义User为外键,在Job中定义与User是ManyToMany
>>> a = User.objects.filter(is_active=True, userjob__is_active=True). annotate(n=Count(‘userjob’)) #一对多反向连接
>>> b = User.objects.filter(is_active=True, job__is_active=True).annotate(n=Count(‘job__name’)) #多对多反向连接,User与Job是多对多
>>> len(a) #这里才会对a求值
>>> len(b) #这里才会对b求值
a对应的SQL语句为(SQL中没有为表起别名,u、ut是我加的):
select auth.user.*,Count(ut.id) as ut_num
from auth_user as u
left outer join job_userjob as ut on u.id = ut.user_id
where u.is_active=True and ut.is_active=True
group by u.*
b对应的SQL语句为(SQL中没有为表起别名,u、t、r是我加的):
select u.*,Count(t.name) as n
from auth_user as u
left outer join job_job_users as r on u.id=r.user_id
left outer join job_job as t on r.job_id=t.id
where t.is_active=True and u.is_active=True
group by u.*
order_by
——对应order by
函数原型 order_by(*fields)
返回QuerySet
正向的反向关联表跟filter
的方式一样。如果直接用字段名,那就是升序asc
排列;如果字段名前加-
,就是降序desc
distinct
——对应distinct
原型 distinct()
一般与values()
、values_list()
连用,这时它返回ValuesQuerySet
、ValuesListQuerySet
这个类跟列表很相似,它的每个元素是一个字典。它没有参数(其实是有参数的,不过,参数只在PostgreSQL上起作用)。使用方法为:
>>> a=Author.objects.values_list(name).distinct()
>>> b=Author.objects.values_list(name,email).distinct()
对应的SQL分别为:
select distinct name
from Author
和
select distinct name,email
from Author
distinct的了解:http://www.cnblogs.com/rainman/archive/2013/05/03/3058451.html
values()和values_list()
——对应select 某几个字段
可以参考的连接:http://blog.csdn.net/tmpbook/article/details/50297403
函数原型values(*field)
, values_list(*field)
返回ValuesQuerySet
, ValuesListQuerySet
Author.objects.filter(**kwargs)
对应的SQL只返回主表(即Author表)的所有字段值,即使在查询时关联了其它表,关联表的字段也不会返回,只有当我们通过Author instance
用关联表时,Django才会再次查询数据库获取值。当我们不用Author instance
的方法,且只想返回几个字段时,就要用values()
,它返回的是一个ValuesQuerySet
对象,它类似于一个列表,不过,它的每个元素是字典。而values_list()
跟values()
相似,它返回的是一个ValuesListQuerySet
,也类型于一个列表,不过它的元素不是字典,而是元组。一般的,当我们不需要model instance
的方法且返回多个字段时,用values(*field)
,而返回单个字段时用values_list(‘field’,flat=True)
,这里flat=True
是要求每个元素不是元组,而是单个值,见下面例子。而且我们可以返回关联表的字段,用法跟filter
中关联表的方式完全相同。
>>> a = User.objects.values(‘id’,’username’,’userex__age’)
>>> type(a)
<class ‘django.db.models.query.ValuesQuerySet’>
>>> a
[{‘id’:0,’username’:u’test0’,’ userex__age’: 20},{‘id’:1,’username’:u’test1’,’userex__age’: 25},
{‘id’:2,’username’:u’test2’, ’ userex__age’: 28}]
>>> b= User.objects.values_list(’username’,flat=True)
>>> b
[u’test0’, u’test1’ ,u’test2’]
select_related()
——对应返回关联记录实体
原型select_related(*filed)
返回QuerySet
它可以指定返回哪些关联表model instance,这里的field跟filter()中的键一样,可以用双下划线,但也有不同,You can refer to any ForeignKey or OneToOneField relation in the list of fields passed to select_related(),QuerySet中的元素中的OneToOne关联及外键对应的是都是关联表的一条记录,如my_entry=Entry.objects.get(id=1)
,my_entry.blog
就是关联表的一条记录的对象。select_related()
不能用于OneToMany的反向连接,和ManyToMany,这些都是model的一条记录对应关联表中的多条记录。前面提到了对于a = Author.objects.filter(**kwargs)
这类语句,对应的SQL只返回主表,即Author的所有字段,并不会返回关联表字段值,只有当我们使用关联表时才会再查数据库返回,但有些时候这样做并不好。看下面两段代码,这两段代码在1.1中提到过。在代码1中,在遍历a前,先执行a对应的SQL,拿到数据后,然后再遍历a,而遍历过程中,每次都还要查询数据库获取关联表。代码2中,当遍历开始前,先拿到Entry的QuerySet
,并且也拿到这个QuerySet的每个object中的blog对象,这样遍历过程中,就不用再查询数据库了,这样就减少了数据库读次数。
代码1
a = Entry.objects.all()
for e in a:
print (e.blog.name)
代码2
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
prefetch_related(*field)
——对应返回关联记录实体的集合
函数原型prefetch_related(*field)
返回的是QuerySet
这里的field
跟filter()
中的键一样,可以用双下划线。用于OneToMany
的反向连接,及ManyToMany
。其实,prefetch_related()
也能做select_related()
的事情,但由于策略不同,可能相比select_related()
要低效一些,所以建议还是各管各擅长的。select_related
是用select ……join
来返回关联的表字段,而prefetch_related
是用多条SQL语句的形式查询,一般,后一条语句用IN
来调用上一句话返回的结果。
class Restaurant(models.Model):
pizzas = models.ManyToMany(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
先用select_related
查到best_pizza
对象,再用prefetch_related
从best_pizza
查出toppings
extra()
——实现复杂的where
子句
函数原型:extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
基本上,查询时用django提供的方法就够用了,不过有时where子句
中包含复杂的逻辑,这种情况下django提供的方法可能不容易做到,还好,django有extra()
, extra()
中直接写一些SQL语句。不过,不同的数据库用的SQL有些差异,所以尽可能不要用extra()
。需要时再看使用方法吧。
aggregate(*args, **kwargs)
——对应聚合函数
参数为聚合函数,最好用**kwargs
的形式,每个参数起一个名字。
该函数与annotate()
有何区别呢?annotate
相当于aggregate()
和group by
的结合,对每个group
执行aggregate()
函数。而单独的aggregate()
并没有group by
。
>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry')) #这是用*args的形式,最好不要这样用
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) #这是用**kwargs的形式
{'number_of_entries': 16}
至此,我们总结了QuerySet方法返回的数据形式,主要有五种。
第一种:返回QuerySet,每个object只包含主表字段;
第二种:返回QuerySet,每个object除了包含主表所有字段,还包含某些关联表的object,这种情况要用select_related()和prefetch_related(),可以是任意深度(即任意多个双下划线)的关联,通常一层关联和二层关联用的比较多;
第三种:返回ValuesQuerySet, ValuesListQuerySet,它们的每个元素包含若干主表和关联表的字段,不包含任何实体和关联实例,这种情况要用values()和values_list();
第四种:返回model instance;
第五种:单个值,如aggregate()方法。
exists()、count()、len()
如果只是想知道一个QuerySet
是否为空,而不想获取QuerySet
中的每个元素,那就用exists()
,它要比len()
、count()
、和直接进行if
判断效率高。如果只想知道一个QuerySet
有多大,而不想获取QuerySet
中的每个元素,那就用count()
;如果已经从数据库获取到了QuerySet
,那就用len()
。
上面也有提到。
contains/startswith/endswith
——对应like
字段名加双下划线,除了它,还有icontains
,即Case-insensitive contains
,这个是大小写不敏感的,这需要相应数据库的支持。有些数据库需要设置才能支持大小写敏感。
in
——对应in
字段名加双下划线。
exclude(field__in=iterable)
——对应not in
iterable
是可迭代对象
gt/gte/lt/lte
——对应于`>,>=,<,<=
字段名加双下划线
range
——对应于between and
字段名加双下划线,range
后面值是列表
isnull
——对应于is null
Entry.objects.filter(pub_date__isnull=True)对应的SQL为SELECT ... WHERE pub_date IS NULL;
QuerySet
切片——对应于limit
QuerySet
的索引只能是非负整数,不支持负整数,所以QuerySet[-1]
错误
a=Entry.objects.all()[5:10]
b=len(a)
执行Entry.objects.all()[5:8]
,对于不同的数据库,SQL语句不同,
Sqlite 的SQL语句为
select * from tablename limit 3 offset 5;
MySQL的SQL语句为
select * from tablename limit 5,3;
3.2.5 经验
queryset
的关联查询:
qs = Disk.objects.filter(hostType=obj).values(
"id","diskEssenceType__name", "diskOsType__name", "hostType__name"
)
class HostTypeSerializer(ModelSerializer):
cputypes = CPUTypeSerializer(many=True, read_only=True)
memtypes = MEMTypeSerializer(many=True, read_only=True)
bandwidths = BandWidthTypeSerializer(many=True, read_only=True)
#disks = DiskSerializer(many=True, read_only=True)
disks = serializers.SerializerMethodField()
class Meta:
model = HostType
fields = [
"id",
"name",
"cputypes",
"memtypes",
"bandwidths",
"disks",
]
def get_disks(self, obj):
qs = Disk.objects.filter(hostType=obj).values(
"id","diskEssenceType__name", "diskOsType__name", "hostType__name"
)
dict = {"系统盘": [], "数据盘": []}
for item in qs:
if item['diskOsType__name'] == "系统盘":
dict["系统盘"].append(item)
elif item['diskOsType__name'] == "数据盘":
dict["数据盘"].append(item)
disk_list = [{"id":1, "type":"系统盘", "details":[]}, {"id":2, "type":"数据盘", "details":[]}]
for os_disk in dict["系统盘"]:
disk_list[0]["details"].append(os_disk)
for data_dist in dict["数据盘"]:
disk_list[1]["details"].append(data_dist)
return disk_list
作者:廖马儿
链接:https://www.jianshu.com/p/005b72158e26
作者:llicety
链接:https://www.jianshu.com/p/ac87788b55f3
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2020-03-20 常用模块之time、datetime、random