Correct django querying example. Resolve #3422

This commit is contained in:
Griatch 2024-02-25 18:25:57 +01:00
parent 43e31abc8d
commit 577f66c3ec
8 changed files with 99 additions and 23 deletions

View file

@ -2,16 +2,40 @@
## main branch
- [Feature] Add `evennia.ON_DEMAND_HANDLER` for making it easier to implement
timed element with the on-demand approach (Griatch)
- [Fix] Remove `AMP_ENABLED` setting since it services no real purpose and
erroring out on setting it would make it even less useful (Griatch).
- [Fix] `services` command with no args would traceback (regression) (Griatch)
- Feature: Add [`evennia.ON_DEMAND_HANDLER`][new-ondemandhandler] for making it
easier to implement changes that are calculated on-demand (Griatch)
- [Feature][pull3412]: Make it possible to add custom webclient css in
`webclient/css/custom.css`, same as for website (InspectorCaracal)
- [Feature][pull3367]: [Component contrib][pull3367extra] got better
inheritance, slot names to choose attr storage, speedups and fixes (ChrisLR)
- Feature: Break up `DefaultObject.search` method into several helpers to make
it easier to override (Griatch)
- Fix: Resolve multimatch error with rpsystem contrib (Griatch)
- Fix: Remove `AMP_ENABLED` setting since it services no real purpose and
erroring out on setting it would make it even less useful (Griatch).
- Feature: Remove too-strict password restrictions for Evennia logins, using
django defaults instead for passwords with more varied characters.
- Fix `services` command with no args would traceback (regression) (Griatch)
- [Fix][pull3423]: Fix wilderness contrib error moving to an already existing
wilderness room (InspectorCaracal)
- [Fix][pull3425]: Don't always include example the crafting recipe when
using the crafting contrib (InspectorCaracal)
- [Fix][pull3426]: Traceback banning a channel using with only one nick
(InspectorCaracal)
- [Fix][pull3434]: Adjust lunr search weights to void clashing of cmd-aliases over
keys which caused some help entries to shadow others (InspectorCaracal)
- Fix: Make `menu/email_login` contribs honor `NEW_ACCOUNT_REGISTRATION_ENABLED`
setting (Griatch)
- Doc fixes (InspectorCaracal, Griatch)
[new-ondemandhandler][https://www.evennia.com/docs/latest/Components/OnDemandHandler.html]
[pull3412]: https://github.com/evennia/evennia/pull/3412
[pull3423]: https://github.com/evennia/evennia/pull/3423
[pull3425]: https://github.com/evennia/evennia/pull/3425
[pull3426]: https://github.com/evennia/evennia/pull/3426
[pull3434]: https://github.com/evennia/evennia/pull/3434
[pull3367]: https://github.com/evennia/evennia/pull/3367
[pull3367extra]: https://www.evennia.com/docs/latest/Contribs/Contrib-Components.html
## Evennia 3.1.1

View file

@ -294,6 +294,20 @@ A lock is no good if nothing checks it -- and by default Evennia does not check
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`, those will check for the `attredit` lock type.
## Querying by Attribute
While you can get attributes using the `obj.attributes.get` handler, you can also find objects based on the Attributes they have through the `db_attributes` many-to-many field available on each typeclassed entity:
```python
# find objects by attribue assigned (regardless of value)
objs = evennia.ObjectDB.objects.filter(db_attributes__db_key="foo")
# find objects with attribute of particular value assigned to them
objs = evennia.ObjectDB.objects.filter(db_attributes__db_key="foo", db_attributes__db_value="bar")
```
```{important}
Internally, Attribute values are stored as _pickled strings_ (see next section). When querying, your search string is converted to the same format and matched in that form. While this means Attributes can store arbitrary Python structures, the drawback is that you cannot do more advanced database comparisons on them. For example doing `db_attributes__db__value__lt=4` or `__gt=0` will not work since less-than and greater-than doesn't do what you want between strings.
```
## What types of data can I save in an Attribute?

View file

@ -30,12 +30,20 @@ class Character(ComponentHolderMixin, DefaultCharacter):
# ...
```
Components need to inherit the Component class directly and require a name.
Components need to inherit the Component class and require a unique name.
Components may inherit from other components but must specify another name.
You can assign the same 'slot' to both components to have alternative implementations.
```python
from evennia.contrib.base_systems.components import Component
class Health(Component):
name = "health"
class ItemHealth(Health):
name = "item_health"
slot = "health"
```
Components may define DBFields or NDBFields at the class level.
@ -103,7 +111,10 @@ character.components.add(vampirism)
...
vampirism_from_elsewhere = character.components.get("vampirism")
vampirism = character.components.get("vampirism")
# Alternatively
vampirism = character.cmp.vampirism
```
Keep in mind that all components must be imported to be visible in the listing.
@ -128,6 +139,14 @@ from typeclasses.components import health
```
Both of the above examples will work.
## Known Issues
Assigning mutable default values such as a list to a DBField will share it across instances.
To avoid this, you must set autocreate=True on the field, like this.
```python
health = DBField(default=[], autocreate=True)
```
## Full Example
```python
from evennia.contrib.base_systems import components

View file

@ -98,8 +98,7 @@ found.
## Queryset field lookups
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
so it would not find `"Rose"`.
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_, so it would not find `"Rose"`.
```python
# this is case-sensitive and the same as =
@ -108,15 +107,13 @@ roses = Flower.objects.filter(db_key__exact="rose"
# the i means it's case-insensitive
roses = Flower.objects.filter(db_key__iexact="rose")
```
The Django field query language uses `__` similarly to how Python uses `.` to access resources. This
is because `.` is not allowed in a function keyword.
The Django field query language uses `__` similarly to how Python uses `.` to access resources. This is because `.` is not allowed in a function keyword.
```python
roses = Flower.objects.filter(db_key__icontains="rose")
```
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The `i` in the beginning makes the search case-insensitive. Other useful variations to use
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than" comparisons (same for `__lt` and `__le`). There is also `__in`:
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The `i` in the beginning makes the search case-insensitive. Other useful variations to use are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than" comparisons (same for `__lt` and `__le`). There is also `__in`:
```python
swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword"))
@ -178,7 +175,7 @@ will_transform = (
.filter(
db_location__db_tags__db_key__iexact="moonlit",
db_attributes__db_key="lycantrophy",
db_attributes__db_value__gt=2
db_attributes__db_value__eq=2
)
)
```
@ -193,10 +190,13 @@ Don't confuse database fields with [Attributes](../../../Components/Attributes.m
that we can treat like an object for this purpose; it references all Tags on the location)
- ... and from those `Tags`, we looking for `Tags` whose `db_key` is "monlit" (non-case sensitive).
- **Line 7**: ... We also want only Characters with `Attributes` whose `db_key` is exactly `"lycantrophy"`
- **Line 8** :... at the same time as the `Attribute`'s `db_value` is greater-than 2.
- **Line 8** :... at the same time as the `Attribute`'s `db_value` is exactly 2.
Running this query makes our newly lycantrophic Character appear in `will_transform` so we
know to transform it. Success!
Running this query makes our newly lycantrophic Character appear in `will_transform` so we know to transform it. Success!
```{important}
You can't query for an Attribute `db_value` quite as freely as other data-types. This is because Attributes can store any Python entity and is actually stored as _strings_ on the database side. So while you can use `__eq=2` in the above example, you will not be able to `__gt=2` or `__lt=2` because these operations don't make sense for strings. See [Attributes](../../../Components/Attributes.md#querying-by-attribute) for more information on dealing with Attributes.
```
## Queries with OR or NOT
@ -243,7 +243,7 @@ will_transform = (
Q(db_location__db_tags__db_key__iexact="moonlit")
& (
Q(db_attributes__db_key="lycantrophy",
db_attributes__db_value__gt=2)
db_attributes__db_value__eq=2)
| Q(db_tags__db_key__iexact="recently_bitten")
))
.distinct()
@ -256,7 +256,7 @@ That's quite compact. It may be easier to see what's going on if written this wa
from django.db.models import Q
q_moonlit = Q(db_location__db_tags__db_key__iexact="moonlit")
q_lycantropic = Q(db_attributes__db_key="lycantrophy", db_attributes__db_value__gt=2)
q_lycantropic = Q(db_attributes__db_key="lycantrophy", db_attributes__db_value__eq=2)
q_recently_bitten = Q(db_tags__db_key__iexact="recently_bitten")
will_transform = (
@ -362,9 +362,7 @@ result = (
)
```
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then
directly use these results in the filter. Using `F()` allows for also the right-hand-side of the filter
condition to be calculated on the fly, completely within the database.
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then directly use these results in the filter. Using `F()` allows for also the right-hand-side of the filter condition to be calculated on the fly, completely within the database.
## Grouping and returning only certain properties

View file

@ -1115,7 +1115,6 @@ AUTH_PASSWORD_VALIDATORS = [
},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
{"NAME": "evennia.server.validators.EvenniaPasswordValidator"},
]
# Username validation plugins

View file

@ -0,0 +1,10 @@
```{eval-rst}
evennia.contrib.base\_systems.components.exceptions
==========================================================
.. automodule:: evennia.contrib.base_systems.components.exceptions
:members:
:undoc-members:
:show-inheritance:
```

View file

@ -0,0 +1,10 @@
```{eval-rst}
evennia.contrib.base\_systems.components.listing
=======================================================
.. automodule:: evennia.contrib.base_systems.components.listing
:members:
:undoc-members:
:show-inheritance:
```

View file

@ -14,7 +14,9 @@ evennia.contrib.base\_systems.components
evennia.contrib.base_systems.components.component
evennia.contrib.base_systems.components.dbfield
evennia.contrib.base_systems.components.exceptions
evennia.contrib.base_systems.components.holder
evennia.contrib.base_systems.components.listing
evennia.contrib.base_systems.components.signals
evennia.contrib.base_systems.components.tests