mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Add more Models docs
This commit is contained in:
parent
5b2e9bd5a1
commit
8aa5ca8536
2 changed files with 54 additions and 66 deletions
|
|
@ -86,10 +86,7 @@ to version control.
|
|||
|
||||
## Defining your models
|
||||
|
||||
A Django *model* is the Python representation of a database table. It can be handled like any other
|
||||
Python class. It defines *fields* on itself, objects of a special type. These become the "columns"
|
||||
of the database table. Finally, you create new instances of the model to add new rows to the
|
||||
database.
|
||||
A Django *model* is the Python representation of a database table. It can be handled like any other Python class. It defines *fields* on itself, objects of a special type. These become the "columns" of the database table. Finally, you create new instances of the model to add new rows to the database.
|
||||
|
||||
We won't describe all aspects of Django models here, for that we refer to the vast [Django
|
||||
documentation](https://docs.djangoproject.com/en/4.1/topics/db/models/) on the subject. Here is a
|
||||
|
|
@ -112,28 +109,51 @@ class MyDataStore(models.Model):
|
|||
We create four fields: two character fields of limited length and one text field which has no
|
||||
maximum length. Finally we create a field containing the current time of us creating this object.
|
||||
|
||||
> The `db_date_created` field, with exactly this name, is *required* if you want to be able to store
|
||||
instances of your custom model in an Evennia [Attribute](../Components/Attributes.md). It will automatically be set
|
||||
upon creation and can after that not be changed. Having this field will allow you to do e.g.
|
||||
`obj.db.myinstance = mydatastore`. If you know you'll never store your model instances in Attributes
|
||||
the `db_date_created` field is optional.
|
||||
> The `db_date_created` field, with exactly this name, is *required* if you want to be able to store instances of your custom model in an Evennia [Attribute](../Components/Attributes.md). It will automatically be set upon creation and can after that not be changed. Having this field will allow you to do e.g. `obj.db.myinstance = mydatastore`. If you know you'll never store your model instances in Attributes the `db_date_created` field is optional.
|
||||
|
||||
You don't *have* to start field names with `db_`, this is an Evennia convention. It's nevertheless
|
||||
recommended that you do use `db_`, partly for clarity and consistency with Evennia (if you ever want
|
||||
to share your code) and partly for the case of you later deciding to use Evennia's
|
||||
You don't *have* to start field names with `db_`, this is an Evennia convention. It's nevertheless recommended that you do use `db_`, partly for clarity and consistency with Evennia (if you ever want to share your code) and partly for the case of you later deciding to use Evennia's
|
||||
`SharedMemoryModel` parent down the line.
|
||||
|
||||
The field keyword `db_index` creates a *database index* for this field, which allows quicker
|
||||
lookups, so it's recommended to put it on fields you know you'll often use in queries. The
|
||||
`null=True` and `blank=True` keywords means that these fields may be left empty or set to the empty
|
||||
string without the database complaining. There are many other field types and keywords to define
|
||||
them, see django docs for more info.
|
||||
The field keyword `db_index` creates a *database index* for this field, which allows quicker lookups, so it's recommended to put it on fields you know you'll often use in queries. The `null=True` and `blank=True` keywords means that these fields may be left empty or set to the empty string without the database complaining. There are many other field types and keywords to define them, see django docs for more info.
|
||||
|
||||
Similar to using [django-admin](https://docs.djangoproject.com/en/4.1/howto/legacy-databases/) you
|
||||
are able to do `evennia inspectdb` to get an automated listing of model information for an existing
|
||||
database. As is the case with any model generating tool you should only use this as a starting
|
||||
Similar to using [django-admin](https://docs.djangoproject.com/en/4.1/howto/legacy-databases/) you are able to do `evennia inspectdb` to get an automated listing of model information for an existing database. As is the case with any model generating tool you should only use this as a starting
|
||||
point for your models.
|
||||
|
||||
## Referencing existing models and typeclasses
|
||||
|
||||
You may want to use `ForeignKey` or `ManyToManyField` to relate your new model to existing ones.
|
||||
|
||||
To do this we need to specify the app-path for the root object type we want to store as a string (we must use a string rather than the class directly or you'll run into problems with models not having been initialized yet).
|
||||
|
||||
- `"objects.ObjectDB"` for all [Objects](Objects) (like exits, rooms, characters etc)
|
||||
- `"accounts.AccountDB"` for [Accounts](Accounts).
|
||||
- `"scripts.ScriptDB"` for [Scripts](Scripts).
|
||||
- `"comms.ChannelDB"` for [Channels](Channels).
|
||||
- `"comms.Msg"` for [Msg](Msg) objects.
|
||||
- `"help.HelpEntry"` for [Help Entries](Help-System).
|
||||
|
||||
Here's an example:
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class MySpecial(models.Model):
|
||||
db_character = models.ForeignKey("objects.ObjectDB")
|
||||
db_items = models.ManyToManyField("objects.ObjectDB")
|
||||
db_account = modeles.ForeignKey("accounts.AccountDB")
|
||||
```
|
||||
|
||||
It may seem counter-intuitive, but this will work correctly:
|
||||
|
||||
myspecial.db_character = my_character # a Character instance
|
||||
my_character = myspecial.db_character # still a Character
|
||||
|
||||
This works because when the `.db_character` field is loaded into Python, the entity itself knows that it's supposed to be a `Character` and loads itself to that form.
|
||||
|
||||
The drawback of this is that the database won't _enforce_ the type of object you store in the relation. This is the price we pay for many of the other advantages of the Typeclass system.
|
||||
|
||||
While the `db_character` field fail if you try to store an `Account`, it will gladly accept any instance of a typeclass that inherits from `ObjectDB`, such as rooms, exits or other non-character Objects. It's up to you to validate that what you store is what you expect it to be.
|
||||
|
||||
## Creating a new model instance
|
||||
|
||||
To create a new row in your table, you instantiate the model and then call its `save()` method:
|
||||
|
|
@ -149,46 +169,33 @@ To create a new row in your table, you instantiate the model and then call its `
|
|||
|
||||
```
|
||||
|
||||
Note that the `db_date_created` field of the model is not specified. Its flag `at_now_add=True`
|
||||
makes sure to set it to the current date when the object is created (it can also not be changed
|
||||
further after creation).
|
||||
Note that the `db_date_created` field of the model is not specified. Its flag `at_now_add=True` makes sure to set it to the current date when the object is created (it can also not be changed further after creation).
|
||||
|
||||
When you update an existing object with some new field value, remember that you have to save the
|
||||
object afterwards, otherwise the database will not update:
|
||||
When you update an existing object with some new field value, remember that you have to save the object afterwards, otherwise the database will not update:
|
||||
|
||||
```python
|
||||
my_datastore.db_key = "Larger Sword"
|
||||
my_datastore.save()
|
||||
```
|
||||
|
||||
Evennia's normal models don't need to explicitly save, since they are based on `SharedMemoryModel`
|
||||
rather than the raw django model. This is covered in the next section.
|
||||
Evennia's normal models don't need to explicitly save, since they are based on `SharedMemoryModel` rather than the raw django model. This is covered in the next section.
|
||||
|
||||
## Using the `SharedMemoryModel` parent
|
||||
|
||||
Evennia doesn't base most of its models on the raw `django.db.models` but on the Evennia base model
|
||||
`evennia.utils.idmapper.models.SharedMemoryModel`. There are two main reasons for this:
|
||||
Evennia doesn't base most of its models on the raw `django.db.models` but on the Evennia base model `evennia.utils.idmapper.models.SharedMemoryModel`. There are two main reasons for this:
|
||||
|
||||
1. Ease of updating fields without having to explicitly call `save()`
|
||||
2. On-object memory persistence and database caching
|
||||
|
||||
The first (and least important) point means that as long as you named your fields `db_*`, Evennia
|
||||
will automatically create field wrappers for them. This happens in the model's
|
||||
[Metaclass](http://en.wikibooks.org/wiki/Python_Programming/Metaclasses) so there is no speed
|
||||
penalty for this. The name of the wrapper will be the same name as the field, minus the `db_`
|
||||
prefix. So the `db_key` field will have a wrapper property named `key`. You can then do:
|
||||
The first (and least important) point means that as long as you named your fields `db_*`, Evennia will automatically create field wrappers for them. This happens in the model's [Metaclass](http://en.wikibooks.org/wiki/Python_Programming/Metaclasses) so there is no speed penalty for this. The name of the wrapper will be the same name as the field, minus the `db_` prefix. So the `db_key` field will have a wrapper property named `key`. You can then do:
|
||||
|
||||
```python
|
||||
my_datastore.key = "Larger Sword"
|
||||
```
|
||||
|
||||
and don't have to explicitly call `save()` afterwards. The saving also happens in a more efficient
|
||||
way under the hood, updating only the field rather than the entire model using django optimizations.
|
||||
Note that if you were to manually add the property or method `key` to your model, this will be used
|
||||
instead of the automatic wrapper and allows you to fully customize access as needed.
|
||||
and don't have to explicitly call `save()` afterwards. The saving also happens in a more efficient way under the hood, updating only the field rather than the entire model using django optimizations. Note that if you were to manually add the property or method `key` to your model, this will be used instead of the automatic wrapper and allows you to fully customize access as needed.
|
||||
|
||||
To explain the second and more important point, consider the following example using the default
|
||||
Django model parent:
|
||||
To explain the second and more important point, consider the following example using the default Django model parent:
|
||||
|
||||
```python
|
||||
shield = MyDataStore.objects.get(db_key="SmallShield")
|
||||
|
|
@ -202,30 +209,14 @@ And then later:
|
|||
print(shield.cracked) # error!
|
||||
```
|
||||
|
||||
The outcome of that last print statement is *undefined*! It could *maybe* randomly work but most
|
||||
likely you will get an `AttributeError` for not finding the `cracked` property. The reason is that
|
||||
`cracked` doesn't represent an actual field in the database. It was just added at run-time and thus
|
||||
Django don't care about it. When you retrieve your shield-match later there is *no* guarantee you
|
||||
will get back the *same Python instance* of the model where you defined `cracked`, even if you
|
||||
search for the same database object.
|
||||
The outcome of that last print statement is *undefined*! It could *maybe* randomly work but most likely you will get an `AttributeError` for not finding the `cracked` property. The reason is that `cracked` doesn't represent an actual field in the database. It was just added at run-time and thus Django don't care about it. When you retrieve your shield-match later there is *no* guarantee you will get back the *same Python instance* of the model where you defined `cracked`, even if you search for the same database object.
|
||||
|
||||
Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than
|
||||
using the vanilla Django models, Evennia uses `SharedMemoryModel`, which levies something called
|
||||
*idmapper*. The idmapper caches model instances so that we will always get the *same* instance back
|
||||
after the first lookup of a given object. Using idmapper, the above example would work fine and you
|
||||
could retrieve your `cracked` property at any time - until you rebooted when all non-persistent data
|
||||
goes.
|
||||
Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than using the vanilla Django models, Evennia uses `SharedMemoryModel`, which levies something called *idmapper*. The idmapper caches model instances so that we will always get the *same* instance back after the first lookup of a given object. Using idmapper, the above example would work fine and you could retrieve your `cracked` property at any time - until you rebooted when all non-persistent data goes.
|
||||
|
||||
Using the idmapper is both more intuitive and more efficient *per object*; it leads to a lot less
|
||||
reading from disk. The drawback is that this system tends to be more memory hungry *overall*. So if
|
||||
you know that you'll *never* need to add new properties to running instances or know that you will
|
||||
create new objects all the time yet rarely access them again (like for a log system), you are
|
||||
probably better off making "plain" Django models rather than using `SharedMemoryModel` and its
|
||||
idmapper.
|
||||
reading from disk. The drawback is that this system tends to be more memory hungry *overall*. So if you know that you'll *never* need to add new properties to running instances or know that you will create new objects all the time yet rarely access them again (like for a log system), you are probably better off making "plain" Django models rather than using `SharedMemoryModel` and its idmapper.
|
||||
|
||||
To use the idmapper and the field-wrapper functionality you just have to have your model classes
|
||||
inherit from `evennia.utils.idmapper.models.SharedMemoryModel` instead of from the default
|
||||
`django.db.models.Model`:
|
||||
To use the idmapper and the field-wrapper functionality you just have to have your model classes inherit from `evennia.utils.idmapper.models.SharedMemoryModel` instead of from the default `django.db.models.Model`:
|
||||
|
||||
```python
|
||||
from evennia.utils.idmapper.models import SharedMemoryModel
|
||||
|
|
@ -242,9 +233,7 @@ class MyDataStore(SharedMemoryModel):
|
|||
|
||||
## Searching for your models
|
||||
|
||||
To search your new custom database table you need to use its database *manager* to build a *query*.
|
||||
Note that even if you use `SharedMemoryModel` as described in the previous section, you have to use
|
||||
the actual *field names* in the query, not the wrapper name (so `db_key` and not just `key`).
|
||||
To search your new custom database table you need to use its database *manager* to build a *query*. Note that even if you use `SharedMemoryModel` as described in the previous section, you have to use the actual *field names* in the query, not the wrapper name (so `db_key` and not just `key`).
|
||||
|
||||
```python
|
||||
from world.myapp import MyDataStore
|
||||
|
|
@ -260,5 +249,4 @@ the actual *field names* in the query, not the wrapper name (so `db_key` and not
|
|||
self.caller.msg(match.db_text)
|
||||
```
|
||||
|
||||
See the [Django query documentation](https://docs.djangoproject.com/en/4.1/topics/db/queries/) for a
|
||||
lot more information about querying the database.
|
||||
See the [Django query documentation](https://docs.djangoproject.com/en/4.1/topics/db/queries/) for a lot more information about querying the database.
|
||||
Loading…
Add table
Add a link
Reference in a new issue