Make all model .created_date properties resolve to TIME_ZONE time zone. Resolve #3522.

This commit is contained in:
Griatch 2024-06-14 23:44:41 +02:00
parent be8c024465
commit 7b299f2cad
9 changed files with 81 additions and 116 deletions

View file

@ -20,6 +20,8 @@
new `=` argument, for example `:f=40` or `:j 1:2 l = 60` (chiizujin) new `=` argument, for example `:f=40` or `:j 1:2 l = 60` (chiizujin)
- [Feature][pull3549]: Run the `collectstatic` command when reloading server to - [Feature][pull3549]: Run the `collectstatic` command when reloading server to
keep game assets in sync automatically (InspectorCaracal) keep game assets in sync automatically (InspectorCaracal)
- [Feature][issue3522]: (also a fix) Make `.created_date` property on all models property return
a time adjusted based on `settings.TIME_ZONE` (Griatch)
- [Language][pull3523]: Updated Polish translation (Moonchasered) - [Language][pull3523]: Updated Polish translation (Moonchasered)
- [Fix][pull3495]: Fix rate in Trait contribs not updating after reload (jaborsh) - [Fix][pull3495]: Fix rate in Trait contribs not updating after reload (jaborsh)
- [Fix][pull3491]: Fix traceback in EvEditor when searching with malformed regex (chiizujin) - [Fix][pull3491]: Fix traceback in EvEditor when searching with malformed regex (chiizujin)
@ -77,6 +79,7 @@
[pull3549]: https://github.com/evennia/evennia/pull/3549 [pull3549]: https://github.com/evennia/evennia/pull/3549
[pull3554]: https://github.com/evennia/evennia/pull/3554 [pull3554]: https://github.com/evennia/evennia/pull/3554
[pull3523]: https://github.com/evennia/evennia/pull/3523 [pull3523]: https://github.com/evennia/evennia/pull/3523
[issue3522]: https://github.com/evennia/evennia/issue/3522
## Evennia 4.1.1 ## Evennia 4.1.1

View file

@ -20,6 +20,8 @@
new `=` argument, for example `:f=40` or `:j 1:2 l = 60` (chiizujin) new `=` argument, for example `:f=40` or `:j 1:2 l = 60` (chiizujin)
- [Feature][pull3549]: Run the `collectstatic` command when reloading server to - [Feature][pull3549]: Run the `collectstatic` command when reloading server to
keep game assets in sync automatically (InspectorCaracal) keep game assets in sync automatically (InspectorCaracal)
- [Feature][issue3522]: (also a fix) Make `.created_date` property on all models property return
a time adjusted based on `settings.TIME_ZONE` (Griatch)
- [Language][pull3523]: Updated Polish translation (Moonchasered) - [Language][pull3523]: Updated Polish translation (Moonchasered)
- [Fix][pull3495]: Fix rate in Trait contribs not updating after reload (jaborsh) - [Fix][pull3495]: Fix rate in Trait contribs not updating after reload (jaborsh)
- [Fix][pull3491]: Fix traceback in EvEditor when searching with malformed regex (chiizujin) - [Fix][pull3491]: Fix traceback in EvEditor when searching with malformed regex (chiizujin)
@ -77,6 +79,7 @@
[pull3549]: https://github.com/evennia/evennia/pull/3549 [pull3549]: https://github.com/evennia/evennia/pull/3549
[pull3554]: https://github.com/evennia/evennia/pull/3554 [pull3554]: https://github.com/evennia/evennia/pull/3554
[pull3523]: https://github.com/evennia/evennia/pull/3523 [pull3523]: https://github.com/evennia/evennia/pull/3523
[issue3522]: https://github.com/evennia/evennia/issue/3522
## Evennia 4.1.1 ## Evennia 4.1.1

View file

