My favorites | 中文(简体) | Sign in

实体和模型

数据存储区实体有一个键和一组属性。应用程序使用数据存储区 API 定义数据模型,并创建要存储为实体的模型的实例。模型可为 API 所创建的实体提供通用结构,并可以定义用于验证属性值的规则。

Model 接口

应用程序介绍了它与 Model 配合使用的数据的类型。模型是从 Model 类继承的 Python 类。Model 类定义了数据存储区实体的新类型,以及该类型将采用的属性。

Model 属性使用 Model 类中的类属性定义。每个类属性都是 Property 类的子类实例,通常是提供的 Property 类之一。Property 实例保留了属性的配置,例如实例是否必须要有该属性才有效,或用于实例的默认值(如果没有提供的话)。

from google.appengine.ext import db

class Pet(db.Model):
  name = db.StringProperty(required=True)
  type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
  birthdate = db.DateProperty()
  weight_in_pounds = db.IntegerProperty()
  spayed_or_neutered = db.BooleanProperty()
  owner = db.UserProperty(required=True)

某个定义的实体类型的实体在 API 中用对应的 Model 类的实例表示。应用程序可以通过调用类的构造函数创建新实体。应用程序使用实例的属性来访问和控制实体的属性。Model 实例构造函数接受属性的初始值作为关键字参数。

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat",
          owner=users.get_current_user())
pet.weight_in_pounds = 24

注意:Model 类的属性是模型属性的配置,其值为 Property 实例。Model 实例的属性是实际属性值,其值为 Property 类接受的类型。

Model 类会使用 Property 实例来验证分配给 Model 实例属性的值。当第一次构建 Model 实例时以及为实例属性分配新值时,都会进行属性值验证。这确保了属性绝不会有无效值。

由于在构建实例时进行验证,配置为必需属性的任何属性都必须在构造函数中进行初始化。在该示例中,nametypeowner 都是必需的属性,所以它们的初始值在构造函数中指定。weight_in_pounds 不是模型所必需的,所以它在开始时未被分配值,到后面才分配了值。

使用构造函数创建的模型的实例直至第一次“放置”才会存在于数据存储区中。请参阅创建、获取和删除数据

注意:如同所有 Python 类属性一样,模型属性配置是在第一次导入脚本或模块时初始化的。由于 App Engine 缓存是在请求之间导入模块,因此模块配置可能在某个用户的请求期间进行初始化,并在另一个用户的请求期间重复使用。请勿用请求或当前用户特定的数据初始化模型属性配置(例如默认值)。有关详细信息,请参阅应用程序缓存

Expando 模型

使用 Model 类定义的模型建立一组固定的属性,该类的每个实例都必须具有这些属性(可能有默认值)。这是一种非常有用的对数据对象进行建模的方法,但是数据存储区不要求指定类型的每个实体都有相同的属性集。

有时,实体有非必要属性(例如其他同类实体的属性)其实是非常有用的。这样的实体在数据存储区 API 中由“Expando”模型表示。Expando Model 类是 Expando 超类的子类。分配给 Expando 模型实例的属性的任何值都将成为数据存储区实体的属性,并使用该属性的名称。这些属性称为动态属性。类属性中使用 Property 类实例定义的属性为固定属性。

Expando 模型可以拥有固定属性和动态属性。Model 类仅使用固定属性的 Property 配置对象来设置类属性。应用程序为动态属性分配值时将创建动态属性。

class Person(db.Expando):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hobbies = db.StringListProperty()

p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

p.chess_elo_rating = 1350

p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"]
p.travel_trip_count = 13

由于动态属性没有模型属性定义,所以动态属性未经验证。所有动态属性都可以有任何数据存储区基本类型的值,包括 None。两个同类实体的相同动态属性可以有不同的值类型,并且可以有一个不设置属性,而另一个设置。

与固定属性不同,动态属性不需要已存在。值为 None 的动态属性与不存在的动态属性不同。如果 Expando 模型实例没有属性的属性,对应的数据实体将没有该属性。您可以通过删除该属性来删除动态属性。

del p.chess_elo_rating

在过滤条件中使用动态属性的查询将仅返回其属性值与查询中使用的值的类型相同的实体。同样,查询将仅返回设置了该属性的实体。

p1 = Person()
p1.favorite = 42
p1.put()

p2 = Person()
p2.favorite = "blue"
p2.put()

p3 = Person()
p3.put()

people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50)
# people has p1, but not p2 or p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people has no results

Expando 类是 Model 类的子类,并且继承其所有方法。

多态模型

Python API 包含另一个用于数据建模的类,您可以通过它定义类的层次结构,并执行可以返回给定的类的实体或任何该类的子类的实体的查询。这样的模型和查询称为“多态”,因为它们允许一个类的实例成为对父类的查询的结果。

