Add more fields to serializers

This commit is contained in:
TehomCD 2020-03-01 19:11:04 -05:00
parent ec570a17cd
commit 1c4fabbfa2
5 changed files with 161 additions and 38 deletions

View file

@ -17,11 +17,20 @@ class EvenniaPermission(permissions.BasePermission):
update_locks = settings.REST_FRAMEWORK.get("DEFAULT_UPDATE_LOCKS", ["control", "edit"])
def has_permission(self, request, view):
"""
This method is a check that always happens first. If there's an object involved,
such as with retrieve, update, or delete, then the has_object_permission method
is also called if this returns True. If we return False, a permission denied
error is raised.
"""Checks for permissions
Args:
request (Request): The incoming request object.
view (View): The django view we are checking permission for.
Returns:
bool: If permission is granted or not. If we return False here, a PermissionDenied
error will be raised from the view.
Notes:
This method is a check that always happens first. If there's an object involved,
such as with retrieve, update, or delete, then the has_object_permission method
is called after this, assuming this returns `True`.
"""
# Only allow authenticated users to call the API
if not request.user.is_authenticated:
@ -37,17 +46,34 @@ class EvenniaPermission(permissions.BasePermission):
@staticmethod
def check_locks(obj, user, locks):
"""Checks access for user for object with given locks
Args:
obj: Object instance we're checking
user (Account): User who we're checking permissions
locks (list): list of lockstrings
Returns:
bool: True if they have access, False if they don't
"""
return any([obj.access(user, lock) for lock in locks])
def has_object_permission(self, request, view, obj):
"""Checks object-level permissions after has_permission
Args:
request (Request): The incoming request object.
view (View): The django view we are checking permission for.
obj: Object we're checking object-level permissions for
Returns:
bool: If permission is granted or not. If we return False here, a PermissionDenied
error will be raised from the view.
Notes:
This method assumes that has_permission has already returned True. We check
equivalent Evennia permissions in the request.user to determine if they can
complete the action.
"""
This method assumes that has_permission has already returned True. We check
equivalent Evennia permissions in the request.user to determine if they can
complete the action. If so, we return True. Otherwise we return False, and
a permission denied error will be raised.
"""
if request.user.is_superuser:
return True
if view.action in ("list", "retrieve"):
# access_type is based on the examine command
return self.check_locks(obj, request.user, self.view_locks)

View file

