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 ## main branch
- [Feature] Add `evennia.ON_DEMAND_HANDLER` for making it easier to implement - Feature: Add [`evennia.ON_DEMAND_HANDLER`][new-ondemandhandler] for making it
timed element with the on-demand approach (Griatch) easier to implement changes that are calculated on-demand (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][pull3412]: Make it possible to add custom webclient css in - [Feature][pull3412]: Make it possible to add custom webclient css in
`webclient/css/custom.css`, same as for website (InspectorCaracal) `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) - Doc fixes (InspectorCaracal, Griatch)
[new-ondemandhandler][https://www.evennia.com/docs/latest/Components/OnDemandHandler.html]
[pull3412]: https://github.com/evennia/evennia/pull/3412 [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 ## 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. 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? ## 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 ```python
from evennia.contrib.base_systems.components import Component from evennia.contrib.base_systems.components import Component
class Health(Component): class Health(Component):
name = "health" name = "health"
class ItemHealth(Health):
name = "item_health"
slot = "health"
``` ```
Components may define DBFields or NDBFields at the class level. 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. 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. 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 ## Full Example
```python ```python
from evennia.contrib.base_systems import components from evennia.contrib.base_systems import components

View file

@ -98,8 +98,7 @@ found.
## Queryset field lookups ## Queryset field lookups
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_, 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"`.
so it would not find `"Rose"`.
```python ```python
# this is case-sensitive and the same as = # 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 # the i means it's case-insensitive
roses = Flower.objects.filter(db_key__iexact="rose") roses = Flower.objects.filter(db_key__iexact="rose")
``` ```
The Django field query language uses `__` similarly to how Python uses `.` to access resources. This The Django field query language uses `__` similarly to how Python uses `.` to access resources. This is because `.` is not allowed in a function keyword.
is because `.` is not allowed in a function keyword.
```python ```python
roses = Flower.objects.filter(db_key__icontains="rose") 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 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`:
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 ```python
swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword")) swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword"))
@ -178,7 +175,7 @@ will_transform = (
.filter( .filter(
db_location__db_tags__db_key__iexact="moonlit", db_location__db_tags__db_key__iexact="moonlit",
db_attributes__db_key="lycantrophy", 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) 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). - ... 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 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 Running this query makes our newly lycantrophic Character appear in `will_transform` so we know to transform it. Success!
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 ## Queries with OR or NOT
@ -243,7 +243,7 @@ will_transform = (
Q(db_location__db_tags__db_key__iexact="moonlit") Q(db_location__db_tags__db_key__iexact="moonlit")
& ( & (
Q(db_attributes__db_key="lycantrophy", 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") | Q(db_tags__db_key__iexact="recently_bitten")
)) ))
.distinct() .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 from django.db.models import Q
q_moonlit = Q(db_location__db_tags__db_key__iexact="moonlit") 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") q_recently_bitten = Q(db_tags__db_key__iexact="recently_bitten")
will_transform = ( will_transform = (
@ -362,9 +362,7 @@ result = (
) )
``` ```
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then 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.
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 ## 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.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
{"NAME": "evennia.server.validators.EvenniaPasswordValidator"},
] ]
# Username validation plugins # 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.component
evennia.contrib.base_systems.components.dbfield evennia.contrib.base_systems.components.dbfield
evennia.contrib.base_systems.components.exceptions
evennia.contrib.base_systems.components.holder evennia.contrib.base_systems.components.holder
evennia.contrib.base_systems.components.listing
evennia.contrib.base_systems.components.signals evennia.contrib.base_systems.components.signals
evennia.contrib.base_systems.components.tests evennia.contrib.base_systems.components.tests