Add helpentry api

This commit is contained in:
Griatch 2021-05-23 15:16:55 +02:00
parent 87e0796f05
commit cc9f42a398
5 changed files with 199 additions and 44 deletions

View file

@ -5,6 +5,7 @@ that is retrieved in GET requests. By default, Django Rest Framework uses the
documentation specifically regarding DRF integration. documentation specifically regarding DRF integration.
https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html https://django-filter.readthedocs.io/en/latest/guide/rest_framework.html
""" """
from typing import Union from typing import Union
from django.db.models import Q from django.db.models import Q
@ -25,6 +26,7 @@ def get_tag_query(tag_type: Union[str, None], key: str) -> Q:
Returns: Returns:
A Q object that for searching by this tag type and name A Q object that for searching by this tag type and name
""" """
return Q(db_tags__db_tagtype=tag_type) & Q(db_tags__db_key__iexact=key) return Q(db_tags__db_tagtype=tag_type) & Q(db_tags__db_key__iexact=key)
@ -32,6 +34,7 @@ def get_tag_query(tag_type: Union[str, None], key: str) -> Q:
class TagTypeFilter(CharFilter): class TagTypeFilter(CharFilter):
""" """
This class lets you create different filters for tags of a specified db_tagtype. This class lets you create different filters for tags of a specified db_tagtype.
""" """
tag_type = None tag_type = None
@ -60,11 +63,14 @@ SHARED_FIELDS = ["db_key", "db_typeclass_path", "db_tags__db_key", "db_tags__db_
class BaseTypeclassFilterSet(FilterSet): class BaseTypeclassFilterSet(FilterSet):
"""A parent class with filters for aliases and permissions""" """
A parent class with filters for aliases and permissions
"""
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
alias = AliasFilter(lookup_expr="iexact") alias = AliasFilter(lookup_expr="iexact")
permission = PermissionFilter(lookup_expr="iexact") permission = PermissionFilter(lookup_expr="iexact")
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
@staticmethod @staticmethod
def filter_name(queryset, name, value): def filter_name(queryset, name, value):
@ -84,7 +90,10 @@ class BaseTypeclassFilterSet(FilterSet):
class ObjectDBFilterSet(BaseTypeclassFilterSet): class ObjectDBFilterSet(BaseTypeclassFilterSet):
"""This adds filters for ObjectDB instances - characters, rooms, exits, etc""" """
This adds filters for ObjectDB instances - characters, rooms, exits, etc
"""
class Meta: class Meta:
model = ObjectDB model = ObjectDB
@ -103,7 +112,9 @@ class AccountDBFilterSet(BaseTypeclassFilterSet):
class Meta: class Meta:
model = AccountDB model = AccountDB
fields = SHARED_FIELDS + ["username", "db_is_connected", "db_is_bot"] fields = [fi for fi in (SHARED_FIELDS + ["username", "db_is_connected", "db_is_bot"])
if fi != 'db_key']
class ScriptDBFilterSet(BaseTypeclassFilterSet): class ScriptDBFilterSet(BaseTypeclassFilterSet):
@ -121,3 +132,16 @@ class ScriptDBFilterSet(BaseTypeclassFilterSet):
"db_persistent", "db_persistent",
"db_interval", "db_interval",
] ]
class HelpFilterSet(FilterSet):
"""
Filter for help entries
"""
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
category = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_category")
alias = AliasFilter(lookup_expr="iexact")

View file