@ -45,8 +45,7 @@ The `typeclass/list` command will provide a list of all typeclasses known to Eve
## Difference between typeclasses and classes ## Difference between typeclasses and classes
All Evennia classes inheriting from class in the table above share one important feature and two All Evennia classes inheriting from class in the table above share one important feature and two important limitations. This is why we don't simply call them "classes" but "typeclasses".
[]()important limitations. This is why we don't simply call them "classes" but "typeclasses".
1. A typeclass can save itself to the database. This means that some properties (actually not that many) on the class actually represents database fields and can only hold very specific data types. 1. A typeclass can save itself to the database. This means that some properties (actually not that many) on the class actually represents database fields and can only hold very specific data types.
1. Due to its connection to the database, the typeclass' name must be *unique* across the _entire_ server namespace. That is, there must never be two same-named classes defined anywhere. So the below code would give an error (since `DefaultObject` is now globally found both in this module and in the default library): 1. Due to its connection to the database, the typeclass' name must be *unique* across the _entire_ server namespace. That is, there must never be two same-named classes defined anywhere. So the below code would give an error (since `DefaultObject` is now globally found both in this module and in the default library):
@ -66,8 +65,7 @@ All Evennia classes inheriting from class in the table above share one important
# my content # my content
``` ```
Apart from this, a typeclass works like any normal Python class and you can Apart from this, a typeclass works like any normal Python class and you can treat it as such.
treat it as such.
## Working with typeclasses ## Working with typeclasses
@ -94,9 +92,7 @@ chair.save()
``` ```
To use this you must give the database field names as keywords to the call. Which are available To use this you must give the database field names as keywords to the call. Which are available depends on the entity you are creating, but all start with `db_*` in Evennia. This is a method you may be familiar with if you know Django from before.
depends on the entity you are creating, but all start with `db_*` in Evennia. This is a method you
may be familiar with if you know Django from before.
It is recommended that you instead use the `create_*` functions to create typeclassed entities: It is recommended that you instead use the `create_*` functions to create typeclassed entities:
@ -109,17 +105,9 @@ chair = create_object(Furniture, key="Chair")
chair = create_object("furniture.Furniture", key="Chair") chair = create_object("furniture.Furniture", key="Chair")
``` ```
The `create_object` (`create_account`, `create_script` etc) takes the typeclass as its first The `create_object` (`create_account`, `create_script` etc) takes the typeclass as its first argument; this can both be the actual class or the python path to the typeclass as found under your game directory. So if your `Furniture` typeclass sits in `mygame/typeclasses/furniture.py`, you could point to it as `typeclasses.furniture.Furniture`. Since Evennia will itself look in `mygame/typeclasses`, you can shorten this even further to just `furniture.Furniture`. The create-functions take a lot of extra keywords allowing you to set things like [Attributes](./Attributes.md) and [Tags](./Tags.md) all in one go. These keywords don't use the `db_*` prefix. This will also automatically save the new instance to the database, so you don't need to call `save()` explicitly.
argument; this can both be the actual class or the python path to the typeclass as found under your
game directory. So if your `Furniture` typeclass sits in `mygame/typeclasses/furniture.py`, you
could point to it as `typeclasses.furniture.Furniture`. Since Evennia will itself look in
`mygame/typeclasses`, you can shorten this even further to just `furniture.Furniture`. The create-
functions take a lot of extra keywords allowing you to set things like [Attributes](./Attributes.md) and
[Tags](./Tags.md) all in one go. These keywords don't use the `db_*` prefix. This will also automatically
save the new instance to the database, so you don't need to call `save()` explicitly.
An example of a database field is `db_key`. This stores the "name" of the entity you are modifying An example of a database field is `db_key`. This stores the "name" of the entity you are modifying and can thus only hold a string. This is one way of making sure to update the `db_key`:
and can thus only hold a string. This is one way of making sure to update the `db_key`:
```python ```python
chair.db_key = "Table" chair.db_key = "Table"
@ -129,9 +117,7 @@ print(chair.db_key)
<<< Table <<< Table
``` ```
That is, we change the chair object to have the `db_key` "Table", then save this to the database. That is, we change the chair object to have the `db_key` "Table", then save this to the database. However, you almost never do things this way; Evennia defines property wrappers for all the database fields. These are named the same as the field, but without the `db_` part:
However, you almost never do things this way; Evennia defines property wrappers for all the database
fields. These are named the same as the field, but without the `db_` part:
```python ```python
chair.key = "Table" chair.key = "Table"
@ -141,44 +127,32 @@ print(chair.key)
``` ```
The `key` wrapper is not only shorter to write, it will make sure to save the field for you, and The `key` wrapper is not only shorter to write, it will make sure to save the field for you, and does so more efficiently by levering sql update mechanics under the hood. So whereas it is good to be aware that the field is named `db_key` you should use `key` as much as you can.
does so more efficiently by levering sql update mechanics under the hood. So whereas it is good to
be aware that the field is named `db_key` you should use `key` as much as you can.
Each typeclass entity has some unique fields relevant to that type. But all also share the Each typeclass entity has some unique fields relevant to that type. But all also share the
following fields (the wrapper name without `db_` is given): following fields (the wrapper name without `db_` is given):
- `key` (str): The main identifier for the entity, like "Rose", "myscript" or "Paul". `name` is an - `key` (str): The main identifier for the entity, like "Rose", "myscript" or "Paul". `name` is an alias.
alias.
- `date_created` (datetime): Time stamp when this object was created. - `date_created` (datetime): Time stamp when this object was created.
- `typeclass_path` (str): A python path pointing to the location of this (type)class - `typeclass_path` (str): A python path pointing to the location of this (type)class
There is one special field that doesn't use the `db_` prefix (it's defined by Django): There is one special field that doesn't use the `db_` prefix (it's defined by Django):
- `id` (int): the database id (database ref) of the object. This is an ever-increasing, unique - `id` (int): the database id (database ref) of the object. This is an ever-increasing, unique integer. It can also be accessed as `dbid` (database ID) or `pk` (primary key). The `dbref` property returns the string form "#id".
integer. It can also be accessed as `dbid` (database ID) or `pk` (primary key). The `dbref` property
returns the string form "#id".
The typeclassed entity has several common handlers: The typeclassed entity has several common handlers:
- `tags` - the [TagHandler](./Tags.md) that handles tagging. Use `tags.add()` , `tags.get()` etc. - `tags` - the [TagHandler](./Tags.md) that handles tagging. Use `tags.add()` , `tags.get()` etc.
- `locks` - the [LockHandler](./Locks.md) that manages access restrictions. Use `locks.add()`, - `locks` - the [LockHandler](./Locks.md) that manages access restrictions. Use `locks.add()`, `locks.get()` etc.
`locks.get()` etc. - `attributes` - the [AttributeHandler](./Attributes.md) that manages Attributes on the object. Use `attributes.add()`
- `attributes` - the [AttributeHandler](./Attributes.md) that manages Attributes on the object. Use
`attributes.add()`
etc. etc.
- `db` (DataBase) - a shortcut property to the AttributeHandler; allowing `obj.db.attrname = value` - `db` (DataBase) - a shortcut property to the AttributeHandler; allowing `obj.db.attrname = value`
- `nattributes` - the [Non-persistent AttributeHandler](./Attributes.md) for attributes not saved in the - `nattributes` - the [Non-persistent AttributeHandler](./Attributes.md) for attributes not saved in the
database. database.
- `ndb` (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows - `ndb` (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows `obj.ndb.attrname = value`
`obj.ndb.attrname = value`
Each of the typeclassed entities then extend this list with their own properties. Go to the Each of the typeclassed entities then extend this list with their own properties. Go to the respective pages for [Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md) and [Channels](./Channels.md) for more info. It's also recommended that you explore the available entities using [Evennia's flat API](../Evennia-API.md) to explore which properties and methods they have available.
respective pages for [Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md) and
[Channels](./Channels.md) for more info. It's also recommended that you explore the available
entities using [Evennia's flat API](../Evennia-API.md) to explore which properties and methods they have
available.
### Overloading hooks ### Overloading hooks
@ -186,25 +160,16 @@ The way to customize typeclasses is usually to overload *hook methods* on them.
### Querying for typeclasses ### Querying for typeclasses
Most of the time you search for objects in the database by using convenience methods like the Most of the time you search for objects in the database by using convenience methods like the `caller.search()` of [Commands](./Commands.md) or the search functions like `evennia.search_objects`.
`caller.search()` of [Commands](./Commands.md) or the search functions like `evennia.search_objects`.
You can however also query for them directly using [Django's query You can however also query for them directly using [Django's query language](https://docs.djangoproject.com/en/4.1/topics/db/queries/). This makes use of a _database manager_ that sits on all typeclasses, named `objects`. This manager holds methods that allow database searches against that particular type of object (this is the way Django normally works too). When using Django queries, you need to use the full field names (like `db_key`) to search:
language](https://docs.djangoproject.com/en/4.1/topics/db/queries/). This makes use of a _database
manager_ that sits on all typeclasses, named `objects`. This manager holds methods that allow
database searches against that particular type of object (this is the way Django normally works
too). When using Django queries, you need to use the full field names (like `db_key`) to search:
```python ```python
matches = Furniture.objects.get(db_key="Chair") matches = Furniture.objects.get(db_key="Chair")
``` ```
It is important that this will *only* find objects inheriting directly from `Furniture` in your It is important that this will *only* find objects inheriting directly from `Furniture` in your database. If there was a subclass of `Furniture` named `Sitables` you would not find any chairs derived from `Sitables` with this query (this is not a Django feature but special to Evennia). To find objects from subclasses Evennia instead makes the `get_family` and `filter_family` query methods available:
database. If there was a subclass of `Furniture` named `Sitables` you would not find any chairs
derived from `Sitables` with this query (this is not a Django feature but special to Evennia). To
find objects from subclasses Evennia instead makes the `get_family` and `filter_family` query
methods available:
```python ```python
# search for all furnitures and subclasses of furnitures # search for all furnitures and subclasses of furnitures
@ -213,26 +178,18 @@ matches = Furniture.objects.filter_family(db_key__startswith="Chair")
``` ```
To make sure to search, say, all `Scripts` *regardless* of typeclass, you need to query from the To make sure to search, say, all `Scripts` *regardless* of typeclass, you need to query from the database model itself. So for Objects, this would be `ObjectDB` in the diagram above. Here's an example for Scripts:
database model itself. So for Objects, this would be `ObjectDB` in the diagram above. Here's an
example for Scripts:
```python ```python
from evennia import ScriptDB from evennia import ScriptDB
matches = ScriptDB.objects.filter(db_key__contains="Combat") matches = ScriptDB.objects.filter(db_key__contains="Combat")
``` ```
When querying from the database model parent you don't need to use `filter_family` or `get_family` - When querying from the database model parent you don't need to use `filter_family` or `get_family` - you will always query all children on the database model.
you will always query all children on the database model.
### Updating existing typeclass instances ### Updating existing typeclass instances
If you already have created instances of Typeclasses, you can modify the *Python code* at any time - If you already have created instances of Typeclasses, you can modify the *Python code* at any time - due to how Python inheritance works your changes will automatically be applied to all children once you have reloaded the server. However, database-saved data, like `db_*` fields, [Attributes](./Attributes.md), [Tags](./Tags.md) etc, are not themselves embedded into the class and will *not* be updated automatically. This you need to manage yourself, by searching for all relevant objects and updating or adding the data:
due to how Python inheritance works your changes will automatically be applied to all children once you have reloaded the server.
However, database-saved data, like `db_*` fields, [Attributes](./Attributes.md), [Tags](./Tags.md) etc, are
not themselves embedded into the class and will *not* be updated automatically. This you need to
manage yourself, by searching for all relevant objects and updating or adding the data:
```python ```python
# add a worth Attribute to all existing Furniture # add a worth Attribute to all existing Furniture
@ -241,11 +198,7 @@ for obj in Furniture.objects.all():
obj.db.worth = 100 obj.db.worth = 100
``` ```
A common use case is putting all Attributes in the `at_*_creation` hook of the entity, such as A common use case is putting all Attributes in the `at_*_creation` hook of the entity, such as `at_object_creation` for `Objects`. This is called every time an object is created - and only then. This is usually what you want but it does mean already existing objects won't get updated if you change the contents of `at_object_creation` later. You can fix this in a similar way as above (manually setting each Attribute) or with something like this:
`at_object_creation` for `Objects`. This is called every time an object is created - and only then.
This is usually what you want but it does mean already existing objects won't get updated if you
change the contents of `at_object_creation` later. You can fix this in a similar way as above
(manually setting each Attribute) or with something like this:
```python ```python
# Re-run at_object_creation only on those objects not having the new Attribute # Re-run at_object_creation only on those objects not having the new Attribute
@ -254,19 +207,14 @@ for obj in Furniture.objects.all():
obj.at_object_creation() obj.at_object_creation()
``` ```
The above examples can be run in the command prompt created by `evennia shell`. You could also run The above examples can be run in the command prompt created by `evennia shell`. You could also run it all in-game using `@py`. That however requires you to put the code (including imports) as one single line using `;` and [list comprehensions](http://www.secnetix.de/olli/Python/list_comprehensions.hawk), like this (ignore the line break, that's only for readability in the wiki):
it all in-game using `@py`. That however requires you to put the code (including imports) as one
single line using `;` and [list
comprehensions](http://www.secnetix.de/olli/Python/list_comprehensions.hawk), like this (ignore the
line break, that's only for readability in the wiki):
``` ```
py from typeclasses.furniture import Furniture; py from typeclasses.furniture import Furniture;
[obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth] [obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]
``` ```
It is recommended that you plan your game properly before starting to build, to avoid having to It is recommended that you plan your game properly before starting to build, to avoid having to retroactively update objects more than necessary.
retroactively update objects more than necessary.
### Swap typeclass ### Swap typeclass
@ -294,8 +242,7 @@ The arguments to this method are described [in the API docs here](github:evennia
*This is considered an advanced section.* *This is considered an advanced section.*
Technically, typeclasses are [Django proxy models](https://docs.djangoproject.com/en/4.1/topics/db/models/#proxy-models). The only database Technically, typeclasses are [Django proxy models](https://docs.djangoproject.com/en/4.1/topics/db/models/#proxy-models). The only database models that are "real" in the typeclass system (that is, are represented by actual tables in the database) are `AccountDB`, `ObjectDB`, `ScriptDB` and `ChannelDB` (there are also [Attributes](./Attributes.md) and [Tags](./Tags.md) but they are not typeclasses themselves). All the subclasses of them are "proxies", extending them with Python code without actually modifying the database layout.
models that are "real" in the typeclass system (that is, are represented by actual tables in the database) are `AccountDB`, `ObjectDB`, `ScriptDB` and `ChannelDB` (there are also [Attributes](./Attributes.md) and [Tags](./Tags.md) but they are not typeclasses themselves). All the subclasses of them are "proxies", extending them with Python code without actually modifying the database layout.
Evennia modifies Django's proxy model in various ways to allow them to work without any boiler plate (for example you don't need to set the Django "proxy" property in the model `Meta` subclass, Evennia handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as patches django to allow multiple inheritance from the same base class. Evennia modifies Django's proxy model in various ways to allow them to work without any boiler plate (for example you don't need to set the Django "proxy" property in the model `Meta` subclass, Evennia handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as patches django to allow multiple inheritance from the same base class.
@ -303,9 +250,8 @@ Evennia modifies Django's proxy model in various ways to allow them to work with
Evennia uses the *idmapper* to cache its typeclasses (Django proxy models) in memory. The idmapper allows things like on-object handlers and properties to be stored on typeclass instances and to not get lost as long as the server is running (they will only be cleared on a Server reload). Django does not work like this by default; by default every time you search for an object in the database you'll get a *different* instance of that object back and anything you stored on it that was not in the database would be lost. The bottom line is that Evennia's Typeclass instances subside in memory a lot longer than vanilla Django model instance do. Evennia uses the *idmapper* to cache its typeclasses (Django proxy models) in memory. The idmapper allows things like on-object handlers and properties to be stored on typeclass instances and to not get lost as long as the server is running (they will only be cleared on a Server reload). Django does not work like this by default; by default every time you search for an object in the database you'll get a *different* instance of that object back and anything you stored on it that was not in the database would be lost. The bottom line is that Evennia's Typeclass instances subside in memory a lot longer than vanilla Django model instance do.
There is one caveat to consider with this, and that relates to [making your own models](New- There is one caveat to consider with this, and that relates to [making your own models](New-
Models): Foreign relationships to typeclasses are cached by Django and that means that if you were to change an object in a foreign relationship via some other means than via that relationship, the object seeing the relationship may not reliably update but will still see its old cached version. Due to typeclasses staying so long in memory, stale caches of such relationships could be more Models): Foreign relationships to typeclasses are cached by Django and that means that if you were to change an object in a foreign relationship via some other means than via that relationship, the object seeing the relationship may not reliably update but will still see its old cached version. Due to typeclasses staying so long in memory, stale caches of such relationships could be more visible than common in Django. See the [closed issue #1098 and its comments](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
visible than common in Django. See the [closed issue #1098 and its comments](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
## Will I run out of dbrefs? ## Will I run out of dbrefs?

View file

@ -7,17 +7,17 @@ Commands for managing and initiating an in-game character-creation menu.
## Installation ## Installation
In your game folder `commands/default_cmdsets.py`, import and add In your game folder `commands/default_cmdsets.py`, import and add
`ContribCmdCharCreate` to your `AccountCmdSet`. `ContribChargenCmdSet` to your `AccountCmdSet`.
Example: Example:
```python ```python
from evennia.contrib.rpg.character_creator.character_creator import ContribCmdCharCreate from evennia.contrib.rpg.character_creator.character_creator import ContribChargenCmdSet
class AccountCmdSet(default_cmds.AccountCmdSet): class AccountCmdSet(default_cmds.AccountCmdSet):
def at_cmdset_creation(self): def at_cmdset_creation(self):
super().at_cmdset_creation() super().at_cmdset_creation()
self.add(ContribCmdCharCreate) self.add(ContribChargenCmdSet)
``` ```
In your game folder `typeclasses/accounts.py`, import and inherit from `ContribChargenAccount` In your game folder `typeclasses/accounts.py`, import and inherit from `ContribChargenAccount`
@ -100,15 +100,19 @@ character creator menu, as well as supporting exiting/resuming the process. In
addition, unlike the core command, it's designed for the character name to be addition, unlike the core command, it's designed for the character name to be
chosen later on via the menu, so it won't parse any arguments passed to it. chosen later on via the menu, so it won't parse any arguments passed to it.
### Changes to `Account.at_look` ### Changes to `Account`
The contrib version works mostly the same as core evennia, but adds an The contrib version works mostly the same as core evennia, but modifies `ooc_appearance_template`
additional check to recognize an in-progress character. If you've modified your to match the contrib's command syntax, and the `at_look` method to recognize an in-progress
own `at_look` hook, it's an easy addition to make: just add this section to the character.
If you've modified your own `at_look` hook, it's an easy change to add: just add this section to the
playable character list loop. playable character list loop.
```python ```python
# the beginning of the loop starts here
for char in characters: for char in characters:
# ...
# contrib code starts here # contrib code starts here
if char.db.chargen_step: if char.db.chargen_step:
# currently in-progress character; don't display placeholder names # currently in-progress character; don't display placeholder names

View file

@ -24,6 +24,7 @@ evennia.utils
evennia.utils.evtable evennia.utils.evtable
evennia.utils.funcparser evennia.utils.funcparser
evennia.utils.gametime evennia.utils.gametime
evennia.utils.hex_colors
evennia.utils.logger evennia.utils.logger
evennia.utils.optionclasses evennia.utils.optionclasses
evennia.utils.optionhandler evennia.utils.optionhandler

View file

@ -9,7 +9,6 @@ Communication commands:
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from evennia.accounts import bots from evennia.accounts import bots
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.comms.comms import DefaultChannel from evennia.comms.comms import DefaultChannel
@ -1414,14 +1413,14 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
# create the persistent message object # create the persistent message object
target_perms = " or ".join( target_perms = " or ".join(
[f"id({target.id})" for target in targets if target != caller] [f"id({target.id})" for target in targets + [caller]]
) )
create.create_message( create.create_message(
caller, caller,
message, message,
receivers=targets, receivers=targets,
locks=( locks=(
f"read:id({caller.id}) or {target_perms} or perm(Admin);" f"read:{target_perms} or perm(Admin);"
f"delete:id({caller.id}) or perm(Admin);" f"delete:id({caller.id}) or perm(Admin);"
f"edit:id({caller.id}) or perm(Admin)" f"edit:id({caller.id}) or perm(Admin)"
), ),

View file

@ -22,7 +22,6 @@ necessary to easily be able to delete connections on the fly).
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from evennia.comms import managers from evennia.comms import managers
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.typeclasses.models import TypedObject from evennia.typeclasses.models import TypedObject
@ -151,7 +150,7 @@ class Msg(SharedMemoryModel):
db_header = models.TextField("header", null=True, blank=True) db_header = models.TextField("header", null=True, blank=True)
# the message body itself # the message body itself
db_message = models.TextField("message") db_message = models.TextField("message")
# send date # send date (note - this is in UTC. Use the .date_created property to get it in local time)
db_date_created = models.DateTimeField( db_date_created = models.DateTimeField(
"date sent", editable=False, auto_now_add=True, db_index=True "date sent", editable=False, auto_now_add=True, db_index=True
) )
@ -194,6 +193,11 @@ class Msg(SharedMemoryModel):
def tags(self): def tags(self):
return TagHandler(self) return TagHandler(self)
@property
def date_created(self):
"""Return the field in localized time based on settings.TIME_ZONE."""
return timezone.localtime(self.db_date_created)
# Wrapper properties to easily set database fields. These are # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()

View file

@ -13,8 +13,8 @@ game world, policy info, rules and similar.
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify from django.utils.text import slugify
from evennia.help.manager import HelpEntryManager from evennia.help.manager import HelpEntryManager
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.typeclasses.models import AliasHandler, Tag, TagHandler from evennia.typeclasses.models import AliasHandler, Tag, TagHandler
@ -79,7 +79,8 @@ class HelpEntry(SharedMemoryModel):
help_text="tags on this object. Tags are simple string markers to " help_text="tags on this object. Tags are simple string markers to "
"identify, group and alias objects.", "identify, group and alias objects.",
) )
# Creation date. This is not changed once the object is created. # Creation date. This is not changed once the object is created. This is in UTC,
# use the property date_created to get it in local time.
db_date_created = models.DateTimeField("creation date", editable=False, auto_now=True) db_date_created = models.DateTimeField("creation date", editable=False, auto_now=True)
# Database manager # Database manager
@ -100,6 +101,11 @@ class HelpEntry(SharedMemoryModel):
def aliases(self): def aliases(self):
return AliasHandler(self) return AliasHandler(self)
@property
def date_created(self):
"""Return the field in localized time based on settings.TIME_ZONE."""
return timezone.localtime(self.db_date_created)
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
verbose_name = "Help Entry" verbose_name = "Help Entry"

View file

@ -26,6 +26,7 @@ these to create custom managers.
""" """
import evennia
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -33,32 +34,24 @@ from django.db import models
from django.db.models import signals from django.db.models import signals
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
from django.utils.text import slugify from django.utils.text import slugify
import evennia
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.server.signals import SIGNAL_TYPED_OBJECT_POST_RENAME from evennia.server.signals import SIGNAL_TYPED_OBJECT_POST_RENAME
from evennia.typeclasses import managers from evennia.typeclasses import managers
from evennia.typeclasses.attributes import ( from evennia.typeclasses.attributes import (Attribute, AttributeHandler,
Attribute, AttributeProperty, DbHolder,
AttributeHandler, InMemoryAttributeBackend,
AttributeProperty, ModelAttributeBackend)
DbHolder, from evennia.typeclasses.tags import (AliasHandler, PermissionHandler, Tag,
InMemoryAttributeBackend, TagCategoryProperty, TagHandler,
ModelAttributeBackend, TagProperty)
) from evennia.utils.idmapper.models import (SharedMemoryModel,
from evennia.typeclasses.tags import ( SharedMemoryModelBase)
AliasHandler,
PermissionHandler,
Tag,
TagCategoryProperty,
TagHandler,
TagProperty,
)
from evennia.utils.idmapper.models import SharedMemoryModel, SharedMemoryModelBase
from evennia.utils.logger import log_trace from evennia.utils.logger import log_trace
from evennia.utils.utils import class_from_module, inherits_from, is_iter, lazy_property from evennia.utils.utils import (class_from_module, inherits_from, is_iter,
lazy_property)
__all__ = ("TypedObject",) __all__ = ("TypedObject",)
@ -225,7 +218,8 @@ class TypedObject(SharedMemoryModel):
), ),
db_index=True, db_index=True,
) )
# Creation date. This is not changed once the object is created. # Creation date. This is not changed once the object is created. Note that this is UTC,
# use the .date_created property to get a localized version.
db_date_created = models.DateTimeField("creation date", editable=False, auto_now_add=True) db_date_created = models.DateTimeField("creation date", editable=False, auto_now_add=True)
# Lock storage # Lock storage
db_lock_storage = models.TextField( db_lock_storage = models.TextField(
@ -420,6 +414,11 @@ class TypedObject(SharedMemoryModel):
self.at_rename(oldname, value) self.at_rename(oldname, value)
SIGNAL_TYPED_OBJECT_POST_RENAME.send(sender=self, old_key=oldname, new_key=value) SIGNAL_TYPED_OBJECT_POST_RENAME.send(sender=self, old_key=oldname, new_key=value)
@property
def date_created(self):
"""Get the localized date created, based on settings.TIME_ZONE."""
return timezone.localtime(self.db_date_created)
# #
# #
# TypedObject main class methods and properties # TypedObject main class methods and properties