@ -11,8 +11,8 @@ often django model instances, that we can use (deserialization).
from rest_framework import serializers
from evennia.objects.models import ObjectDB
from evennia.accounts.models import AccountDB
from evennia.objects.objects import DefaultObject
from evennia.accounts.accounts import DefaultAccount
from evennia.scripts.models import ScriptDB
from evennia.typeclasses.attributes import Attribute
from evennia.typeclasses.tags import Tag
@ -32,36 +32,122 @@ class TagSerializer(serializers.ModelSerializer):
class SimpleObjectDBSerializer(serializers.ModelSerializer):
class Meta:
model = ObjectDB
model = DefaultObject
fields = ["id", "db_key"]
class TypeclassSerializerMixin(object):
"""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
class. What gives? It's a DRF error: serializer method fields which are inherited do 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.
"""
db_attributes = AttributeSerializer(many=True)
db_tags = TagSerializer(many=True)
shared_fields = ["id", "db_key", "db_attributes", "db_tags", "db_typeclass_path"]
shared_fields = ["id", "db_key", "db_attributes", "db_typeclass_path", "aliases", "tags", "permissions"]
def get_tags(self, obj):
"""
Serializes tags from the object's Tagshandler
Args:
obj: Typeclassed object being serialized
Returns:
List of TagSerializer data
"""
return TagSerializer(obj.tags.get(return_tagobj=True, return_list=True), many=True).data
def get_aliases(self, obj):
"""
Serializes tags from the object's Aliashandler
Args:
obj: Typeclassed object being serialized
Returns:
List of TagSerializer data
"""
return TagSerializer(obj.aliases.get(return_tagobj=True, return_list=True), many=True).data
def get_permissions(self, obj):
"""
Serializes tags from the object's Permissionshandler
Args:
obj: Typeclassed object being serialized
Returns:
List of TagSerializer data
"""
return TagSerializer(obj.permissions.get(return_tagobj=True, return_list=True), many=True).data
class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
contents = SimpleObjectDBSerializer(source="locations_set", many=True, read_only=True)
contents = serializers.SerializerMethodField()
exits = serializers.SerializerMethodField()
tags = serializers.SerializerMethodField()
aliases = serializers.SerializerMethodField()
permissions = serializers.SerializerMethodField()
class Meta:
model = ObjectDB
fields = ["db_location", "db_home", "contents"] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id", "db_attributes", "db_tags"]
model = DefaultObject
fields = ["db_location", "db_home", "contents", "exits"] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id", "db_attributes"]
def get_exits(self, obj):
"""
Gets exits for the object
Args:
obj: Object being serialized
Returns:
List of data from SimpleObjectDBSerializer
"""
exits = [ob for ob in obj.contents if ob.destination]
return SimpleObjectDBSerializer(exits, many=True).data
def get_contents(self, obj):
"""
Gets non-exits for the object
Args:
obj: Object being serialized
Returns:
List of data from SimpleObjectDBSerializer
"""
non_exits = [ob for ob in obj.contents if not ob.destination]
return SimpleObjectDBSerializer(non_exits, many=True).data
class AccountDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"""This uses the DefaultAccount object to have access to the sessions property"""
db_key = serializers.CharField(required=False)
session_ids = serializers.SerializerMethodField()
tags = serializers.SerializerMethodField()
aliases = serializers.SerializerMethodField()
permissions = serializers.SerializerMethodField()
def get_session_ids(self, obj):
"""
Gets a list of session IDs connected to this Account
Args:
obj (DefaultAccount): Account we're grabbing sessions from
Returns:
List of session IDs
"""
return [sess.sessid for sess in obj.sessions.all() if hasattr(sess, "sessid")]
class Meta:
model = AccountDB
fields = ["username"] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id", "db_attributes", "db_tags"]
model = DefaultAccount
fields = ["username", "session_ids"] + TypeclassSerializerMixin.shared_fields
read_only_fields = ["id", "db_attributes", "db_tags", "session_ids"]
class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
tags = serializers.SerializerMethodField()
aliases = serializers.SerializerMethodField()
permissions = serializers.SerializerMethodField()
class Meta:
model = ScriptDB
fields = ["db_interval", "db_persistent", "db_start_delay",

View file

@ -26,6 +26,8 @@ class TestEvenniaRESTApi(EvenniaTest):
self.account.is_superuser = True
self.account.save()
self.client.force_login(self.account)
# scripts do not have default locks. Without them, even superuser access check fails
self.script.locks.add("edit: perm(Admin); examine: perm(Admin); delete: perm(Admin)")
def tearDown(self):
try:
@ -35,21 +37,27 @@ class TestEvenniaRESTApi(EvenniaTest):
def get_view_details(self, action):
"""Helper function for generating list of named tuples"""
View = namedtuple("View", ["view_name", "obj", "list", "serializer", "create_data"])
View = namedtuple("View", ["view_name", "obj", "list", "serializer", "create_data", "retrieve_data"])
views = [
View("object-%s" % action, self.obj1, [self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2,
self.char2], serializers.ObjectDBSerializer,
{"db_key": "object-create-test-name"}),
{"db_key": "object-create-test-name"},
serializers.ObjectDBSerializer(self.obj1).data),
View("character-%s" % action, self.char1, [self.char1, self.char2], serializers.ObjectDBSerializer,
{"db_key": "character-create-test-name"}),
{"db_key": "character-create-test-name"},
serializers.ObjectDBSerializer(self.char1).data),
View("exit-%s" % action, self.exit, [self.exit], serializers.ObjectDBSerializer,
{"db_key": "exit-create-test-name"}),
{"db_key": "exit-create-test-name"},
serializers.ObjectDBSerializer(self.exit).data),
View("room-%s" % action, self.room1, [self.room1, self.room2], serializers.ObjectDBSerializer,
{"db_key": "room-create-test-name"}),
{"db_key": "room-create-test-name"},
serializers.ObjectDBSerializer(self.room1).data),
View("script-%s" % action, self.script, [self.script], serializers.ScriptDBSerializer,
{"db_key": "script-create-test-name"}),
View("account-%s" % action, self.account2, [self.account, self.account2], serializers.AccountDBSerializer,
{"username": "account-create-test-name"}),
{"db_key": "script-create-test-name"},
serializers.ScriptDBSerializer(self.script).data),
View("account-%s" % action, self.account2, [self.account, self.account2], serializers.AccountSerializer,
{"username": "account-create-test-name"},
serializers.AccountSerializer(self.account2).data),
]
return views
@ -62,6 +70,7 @@ class TestEvenniaRESTApi(EvenniaTest):
)
response = self.client.get(view_url)
self.assertEqual(response.status_code, 200)
self.assertDictEqual(response.data, view.retrieve_data)
def test_update(self):
views = self.get_view_details("detail")
@ -78,6 +87,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.assertEqual(response.status_code, 200)
view.obj.refresh_from_db()
self.assertEqual(getattr(view.obj, field), new_key)
self.assertEqual(response.data[field], new_key)
def test_delete(self):
views = self.get_view_details("detail")

View file

@ -14,7 +14,7 @@ from evennia.objects.models import ObjectDB
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom
from evennia.accounts.models import AccountDB
from evennia.scripts.models import ScriptDB
from evennia.web.api.serializers import ObjectDBSerializer, AccountDBSerializer, ScriptDBSerializer, AttributeSerializer
from evennia.web.api.serializers import ObjectDBSerializer, AccountSerializer, ScriptDBSerializer, AttributeSerializer
from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet
from evennia.web.api.permissions import EvenniaPermission
@ -26,7 +26,8 @@ class TypeclassViewSetMixin(object):
"""
# 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
# the filter backend allows for retrieval views to have filter arguments passed to it,
# for example: mygame.com/api/objects?db_key=bob to find matches based on objects having a db_key of bob
filter_backends = [DjangoFilterBackend]
@action(detail=True, methods=["put", "post"])
@ -93,7 +94,7 @@ class ExitViewSet(ObjectDBViewSet):
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
"""Viewset for Account objects"""
serializer_class = AccountDBSerializer
serializer_class = AccountSerializer
queryset = AccountDB.objects.all()
filterset_class = AccountDBFilterSet

View file

@ -5,8 +5,8 @@ attrs >= 19.2.0
django >= 2.2.5, < 2.3
twisted >= 19.2.1, < 20.0.0
pytz
djangorestframework >= 3.10.3
django-filter >= 2.2.0
djangorestframework >= 3.10.3, < 3.12
django-filter >= 2.2.0, < 2.3
django-sekizai
inflect
autobahn >= 17.9.3