@ -7,6 +7,7 @@ those decisions in the hands of clients, and are more focused on converting
data from the server to JSON (serialization) for a response, and validating data from the server to JSON (serialization) for a response, and validating
and converting JSON data sent from clients to our enpoints into python objects, and converting JSON data sent from clients to our enpoints into python objects,
often django model instances, that we can use (deserialization). often django model instances, that we can use (deserialization).
""" """
from rest_framework import serializers from rest_framework import serializers
@ -16,9 +17,14 @@ from evennia.accounts.accounts import DefaultAccount
from evennia.scripts.models import ScriptDB from evennia.scripts.models import ScriptDB
from evennia.typeclasses.attributes import Attribute from evennia.typeclasses.attributes import Attribute
from evennia.typeclasses.tags import Tag from evennia.typeclasses.tags import Tag
from evennia.help.models import HelpEntry
class AttributeSerializer(serializers.ModelSerializer): class AttributeSerializer(serializers.ModelSerializer):
"""
Serialize Attribute views.
"""
value_display = serializers.SerializerMethodField(source="value") value_display = serializers.SerializerMethodField(source="value")
db_value = serializers.CharField(write_only=True, required=False) db_value = serializers.CharField(write_only=True, required=False)
@ -35,6 +41,7 @@ class AttributeSerializer(serializers.ModelSerializer):
Returns: Returns:
The Attribute's value in string format The Attribute's value in string format
""" """
if obj.db_strvalue: if obj.db_strvalue:
return obj.db_strvalue return obj.db_strvalue
@ -53,13 +60,17 @@ class SimpleObjectDBSerializer(serializers.ModelSerializer):
fields = ["id", "db_key"] fields = ["id", "db_key"]
class TypeclassSerializerMixin(object): class TypeclassSerializerMixin:
"""Mixin that contains types shared by typeclasses. A note about tags, aliases, and permissions. You """
might note that the methods and fields are defined here, but they're included explicitly in each child Mixin that contains types shared by typeclasses. A note about tags,
class. What gives? It's a DRF error: serializer method fields which are inherited do not resolve correctly aliases, and permissions. You might note that the methods and fields are
in child classes, and as of this current version (3.11) you must have them in the child classes explicitly defined here, but they're included explicitly in each child class. What
to avoid field errors. Similarly, the child classes must contain the attribute serializer explicitly to gives? It's a DRF error: serializer method fields which are inherited do
not have them render PK-related fields. not resolve correctly in child classes, and as of this current version
(3.11) you must have them in the child classes explicitly to avoid field
errors. Similarly, the child classes must contain the attribute serializer
explicitly to not have them render PK-related fields.
""" """
shared_fields = [ shared_fields = [
@ -135,7 +146,24 @@ class TypeclassSerializerMixin(object):
return AttributeSerializer(obj.nicks.all(), many=True).data return AttributeSerializer(obj.nicks.all(), many=True).data
class TypeclassListSerializerMixin:
"""
Shortened serializer for list views.
"""
shared_fields = [
"id",
"db_key",
"db_typeclass_path",
]
class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""
Serializing Objects.
"""
attributes = serializers.SerializerMethodField() attributes = serializers.SerializerMethodField()
nicks = serializers.SerializerMethodField() nicks = serializers.SerializerMethodField()
contents = serializers.SerializerMethodField() contents = serializers.SerializerMethodField()
@ -182,8 +210,25 @@ class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
return SimpleObjectDBSerializer(non_exits, many=True).data return SimpleObjectDBSerializer(non_exits, many=True).data
class ObjectListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer):
"""
Shortened representation for listings.]
"""
class Meta:
model = DefaultObject
fields = [
"db_location",
"db_home",
] + TypeclassListSerializerMixin.shared_fields
read_only_fields = ["id"]
class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""This uses the DefaultAccount object to have access to the sessions property""" """
This uses the DefaultAccount object to have access to the sessions property
"""
attributes = serializers.SerializerMethodField() attributes = serializers.SerializerMethodField()
nicks = serializers.SerializerMethodField() nicks = serializers.SerializerMethodField()
@ -211,7 +256,23 @@ class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
read_only_fields = ["id"] read_only_fields = ["id"]
class AccountListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer):
"""
A shortened form for listing.
"""
class Meta:
model = DefaultAccount
fields = ["username"] + [
fi for fi in TypeclassListSerializerMixin.shared_fields if fi != "db_key"]
read_only_fields = ["id"]
class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer): class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""
Serializing Account.
"""
attributes = serializers.SerializerMethodField() attributes = serializers.SerializerMethodField()
tags = serializers.SerializerMethodField() tags = serializers.SerializerMethodField()
aliases = serializers.SerializerMethodField() aliases = serializers.SerializerMethodField()
@ -227,3 +288,47 @@ class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"db_repeats", "db_repeats",
] + TypeclassSerializerMixin.shared_fields ] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id"] read_only_fields = ["id"]
class ScriptListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer):
"""
Shortened form for listing.
"""
class Meta:
model = ScriptDB
fields = [
"db_interval",
"db_persistent",
"db_start_delay",
"db_is_active",
"db_repeats",
] + TypeclassListSerializerMixin.shared_fields
read_only_fields = ["id"]
class HelpSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""
Serializers Help entries (not a typeclass).
"""
tags = serializers.SerializerMethodField()
aliases = serializers.SerializerMethodField()
class Meta:
model = HelpEntry
fields = [
"id", "db_key", "db_help_category", "db_entrytext", "db_date_created",
"tags", "aliases"
]
class HelpListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer):
"""
Shortened form for listings.
"""
class Meta:
model = HelpEntry
fields = [
"id", "db_key", "db_help_category", "db_date_created",
]

