mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
Add more fields to serializers
This commit is contained in:
parent
ec570a17cd
commit
1c4fabbfa2
5 changed files with 161 additions and 38 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue