Add action for setting attributes via endpoint

This commit is contained in:
TehomCD 2020-02-22 12:15:13 -05:00
parent 6f350c60c2
commit 2798dfb712
5 changed files with 92 additions and 8 deletions

View file

@ -277,7 +277,7 @@ class PickledObjectField(models.Field):
return value
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
value = self.value_from_object(obj)
return self.get_db_prep_value(value)
def get_internal_type(self):

View file

@ -1,3 +1,14 @@
"""
Serializers in the Django Rest Framework are similar to Forms in normal django.
They're used for transmitting and validating data, both going to clients and
coming to the server. However, where forms often contained presentation logic,
such as specifying widgets to use for selection, serializers typically leave
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
and converting JSON data sent from clients to our enpoints into python objects,
often django model instances, that we can use (deserialization).
"""
from rest_framework import serializers
from evennia.objects.models import ObjectDB

View file

@ -110,3 +110,24 @@ class TestEvenniaRESTApi(EvenniaTest):
# check success when sending the required data
response = self.client.post(view_url, data=view.create_data)
self.assertEqual(response.status_code, 201, f"Response was {response.data}")
def test_set_attribute(self):
views = self.get_view_details("set-attribute")
for view in views:
with self.subTest(msg=f"Testing {view.view_name}"):
view_url = reverse(f"api:{view.view_name}", kwargs={"pk": view.obj.pk})
# check failures from not sending required fields
response = self.client.post(view_url)
self.assertEqual(response.status_code, 400, f"Response was: {response.data}")
# test adding an attribute
self.assertEqual(view.obj.db.some_test_attr, None)
attr_name = "some_test_attr"
attr_data = {"db_key": attr_name, "db_value": "test_value"}
response = self.client.post(view_url, data=attr_data)
self.assertEqual(response.status_code, 200, f"Response was: {response.data}")
self.assertEquals(view.obj.attributes.get(attr_name), "test_value")
# now test removing it
attr_data = {"db_key": attr_name}
response = self.client.post(view_url, data=attr_data)
self.assertEqual(response.status_code, 200, f"Response was: {response.data}")
self.assertEquals(view.obj.attributes.get(attr_name), None)

View file

@ -1,3 +1,20 @@
"""
The Django Rest Framework provides a way of generating urls for different
views that implement standard CRUD operations in a quick way, using 'routers'
and 'viewsets'. A viewset implements standard CRUD actions and any custom actions
that you want, and then a router will automatically generate URLs based on the
actions that it detects for a viewset. For example, below we create a DefaultRouter.
We then register ObjectDBViewSet, a viewset for CRUD operations for ObjectDB
instances, to the 'objects' base endpoint. That will generate a number of URLs
like the following:
list objects: action: GET, url: /objects/, view name: object-list
create object: action: POST, url: /objects/, view name: object-list
retrieve object: action: GET, url: /objects/<:pk>, view name: object-detail
update object: action: POST, url: /objects/<:pk>, view name: object-detail
delete object: action: DELETE, url: /objects/<:pk>, view name: object-detail
set attribute: action: POST, url: /objects/<:pk>/set-attribute, view name: object-set-attribute
"""
from rest_framework import routers
from evennia.web.api.views import (
ObjectDBViewSet,

View file

@ -5,6 +5,8 @@ can generate a number of views for the common CRUD operations.
"""
from rest_framework.viewsets import ModelViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
from django_filters.rest_framework import DjangoFilterBackend
@ -18,17 +20,28 @@ from evennia.web.api.permissions import EvenniaPermission
class TypeclassViewSetMixin(object):
"""
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 determine who is authorized to call the view
permission_classes = [EvenniaPermission]
# the filter backend allows for retrieval views to have filter arguments passed to it
filter_backends = [DjangoFilterBackend]
class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
serializer_class = ObjectDBSerializer
queryset = ObjectDB.objects.all()
filterset_class = ObjectDBFilterSet
@action(detail=True, methods=["put", "post"])
def add_attribute(self, request, pk=None):
def set_attribute(self, request, pk=None):
"""
This is an example of a custom action added to a viewset. Based on the name of the
method, it will create a default url_name (used for reversing) and url_path.
The 'pk' argument is automatically passed to this action because it has a url path
of the format <object type>/:pk/set-attribute. The get_object method is automatically
set in the expected viewset classes that will inherit this, using the pk that's
passed along to retrieve the object.
This action will set an attribute if the db_value is defined, or remove it
if no db_value is provided.
"""
attr = AttributeSerializer(data=request.data)
obj = self.get_object()
if attr.is_valid(raise_exception=True):
@ -44,27 +57,49 @@ class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
handler.add(key=key, value=value, category=category)
else:
handler.remove(key=key, category=category)
return Response(AttributeSerializer(obj.db_attributes.all(), many=True).data, status=status.HTTP_200_OK)
return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST)
class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""
An example of a basic viewset for all ObjectDB instances. It declares the
serializer to use for both retrieving and changing/creating/deleting
instances. Serializers are similar to django forms, used for the
transmitting of data (typically json).
"""
serializer_class = ObjectDBSerializer
queryset = ObjectDB.objects.all()
filterset_class = ObjectDBFilterSet
class CharacterViewSet(ObjectDBViewSet):
"""
This overrides the queryset to only retrieve Character objects
based on your DefaultCharacter typeclass path.
"""
queryset = DefaultCharacter.objects.typeclass_search(DefaultCharacter.path, include_children=True)
class RoomViewSet(ObjectDBViewSet):
"""Viewset for Room objects"""
queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True)
class ExitViewSet(ObjectDBViewSet):
"""Viewset for Exit objects"""
queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True)
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""Viewset for Account objects"""
serializer_class = AccountDBSerializer
queryset = AccountDB.objects.all()
filterset_class = AccountDBFilterSet
class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""Viewset for Script objects"""
serializer_class = ScriptDBSerializer
queryset = ScriptDB.objects.all()
filterset_class = ScriptDBFilterSet