View file

@ -1,4 +1,7 @@
"""Tests for the REST API""" """
Tests for the REST API.
"""
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from evennia.web.api import serializers from evennia.web.api import serializers
from rest_framework.test import APIClient from rest_framework.test import APIClient

View file

@ -22,25 +22,19 @@ from django.views.generic import TemplateView
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
from evennia.web.api.root import APIRootRouter from evennia.web.api.root import APIRootRouter
from evennia.web.api.views import ( from evennia.web.api import views
ObjectDBViewSet,
AccountDBViewSet,
CharacterViewSet,
ExitViewSet,
RoomViewSet,
ScriptDBViewSet,
)
app_name = "api" app_name = "api"
router = APIRootRouter() router = APIRootRouter()
router.trailing_slash = "/?" router.trailing_slash = "/?"
router.register(r"accounts", AccountDBViewSet, basename="account") router.register(r"accounts", views.AccountDBViewSet, basename="account")
router.register(r"objects", ObjectDBViewSet, basename="object") router.register(r"objects", views.ObjectDBViewSet, basename="object")
router.register(r"characters", CharacterViewSet, basename="character") router.register(r"characters", views.CharacterViewSet, basename="character")
router.register(r"exits", ExitViewSet, basename="exit") router.register(r"exits", views.ExitViewSet, basename="exit")
router.register(r"rooms", RoomViewSet, basename="room") router.register(r"rooms", views.RoomViewSet, basename="room")
router.register(r"scripts", ScriptDBViewSet, basename="script") router.register(r"scripts", views.ScriptDBViewSet, basename="script")
router.register(r"helpentries", views.HelpViewSet, basename="script")
urlpatterns = router.urls urlpatterns = router.urls

View file