以下示例定义了 Contact 类,以及 Contact 的子类 PersonCompany

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
  phone_number = db.PhoneNumberProperty()
  address = db.PostalAddressProperty()

class Person(Contact):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  mobile_number = db.PhoneNumberProperty()

class Company(Contact):
  name = db.StringProperty()
  fax_number = db.PhoneNumberProperty()

此模型确保了所有 Person 实体和所有 Company 实体都具有 phone_numberaddress 属性,并且查询 Contact 实体能返回 PersonCompany 实体。只有 Person 实体具有 mobile_number 属性。

子类可以与任何其他 Model 类一样实例化:

p = Person(phone_number='1-206-555-9234',
           address='123 First Ave., Seattle, WA, 98101',
           first_name='Alfred',
           last_name='Smith',
           mobile_number='1-206-555-0117')
p.put()

c = Company(phone_number='1-503-555-9123',
            address='P.O. Box 98765, Salem, OR, 97301',
            name='Data Solutions, LLC',
            fax_number='1-503-555-6622')
c.put()

查询 Contact 实体可以返回 ContactPersonCompany 的实例。以下代码将打印以上所创建的两个实体的信息:

for contact in Contact.all():
  print 'Phone: %s\nAddress: %s\n\n'
        % (contact.phone,
           contact.address))

查询 Company 实体只能返回 Company 的实例:

for company in Company.all()
  # ...

有关如何使用多态模型以及如何实现多态模型的详细信息,请参阅 PolyModel 类

属性和类型

数据存储区支持一组固定的实体属性的值类型,包括 Unicode 字符串、整数、浮点数、日期、实体键、字节字符串 (Blob) 和各种 GData 类型。每个数据存储区值类型都有 google.appengine.ext.db 模块提供的对应的 Property 类。

类型和 Property 类介绍了所有支持的值类型及其对应的 Property 类。下面介绍几个特殊的值类型。

字符串和 Blob

数据存储区支持两个存储文本值类型:长度不超过 500 字节的短文本字符串,和长度不超过 1 兆字节的长文本字符串。短字符串会编入索引并可在查询过滤条件和排序顺序中使用。长字符串不会编入索引且不能在过滤条件或排序顺序中使用。

短字符串值可以是 unicode 值或 str 值。如果值是 str,则假设编码为 'ascii'。要为 str 值指定一种不同的编码,可以用 unicode() 类型的构造函数将其转换为 unicode 值,这种类型的构造函数会使用 str 和编码名称作为参数。可以使用 StringProperty 类对短字符串进行建模。

class MyModel(db.Model):
  string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u"kittens"

# unicode() converts a byte string to a Unicode value using the named codec.
obj.string = unicode("kittens", "latin-1")

# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj.string = "kittens"

# Short string properties can be used in query filters.
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

长字符串值由 db.Text 实例表示。其构造函数采用 unicode 值或 str 值,并可能会使用 str 中使用的编码名称。可以使用 TextProperty 类对长字符串进行建模。

class MyModel(db.Model):
  text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode value.
obj.text = db.Text(u"lots of kittens")

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text("lots of kittens", "latin-1")

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = db.Text("lots of kittens")

# Text properties can store large values.
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

数据存储区还支持两个相似的非文本字节字符串类型:db.ByteStringdb.Blob。这些值是原始字节的字符串,不作为编码文本(如 UTF-8)进行处理。

strunicode 值类似,db.ByteString 值也会编入索引,并且不超过 500 个字符。ByteString 实例代表短字节字符串,并使用 str 值作为构造函数的参数。可以使用 ByteStringProperty 类对字节字符串进行建模。

db.Text 类似,db.Blob 值可以大至 1 兆字节,但是不编入索引,并且不能用作查询过滤条件或排序依据。db.Blob 类采用 str 值作为构造函数的参数。可以使用 BlobProperty 类对 Blob 进行建模。

class MyModel(db.Model):
  blob = db.BlobProperty()

obj = MyModel()

obj.blob = db.Blob(open("image.png").read())

列表

属性可以有多个值,在数据存储区 API 中表示为 Python list。列表可以包含数据存储区支持的任何值类型的值。一个列表属性甚至可以有不同类型的值。顺序将保留,因此当查询和 get() 返回实体时,列表属性值的顺序将与存储这些值时的顺序相同。

ListProperty 类对列表进行建模,并强制列表中的所有值都采用指定类型。为了方便起见,库还提供 StringListProperty,类似于 ListProperty(basestring)

class MyModel(db.Model):
  numbers = db.ListProperty(long)

obj = MyModel()
obj.numbers = [2, 4, 6, 8, 10]

obj.numbers = ["hello"]  # ERROR: MyModel.numbers must be a list of longs.

