主要问题
Model.objects.create()都完成了哪些操作.
基本功能
根据官方文档django document 的介绍只有一句话:
A convenience method for creating an object and saving it all in one step.
create()是一个将
1) 创建类对象;
2) 保存进数据库.
两个操作合二为一的语句.
示例:
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
和:
p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
属于等价的操作.
这基本上就是create()的常用场景,然而今天的实践中却遇到了奇怪的现象.
问题描述:
model当中存在一个DateTimeField()
class TestModel(models.Model):
test_datefield = models.DateTimeField()
往数据中写入一个实例, 再取出:
In [2] TestModel.objects.create(test_datefield='2018-08-14')
Out[2]: <TestModel: TestModel object (1)>
In [3]: test = TestModel.objects.get(pk=1)
In [4]: test.test_datefield
Out[4]: datetime.datetime(2018, 8, 14, 0, 0, tzinfo=<UTC>)
一切正常, django 的DateTimeField()帮我们完成了从字符串的日期转换为datetime类型,再写入数据库对应格式的过程, 这显然不用我们操心.
存进去时是str类型的数据, 取出来已经是datetime类型.
但当我想用create()的返回值, 对数据库中的日期数据进行操作时:
In [5]: obj = TestModel.objects.create(test_datefield='2018-08-15')
In [6]: obj.test_datefield
Out[6]: '2018-08-15'
In [7]: type(obj.test_datefield)
Out[7]: str
会发现obj的test_datefield字段依然是一个str变量, 与get()方法从数据库取出来的对象对比:
In [8]: type(test)
Out[8]: snippets.models.TestModel
In [9]: type(obj)
Out[9]: snippets.models.TestModel
尽管显示type相同,但是同个字段test_datefield的类型却截然不同,当然也没法在后者当中直接对日期值进行操作, 需使用strptime()函数对str类型进行操作之后才可进行日期相关的操作.
源码分析
class QuerySet:
"""Represent a lazy database lookup for a set of objects."""
def create(self, **kwargs):
"""
Create a new object with the given kwargs, saving it to the database
and returning the created object.
"""
obj = self.model(**kwargs) #调用
self._for_write = True
obj.save(force_insert=True, using=self.db)
return obj
在源码当中已经可以看到原因, 确实如同官方文档所述, 一个create()相当于 一次Model()创建对象, 并调用save()完成保存.(self._for_write = True 表明是一次对数据库的加写锁操作, 防止线程不安全)
The save() method has no return value.
save()本身没有返回值, 仅完成写入数据库的操作(实际上是lazy lookup延时操作, 在此不展开)
因此return的依然是obj,也就是说create()的返回值和调用model()完成初始化的返回值是相同的, 返回值obj并不是从数据库中获取的.这就是obj.test_datefield的值依然是python的基础类型str的原因.
对比get
源码:
def get(self, *args, **kwargs):
"""
Perform the query and return 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)
)
可知get()方法是通过filter()拿到queryset之后再判断结果是否唯一, 因此是从数据库中得到的数据. 这也就是为什么上面get()方法的对象test, test_datefield已经是datetime类型, 因为django的整个处理过程是:
model()创建对象–> 结合使用的数据库类型,对于不同类型field有相关的转换方法–>拼接SQL语句,写入数据库.
从get()中获取的对象已经完成了三个步骤, 而create()的返回值依然是第一个步骤的结果.
结论
在某些场景当中, create()的返回值并不能代替get(). 严格来说, create()的作用应该仅限于为创建对象后调用save()提供一个快捷方式.