新模型


这是进阶内容。

Evennia为保存对象数据提供了许多便捷的方式,比如通过属性或脚本,这可以满足大多数的需求,但如果你想建立大型的独立系统,利用这些现有的东西来实现存储可能就比较复杂了。比如要保存公会的数据,而且公会会员要能够变化;要在全局性的经济系统中追踪货币的流动;或者需要建立快速保存自定义数据的自定义游戏系统等。虽然标签脚本能够应对大多数情况,但有时使用自定义的数据库模型会更加简单。


添加新数据库模型

Evennia支持SQL类型的数据库,它们对检索表中的文本是高度优化的。一张表看起来可能是这样的
     id | db_key    | db_typeclass_path             | db_permissions  ...
    ---------------------------------------------------------------------
     1  |  Griatch  | src.objects.objects.Character | Immortals       ...
     2  |  Rock     | src.objects.objects.Object    | None            ...
在你的数据库中,每一行都会更长。每列都被称为“字段”,每行都是一个单独的对象。你可以自己查看它。如果使用默认的 SQLite3 数据库,可以前往 game/ 执行
 python manage.py dbshell
你会进入数据库shell。在那里输入:
 sqlite> .help       # 查看帮助

 sqlite> .tables     # 查看所有表

 # 显示表objects_objectdb的字段名
 sqlite> .schema objects_objectdb

 # 显示表objects_objectdb的第一行
 sqlite> select * from objects_objectdb limit 1;

 sqlite> .exit

Evennia使用Django,它将数据库的SQL操作抽象化了,可以让你在Python中检索、操纵数据库。 虽然许多操作的细节在Evennia中被隐藏了,但你可以跳过Evennia直接使用Django。

以下是如何添加新的数据库表:
  1. 将目录切换到你想添加新代码的地方(这不是存储数据的地方,只是定义新表的地方)。建议放在 game/gamesrc/ 中的某处。
  2. 用Django的术语说,我们将创建一个新的“应用程序”:Evennia主程序中的子系统。在这个例子中,我们将它称为“myapp”。运行以下命令(在此之前你需要先让Evennia运行起来,所以你应该已经执行过准备开始中的步骤了):
    python <game 文件夹的路径>/manage.py startapp myapp
    
  3. 一个新文件夹 myapp 会在你当前所在位置创建出来。它包含了几个空的默认文件。我们现在感兴趣的是 models.py。
  4. 你需要在 models.py 中定义模型,每个模型都是数据库中的一张表。请参阅下一节。
  5. 在 game/settings.py 中添加下面一行内容:
    INSTALLED_APPS = INSTALLED_APPS + ("gamesrc.myapp", )
    
  6. 执行
    python manage.py syncdb
    

这会将新的表添加到数据库中。


定义模型

数据库表在Python中表现为Django模型。它用起来就像其他Python类一样。它自己定义了一个特殊的类型fields,它们会成为数据库表中的“列”。最后,你可以创建模型的新实例,并将新的行添加到数据库中。

我们不会在这里详述Django模型的各个方面,想了解相关内容可以阅读丰富的Django文档。以下是一个非常简单的例子:
from django.db import models

class MyDataStore(models.Model):
    "一个存储数据的简单模型"
    
    db_key = models.CharField(max_length=80, db_index=True)
    db_category = models.CharField(max_length=80, null=True, blank=True)
    db_text = models.TextField(null=True, blank=True)
我们创建了三个字段:两个限定长度的字符串字段和一个没有最大长度的文本字段。你的字段名不是必须以 db_ 开始的,这只是Evennia的约定,但我们还是建议你使用 db_,一方面是为了可读性及与Evennia保持一致(如果你想分享代码),另一方面是考虑到你以后可能会继续使用Evennia的 SharedMemoryModel 父类。使用 db_index 参数可以创建该字段的索引,这可以让搜索更快速。null=True 和 blank=True 关键字表示该字段可以为空而不会引发数据库报错(更多信息请參见Django的文档)。


创建新模型的实例

想要在表中创建新的行,你需要将模型实例化,然后调用它的“save()”方法:
    from game.gamesrc.myapp import MyDataStore

    new_datastore = MyDataStore(db_key="LargeSword",
                                db_category="weapons",
                                db_text="This is a huge weapon!")
    # 这会在数据库中真正创建一行记录!
    new_datastore.save()
(请注意,Evennia的类型类数据库模型 —— 玩家、脚本、频道和物体不能这样实例化,必须通过 ev.create_object() 等方法创建,否则无法在幕后设置类型类系统。)