列表属性上的查询过滤将针对列表的成员测试指定的值。如果至少一个列表成员符合条件,则条件为 True。

# Get all entities where numbers contains a 6.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6")

# Get all entities where numbers contains at least one element less than 10.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")

仅对列表成员进行查询过滤。无法在一个查询过滤中测试两个列表的相似之处。

数据存储区在内部将列表属性值表示为该属性的多个值。如果列表属性值为空列表,则该属性在数据存储区中没有表示。对于静态属性(具有 ListProperty)和动态属性,数据存储区 API 处理这种情况的方式不同:

  • 可以为静态 ListProperty 分配空列表作为值。该属性不存在于数据存储区中,但是 Model 实例的行为就像值是空列表一样。静态 ListProperty 的值不能为 None
  • 无法为具有 list 值的动态属性分配空列表值。但是,它可以有 None 值,并可以被删除(使用 del)。

ListProperty 模型测试添加到列表的值的类型是否正确,如果不正确,则引发 BadValueError。即使是在检索以前存储的实体并将其加载到模型中时,也会进行该测试(并可能失败)。由于 str 值在存储前转换为 unicode 值(作为 ASCII 文本),ListProperty(str) 会被视为 ListProperty(basestring),即接受 strunicode 值的 Python 数据类型。您还可以使用 StringListProperty() 实现此目的。

要存储非文本字节字符串,请使用 db.Blob 值。当存储和检索 Blob 字符串的字节时,将保留这些字节。您可以将形式为 Blob 列表的属性声明为 ListProperty(db.Blob)

列表属性以独特的方式与排序顺序交互。有关详细信息,请参阅查询和索引:排序顺序和具有多个值的属性

引用

一个属性值可以包含另一个实体的键。该值为 Key 实例。

ReferenceProperty 类对键值进行建模,并强制所有值参考指定类型的实体。为了方便起见,库还提供 SelfReferenceProperty,等同于参考具有该属性的实体的同一类型的实体的 ReferenceProperty。

向 ReferenceProperty 属性分配 Model 实例将自动使用其键作为值。

class FirstModel(db.Model):
  prop = db.IntegerProperty()

class SecondModel(db.Model):
  reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity's key as the value.
obj2.reference = obj1
obj2.put()

可以将 ReferenceProperty 属性值当作是引用的实体的 Model 实例使用。如果引用的实体不在内存中,则当使用该属性作为实例时将会自动从数据存储区中抓取实体。

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

当删除键是引用属性的值的实体时,引用属性不会发生更改。引用属性值可以是不再有效的键。如果某个应用程序预计引用可能无效,则它可以使用 if 语句测试对象是否存在:

obj1 = obj2.reference

if not obj1:
  # Referenced entity was deleted.

ReferenceProperty 有另一个便利功能:反向引用。如果一个模型将 ReferenceProperty 传递给另一个模型时,每个引用的实体都会获得一个属性,该属性的值为返回第一个引用它的模型的所有实体的 Query

# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for obj in obj1.secondmodel_set:
  # ...

反向引用属性的名称默认为 modelname_set(使用以小写字母表示的 Model 类的名称,并在末尾添加“_set”),并可以使用 collection_name 参数调整为 ReferenceProperty 构造函数。

如果您有多个参照同一 Model 类的 ReferenceProperty 值,反向引用属性的默认结构将引发错误。

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class raises a DuplicatePropertyError with the message
# "Class Firstmodel already has property secondmodel_set"
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel)
  reference_two = db.ReferenceProperty(FirstModel)

要避免这种错误,您必需明确地设置 collection_name 参数:

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class runs fine
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_one_set")
  reference_two = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_two_set") 

Model 实例的自动引用和取消引用、类型检查和反向引用仅在使用 ReferenceProperty 模型属性类时可用。存储为 Expando 动态属性的值或 ListProperty 值的键没有这些功能。

属性名称

数据存储区保留所有以两个下划线字符 (__*__) 开头和结尾的属性名称。应用程序无法创建具有这样的名称的属性。

在 Python API 中,将忽略名称以下划线 (_) 开头的 Model 实例的属性,且不保存到数据存储区实体。这允许您在 Model 实例上存储值以供临时内部使用,而不影响与实体一起保存的数据。

由于默认情况下 Python API 使用 Model 实例的属性作为属性名称,因此,已被实例模型使用的属性不可直接作为属性的属性名称使用。同样,没有被 Model 构造函数的关键字参数使用的名称可作为属性名称使用。请参阅保留的属性名称列表

数据存储区本身允许使用这些名称。如果应用程序需要数据存储区实体有一个名称类似于 Python API 中保留的字词的属性,应用程序可以使用固定属性,并将 name 参数传递到 Property 类构造函数。请参阅 Property 类构造函数

class MyModel(db.Model):
  obj_key = db.StringProperty(name="key")