@ -15,17 +15,29 @@ from evennia.objects.models import ObjectDB
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.scripts.models import ScriptDB from evennia.scripts.models import ScriptDB
from evennia.web.api.serializers import ( from evennia.help.models import HelpEntry
ObjectDBSerializer, from evennia.web.api import serializers
AccountSerializer, from evennia.web.api import filters
ScriptDBSerializer,
AttributeSerializer,
)
from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet
from evennia.web.api.permissions import EvenniaPermission from evennia.web.api.permissions import EvenniaPermission
class TypeclassViewSetMixin: class GeneralViewSetMixin:
"""
Mixin for both typeclass- and non-typeclass entities.
"""
def get_serializer_class(self):
"""
Allow different serializers for certain actions.
"""
if self.action == 'list':
if hasattr(self, "list_serializer_class"):
return self.list_serializer_class
return self.serializer_class
class TypeclassViewSetMixin(GeneralViewSetMixin):
""" """
This mixin adds some shared functionality to each viewset of a typeclass. They all use the same This mixin adds some shared functionality to each viewset of a typeclass. They all use the same
permission classes and filter backend. You can override any of these in your own viewsets. permission classes and filter backend. You can override any of these in your own viewsets.
@ -53,7 +65,7 @@ class TypeclassViewSetMixin:
it if no db_value is provided. it if no db_value is provided.
""" """
attr = AttributeSerializer(data=request.data) attr = serializers.AttributeSerializer(data=request.data)
obj = self.get_object() obj = self.get_object()
if attr.is_valid(raise_exception=True): if attr.is_valid(raise_exception=True):
key = attr.validated_data["db_key"] key = attr.validated_data["db_key"]
@ -69,7 +81,7 @@ class TypeclassViewSetMixin:
else: else:
handler.remove(key=key, category=category) handler.remove(key=key, category=category)
return Response( return Response(
AttributeSerializer(obj.db_attributes.all(), many=True).data, serializers.AttributeSerializer(obj.db_attributes.all(), many=True).data,
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )
return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST) return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST)
@ -86,9 +98,10 @@ class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
# instances. Serializers are similar to django forms, used for the # instances. Serializers are similar to django forms, used for the
# transmitting of data (typically json). # transmitting of data (typically json).
serializer_class = ObjectDBSerializer serializer_class = serializers.ObjectDBSerializer
queryset = ObjectDB.objects.all() queryset = ObjectDB.objects.all()
filterset_class = ObjectDBFilterSet filterset_class = filters.ObjectDBFilterSet
list_serializer_class = serializers.ObjectListSerializer
class CharacterViewSet(ObjectDBViewSet): class CharacterViewSet(ObjectDBViewSet):
@ -96,10 +109,10 @@ class CharacterViewSet(ObjectDBViewSet):
Characters are a type of Object commonly used as player avatars in-game. Characters are a type of Object commonly used as player avatars in-game.
""" """
queryset = DefaultCharacter.objects.typeclass_search( queryset = DefaultCharacter.objects.typeclass_search(
DefaultCharacter.path, include_children=True DefaultCharacter.path, include_children=True
) )
list_serializer_class = serializers.ObjectListSerializer
class RoomViewSet(ObjectDBViewSet): class RoomViewSet(ObjectDBViewSet):
@ -109,6 +122,7 @@ class RoomViewSet(ObjectDBViewSet):
""" """
queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True) queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True)
list_serializer_class = serializers.ObjectListSerializer
class ExitViewSet(ObjectDBViewSet): class ExitViewSet(ObjectDBViewSet):
@ -119,6 +133,7 @@ class ExitViewSet(ObjectDBViewSet):
""" """
queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True) queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True)
list_serializer_class = serializers.ObjectListSerializer
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet): class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
@ -127,9 +142,10 @@ class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
""" """
serializer_class = AccountSerializer serializer_class = serializers.AccountSerializer
queryset = AccountDB.objects.all() queryset = AccountDB.objects.all()
filterset_class = AccountDBFilterSet filterset_class = filters.AccountDBFilterSet
list_serializer_class = serializers.AccountListSerializer
class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet): class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
@ -139,6 +155,19 @@ class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
""" """
serializer_class = ScriptDBSerializer serializer_class = serializers.ScriptDBSerializer
queryset = ScriptDB.objects.all() queryset = ScriptDB.objects.all()
filterset_class = ScriptDBFilterSet filterset_class = filters.ScriptDBFilterSet
list_serializer_class = serializers.ScriptListSerializer
class HelpViewSet(GeneralViewSetMixin, ModelViewSet):
"""
Database-stored help entries.
Note that command auto-help and file-based help entries are not accessible this way.
"""
serializer_class = serializers.HelpSerializer
queryset = HelpEntry.objects.all()
filterset_class = filters.HelpFilterSet
list_serializer_class = serializers.HelpListSerializer