如果你修改了现有对象中一些字段的值,请记住之后一定要保存对象,否则数据库不会更新:
    my_datastore.db_key = "Larger Sword"
    my_datastore.save()
Evennia内建的普通数据库模型是不需要专门调用 save 方法的,因为它们是基于 SharedMemoryModel 创建的,而不是原始的 django 模型。这会在下一节讨论。


用 SharedMemoryModel 作为父类

Evennia中的大多数模型并不是基于原始的 django.db.models 建立的,而是基于自定义的 src.utils.idmapper.models.SharedMemoryModel。这主要有两个原因:
  • 内存中对象的持久性
  • 可以自动更新字段,无需专门调用 save()

要解释第一点,可以看看以下使用默认Django模型为父类的例子:
    shield = MyDataStore.objects.get(db_key="SmallShield")
    shield.cracked = True # cracked 不是数据库字段
然后:
    shield = MyDataStore.objects.filter(db_key="SmallShield")
    print shield.cracked

最后 print 出来的结果是不确定的,它会产生随机结果,你还可能会得到一个 AttributeError 错误,因为找不到属性 cracked。因为 cracked 不是数据库中的实际字段。它只是在运行时添加的,Django并不关心它。之后你取回 shield 时,无法保证你拿到的是之前定义过 cracked 属性的实例,即使你搜索的是相同的数据库对象。

Evennia需要高度依赖模型中的处理程序及其他动态创建的属性。因此不使用原始的Django模型而是使用SharedMemoryModel,它使用了一种名为 idmapper 的技术。它会缓存模型实例,在搜索过一次指定对象之后,以后搜索该对象都会得到相同的实例。通过 idmapper,上面的例子就能很好地工作了,你可以在任何时候拿到 cracked 属性,直到你重新启动服务器。

使用 idmapper 更直观,而且对单个对象来说内存使用会更高效,但缺点是对于整个系统来说往往需要更多的内存。 Django可以轻松地释放掉旧的内存引用,因为它知道可以在需要时重新创建它们,但我们不能这样做,必须保持住内存的引用计数。所以,如果你能保证永远不会给运行中的实例添加新属性,或者你会一直创建新的对象但很少会再次访问它们(比如日志系统),那就不宜使用 idmapper。

从 SharedMemoryModel 继承的另一个好处是,只要你的字段名是以 db_* 开头的,Evennia就会自动将它们封装成属性。这发生在模型的元类(Metaclass)中,所以没有速度损失。封装的属性名字与字段名减去db_前缀相同。因此 db_key 字段封装成属性名 key。接下来你可以这样做:
    my_datastore.key = "Larger Sword"
之后不必再专门调用 save() 方法。如果你在模型中手动添加过名为 key 的属性或方法,它会代替自动生成属性执行。

如果你使用 SharedMemoryModel,它可以很方便地让你的模型支持idmapper和字段封装功能。你只需要让你的模型类从 src.utils.idmapper.models.SharedMemoryModel 继承而不是从默认的 django.db.models.Model 继承就可以了:
from src.utils.idmapper.models import SharedMemoryModel

class MyDataStore(SharedMemoryModel):
    # 其余的和之前一样,但 db_* 很重要...
    db_key = models.CharField(max_length=80, db_index=True)
    db_category = models.CharField(max_length=80, null=True, blank=True)
    db_text = models.TextField(null=True, blank=True)


搜索你的模型

为了能在你自定义的新数据库表中搜索,你需要使用它的数据库管理程序来构建查询。以下是如何使用代码来实现该功能:
    from game.gamesrc.myapp import MyDataStore

    # 取得所有与指定关键字精确匹配的数据存储对象
    matches = MyDataStore.objects.filter(db_key="Larger Sword")
    # 取得所有关键字中包含“sword”并且属于类别“weapons”(均忽略大小写)
    # 的数据存储对象
    matches2 = MyDataStore.objects.filter(db_key__icontains="sword", 
                                          db_category__iequals="weapons")
    # 显示匹配的数据(例如在命令中)
    for match in matches2:
        self.caller.msg(match.db_text)
在 Django 的 query 文档中有更多关于查询数据库的信息。另外请注意,即使你使用了前面所述的 SharedMemoryModel,你在查询时也必须使用实际的字段名而不是封装的名字(比如要使用 db_key 而不是 key)。


(原文:https://github.com/evennia/evennia/wiki/New Models    翻译:卢铱俊)