创建对象
class Model(**kwargs)¶
关键字参数就是在你的模型中定义的字段的名称。注意,当你实例化一个模型时,Django是绝对不会对数据库进行访问的;若要保存实例化后的数据,你需要调用方法save()
-
在模型类中增加一个类方法:
from django.db import models class Book(models.Model): title = models.CharField(max_length=100) @classmethod def create(cls, title): book = cls(title=title) # do something with the book return book book = Book.create("Pride and Prejudice")
-
在自定义管理器中添加一个方法(推荐):
class BookManager(models.Manager): def create_book(self, title): book = self.create(title=title) # do something with the book return book class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager() book = Book.objects.create_book("Pride and Prejudice")
自定义化模型加载
from_db() 方法用于在模型从数据库加载时自定义模型实例。
db 参数包含数据库的别名,field_names 包含所有加载的字段的名称,values 包含field_names 中每个字段加载的值。field_names 与values 的顺序相同,所以可以使用cls(**(zip(field_names, values))) 来实例化对象。如果模型的所有字段都提供, values 需要被保证其顺序与__init__() 所期望的一致。这表示此时实例可以通过cls(*values) 创建。可以通过cls._deferred 来检查是否提供所有的字段 —— 如果为 False,那么所有的字段都已经从数据库中加载。
此外为了创建新模型,from_db() 方法必须在新实例的属性_state 中设置adding 和db 标识。
下面的示例演示如何保存从数据库中加载进来的字段原始值:
@classmethod def from_db(cls, db, field_names, values): # default implementation of from_db() (could be replaced with super()) 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 ] instance = cls(*values) instance._state.adding = False instance._state.db = db # customization to store the original field values on the instance instance._loaded_values = dict(zip(field_names, values)) return instance
def save(self, *args, **kwargs): # Check how the current values differ from ._loaded_values. For example, # prevent changing the creator_id of the model. (This example doesn't # support cases where 'creator_id' is deferred). if not self._state.adding and ( self.creator_id != self._loaded_values['creator_id']): raise ValueError("Updating the value of creator isn't allowed") super(...).save(*args, **kwargs)
上面的示例演示from_db() 的完整实现。当然在这里的from_db() 中完全可以只用super() 调用。
从数据库更新对象
Model.refresh_from_db(using=None, fields=None, **kwargs)
从数据库加载模型的一个值,使用refresh_from_db() 方法, 当不带参数调用时
- 模型所有的非延迟字段更新成数据库中当前的值。
- 之前加载的关联实例,如果关联的值不再合法,将从重新加载的实例中删除。例如,如果重新加载的实例有一个外键到另外一个模型Author,那么如果 obj.author_id !=obj.author.id,obj.author 将被扔掉并在下次访问它时根据obj.author_id 的值重新加载。
注意,只有本模型的字段会从数据库重新加载。其它依赖数据库的值不会重新加载,例如聚合的结果。重新加载使用的数据库与实例加载时使用的数据库相同,如果实例不是从数据库加载的则使用默认的数据库。可以使用using 参数来强制指定重新加载的数据库。可以回使用fields 参数强制设置加载的字段
编写测试:
from django.db.models import F obj = MyModel.objects.create(var=1) MyModel.objects.filter(pk=obj.pk).update(var=F('var') + 1) print(obj.var) # 1 obj.refresh_from_db() print(obj.var) # 2
自定义延迟字段加载方法:
class ExampleModel(models.Model): def refresh_from_db(self, using=None, fields=None, **kwargs): # fields contains the name of the deferred field to be # loaded. if fields is not None: fields = set(fields) deferred_fields = self.get_deferred_fields() # If any deferred field is going to be loaded if fields.intersection(deferred_fields): # then load all of them fields = fields.union(deferred_fields) super(ExampleModel, self).refresh_from_db(using, fields, **kwargs)
Model.get_deferred_fields()
一个辅助方法,它返回一个集合,包含模型当前所有延迟字段的属性名称。
验证对象
验证一个模型涉及三个步骤:
- 验证模型的字段 —— Model.clean_fields()
- 验证模型的完整性 —— Model.clean()
- 验证模型的唯一性 —— Model.validate_unique()
当你调用模型的full_clean() 方法时,这三个方法都将执行
当你使用ModelForm时,is_valid() 将为表单中的所有字段执行这些验证。更多信息参见ModelForm 文档。 如果你计划自己处理验证出现的错误,或者你已经将需要验证的字段从ModelForm 中去除掉,你只需调用模型的full_clean() 方法。
Model.full_clean(exclude=None, validate_unique=True)
该方法按顺序调用Model.clean_fields()、Model.clean() 和Model.validate_unique()(如果validate_unique 为True),并引发一个ValidationError,该异常的message_dict 属性包含三个步骤的所有错误。
可选的exclude 参数用来提供一个可以从验证和清除中排除的字段名称的列表。ModelForm 使用这个参数来排除表单中没有出现的字段,使它们不需要验证,因为用户无法修正这些字段的错误
注意,当你调用模型的save() 方法时,full_clean() 不会 自动调用。如果你想一步就可以为你手工创建的模型运行验证,你需要手工调用它。例如:
from django.core.exceptions import ValidationError try: article.full_clean() except ValidationError as e: # Do something based on the errors contained in e.message_dict. # Display them to a user, or handle them programmatically. pass
- 1.full_clean() 第一步执行的是验证每个字段
Model.clean_fields(exclude=None)
这个方法将验证模型的所有字段。可选的exclude 参数让你提供一个字段名称列表来从验证中排除。如果有字段验证失败,它将引发一个ValidationError
如果字段没有出现在modelForm中,如何抛出字段特定的验证错误
class Article(models.Model): ... def clean_fields(self, exclude=None): super(Article, self).clean_fields(exclude=exclude) if self.status == 'draft' and self.pub_date is not None: if exclude and 'status' in exclude: raise ValidationError( _('Draft entries may not have a publication date.') ) else: raise ValidationError({ 'status': _( 'Set status to draft if there is not a ' 'publication date.' ), })
- 2.full_clean() 第二步执行的是调用Model.clean()
应该用这个方法来提供自定义的模型验证,以及修改模型的属性。例如,你可以使用它来给一个字段自动提供值,或者用于多个字段需要一起验证的情形
import datetime from django.core.exceptions import ValidationError from django.db import models class Article(models.Model): ... def clean(self): # Don't allow draft entries to have a pub_date. if self.status == 'draft' and self.pub_date is not None: raise ValidationError('Draft entries may not have a publication date.') # Set the pub_date for published items if it hasn't been set already. if self.status == 'published' and self.pub_date is None: self.pub_date = datetime.date.today()
调用模型的save() 方法时不会引起clean() 方法的调用
Model.clean() 引发的ValidationError 异常通过一个字符串实例化,所以它将被保存在一个特殊的错误字典键NON_FIELD_ERRORS中。这个键用于整个模型出现的错误而不是一个特定字段出现的错误:
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS try: article.full_clean() except ValidationError as e: non_field_errors = e.message_dict[NON_FIELD_ERRORS]
若要引发一个特定字段的异常,可以使用一个字典实例化ValidationError,其中字典的键为字段的名称。更新前面的例子,只引发pub_date 字段上的异常:
class Article(models.Model): ... def clean(self): # Don't allow draft entries to have a pub_date. if self.status == 'draft' and self.pub_date is not None: raise ValidationError({'pub_date': 'Draft entries may not have a publication date.'})
在Model.clean() 期间如果你发现多个字段错误,也可以通过字典映射字段名字到错误
raise ValidationError({ 'title': ValidationError(_('Missing title.'), code='required'), 'pub_date': ValidationError(_('Invalid date.'), code='invalid'), })
- 3.full_clean() 将调用 validate_unique 检查模型的唯一性约束
该方法与clean_fields() 类似,只是验证的是模型的所有唯一性约束而不是单个字段的值。可选的exclude 参数允许你提供一个字段名称的列表来从验证中排除。如果有字段验证失败,将引发一个 ValidationError。
注意,如果你提供一个exclude 参数给validate_unique(),任何涉及到其中一个字段的unique_together 约束将不检查
对象保存
将一个对象保存到数据库,需要调用 save()方法:
Model.save([force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None])
class Blog(models.Model): name = models.CharField(max_length=100) def save(self, *args, **kwargs): '''覆盖预定义方法''' # do something # if self.name = .... super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
自增的主键
如果模型具有一个AutoField —— 一个自增的主键 —— 那么该自增的值将在第一次调用对象的save() 时计算并保存:
b1 = Blog(name='hahaha', tagline='heheheh')
b1.id # None
b1.save()
b1.id # 1
b2 = Blog.objects.create(name='blog two', tagline='taggggg')
b2.id # 2
在调用save() 之前无法知道ID 的值,因为这个值是通过数据库而不是Django 计算。 create是直接操作到数据库。
当你保存时,发生了什么?
当你保存一个对象时,Django 执行以下步骤:
-
发出一个pre-save 信号。 发送一个django.db.models.signals.pre_save 信号,以允许监听该信号的函数完成一些自定义的动作。
-
预处理数据。 如果需要,对对象的每个字段进行自动转换。
大部分字段不需要预处理 —— 字段的数据将保持原样。预处理只用于具有特殊行为的字段。例如,如果你的模型具有一个auto_now=True 的DateField,那么预处理阶段将修改对象中的数据以确保该日期字段包含当前的时间戳。(我们的文档还没有所有具有这种“特殊行为”字段的一个列表。)
-
准备数据库数据。 要求每个字段提供的当前值是能够写入到数据库中的类型。
大部分字段不需要数据准备。简单的数据类型,例如整数和字符串,是可以直接写入的Python 对象。但是,复杂的数据类型通常需要一些改动。
例如,DateField 字段使用Python 的 datetime 对象来保存数据。数据库保存的不是datetime 对象,所以该字段的值必须转换成ISO兼容的日期字符串才能插入到数据库中。
-
插入数据到数据库中。 将预处理过、准备好的数据组织成一个SQL 语句用于插入数据库。
-
发出一个post-save 信号。 发送一个django.db.models.signals.post_save 信号,以允许监听听信号的函数完成一些自定义的动作。
Django 如何知道是UPDATE 还是INSERT¶
如何区别调用save() 方法执行的是update还是insert
- 如果对象的主键属性为一个求值为True 的值(例如,非None 值或非空字符串),Django 将执行UPDATE。
- 如果对象的主键属性没有设置或者UPDATE 没有更新任何记录,Django 将执行INSERT。
强制使用INSERT 或UPDATE
- force_insert=True 或 force_update=True 参数给save() 方法
- 在save方法使用update_fields 将强制使用类似force_update 的更新操作
字段值更新
m = MyModel.objects.last()
m.var = F('var') + 1
m.save()
通过更新基于原始字段的值而不是赋予一个新值,可以避免竞态条件而且更快
指定要保存的字段
更新对象的一个或几个字段,不让所有字段都更新将会一些轻微的性能提升
product.name = 'Name changed again' product.save(update_fields=['name'])
update_fields 参数可以是任何包含字符串的可迭代对象。空的update_fields 可迭代对象将会忽略保存。如果为None 值,将执行所有字段上的更新
当保存通过延迟模型加载(only() 或defer())进行访问的模型时,只有从数据库中加载的字段才会得到更新。这种情况下,有个自动的update_fields。如果你赋值或者改变延迟字段的值,该字段将会添加到更新的字段中
删除对象
Model.delete([using=DEFAULT_DB_ALIAS])
发出一个SQL DELETE 操作。它只在数据库中删除这个对象;其Python 实例仍将存在并持有各个字段的数据
get_absolute_url
Model.get_absolute_url()
get_absolute_url() 方法告诉Django 如何计算对象的标准URL。对于调用者,该方法返回的字符串应该可以通过HTTP 引用到这个对象
from django.shortcuts import reverse
def get_absolute_url(self):
return reverse('blog:detail', kwargs={'id': self.id})
额外的实例方法
Model.get_FOO_display()
对于每个具有choices 的字段,每个对象将具有一个get_FOO_display() 方法,其中FOO 为该字段的名称。这个方法返回该字段对“人类可读”的值。
Model.get_next_by_FOO(**kwargs)
Model.get_previous_by_FOO(**kwargs)
如果DateField 和DateTimeField没有设置 null=True,那么该对象将具有get_next_by_FOO() 和get_previous_by_FOO() 方法,其中FOO 为字段的名称。它根据日期字段返回下一个和上一个对象,并适时引发一个DoesNotExist。
这两个方法都将使用模型默认的管理器来执行查询。如果你需要使用自定义的管理器或者你需要自定义的筛选,这个两个方法还接受可选的参数,它们应该用字段查询 中提到的格式。
注意,对于完全相同的日期,这些方法还将利用主键来进行查找。这保证不会有记录遗漏或重复。这还意味着你不可以在未保存的对象上使用这些方法
不存在
exception Model.DoesNotExist
ORM 在好几个地方会引发这个异常,例如QuerySet.get() 根据给定的查询参数找不到对象时。
Django 为每个类提供一个DoesNotExist 异常属性是为了区别找不到的对象所属的类,并让你可以利用try/except捕获一个特定模型的类。这个异常是django.core.exceptions.ObjectDoesNotExist 的子类