Add api customization templates

This commit is contained in:
Griatch 2021-05-23 17:00:02 +02:00
parent cc9f42a398
commit 4250ca1a29
24 changed files with 334 additions and 170 deletions

View file

@ -1,5 +1,8 @@
# Evennia API
This folder contains the code implementing the REST-API of Evennia, based on
Django Rest Framework.
## Synopsis
An API, or [Application Programming Interface][wiki-api], is a way of establishing rules
@ -18,7 +21,7 @@ can convert into python objects for you, a process called deserialization.
When returning a response, it can also convert python objects into JSON
strings to send back to a client, which is called serialization. Because it's
such a common task to want to handle [CRUD][crud] operations for the django models that you use to represent database
objects (such as your Character typeclass, Room typeclass, etc), DRF makes
objects (such as your Character typeclass, Room typeclass, etc), DRF makes
this process very easy by letting you define [Serializers][serializers]
that largely automate the process of serializing your in-game objects into
JSON representations for sending them to a client, or for turning a JSON string
@ -54,7 +57,7 @@ user has permission to perform retrieve/update/delete actions upon them.
To start with, you can view a synopsis of endpoints by making a GET request
to the `yourgame/api/` endpoint by using the excellent [requests library][requests]:
```pythonstub
```python
>>> import requests
>>> r = requests.get("https://www.mygame.com/api", auth=("user", "pw"))
>>> r.json()
@ -131,16 +134,16 @@ object:
>>> response = requests.put("https://www.mygame.com/api/objects/214",
data=data, auth=("Alsoauser", "Badpassword"))
>>> response.json()
{"db_key": "An even SHINIER sword", "id": 214, "db_location": 50, ...}
{"db_key": "An even SHINIER sword", "id": 214, "db_location": 50, ...}
```
```
By making a PUT request to the endpoint that includes the object ID, it becomes
a request to update the object with the specified data you pass along.
In most cases, you won't be making API requests to the backend with python,
but with Javascript from your frontend application.
There are many Javascript libraries which are meant to make this process
easier for requests from the frontend, such as [AXIOS][axios], or using
There are many Javascript libraries which are meant to make this process
easier for requests from the frontend, such as [AXIOS][axios], or using
the native [Fetch][fetch].
[wiki-api]: https://en.wikipedia.org/wiki/Application_programming_interface

View file

@ -321,6 +321,7 @@ class HelpSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
"id", "db_key", "db_help_category", "db_entrytext", "db_date_created",
"tags", "aliases"
]
read_only_fields = ["id"]
class HelpListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializer):
"""
@ -332,3 +333,4 @@ class HelpListSerializer(TypeclassListSerializerMixin, serializers.ModelSerializ
fields = [
"id", "db_key", "db_help_category", "db_date_created",
]
read_only_fields = ["id"]

View file

@ -39,7 +39,7 @@ 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", "retrieve_data"]
"View", ["view_name", "obj", "list", "serializer", "list_serializer", "create_data", "retrieve_data"]
)
views = [
View(
@ -47,6 +47,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.obj1,
[self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2, self.char2],
serializers.ObjectDBSerializer,
serializers.ObjectListSerializer,
{"db_key": "object-create-test-name"},
serializers.ObjectDBSerializer(self.obj1).data,
),
@ -55,6 +56,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.char1,
[self.char1, self.char2],
serializers.ObjectDBSerializer,
serializers.ObjectListSerializer,
{"db_key": "character-create-test-name"},
serializers.ObjectDBSerializer(self.char1).data,
),
@ -63,6 +65,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.exit,
[self.exit],
serializers.ObjectDBSerializer,
serializers.ObjectListSerializer,
{"db_key": "exit-create-test-name"},
serializers.ObjectDBSerializer(self.exit).data,
),
@ -71,6 +74,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.room1,
[self.room1, self.room2],
serializers.ObjectDBSerializer,
serializers.ObjectListSerializer,
{"db_key": "room-create-test-name"},
serializers.ObjectDBSerializer(self.room1).data,
),
@ -79,6 +83,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.script,
[self.script],
serializers.ScriptDBSerializer,
serializers.ScriptListSerializer,
{"db_key": "script-create-test-name"},
serializers.ScriptDBSerializer(self.script).data,
),
@ -87,6 +92,7 @@ class TestEvenniaRESTApi(EvenniaTest):
self.account2,
[self.account, self.account2],
serializers.AccountSerializer,
serializers.AccountListSerializer,
{"username": "account-create-test-name"},
serializers.AccountSerializer(self.account2).data,
),
@ -135,7 +141,7 @@ class TestEvenniaRESTApi(EvenniaTest):
response = self.client.get(view_url)
self.assertEqual(response.status_code, 200)
self.assertCountEqual(
response.data["results"], [view.serializer(obj).data for obj in view.list]
response.data["results"], [view.list_serializer(obj).data for obj in view.list]
)
def test_create(self):

View file

@ -34,7 +34,7 @@ router.register(r"characters", views.CharacterViewSet, basename="character")
router.register(r"exits", views.ExitViewSet, basename="exit")
router.register(r"rooms", views.RoomViewSet, basename="room")
router.register(r"scripts", views.ScriptDBViewSet, basename="script")
router.register(r"helpentries", views.HelpViewSet, basename="script")
router.register(r"helpentries", views.HelpViewSet, basename="helpentry")
urlpatterns = router.urls

View file

@ -36,8 +36,10 @@ folder and edit it to add/remove links to the menu.
{% endif %}
{% if user.is_staff %}
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
<li><a class="nav-link" href="/api">API</a></li>
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
{% if rest_api_enabled %}
<li><a class="nav-link" href="/api">API</a></li>
{% endif %}
{% endif %}
{% endblock %}
</ul>

View file

@ -30,8 +30,6 @@ urlpatterns = [
path("webclient/", include("evennia.web.webclient.urls")),
# admin
path("admin/", include("evennia.web.admin.urls")),
# api
path("api/", include("evennia.web.api.urls")),
# favicon
path("favicon.ico", RedirectView.as_view(url="/media/images/favicon.ico", permanent=False)),
]

View file

@ -1,9 +1,9 @@
"""
This file defines global variables that will always be available in a view
context without having to repeatedly include it.
context without having to repeatedly include it.
For this to work, this file is included in the settings file, in the
TEMPLATE_CONTEXT_PROCESSORS tuple.
TEMPLATES["OPTIONS"]["context_processors"] list.
"""
@ -20,6 +20,7 @@ GAME_ENTITIES = ["Objects", "Scripts", "Comms", "Help"]
GAME_SETUP = ["Permissions", "Config"]
CONNECTIONS = ["Irc"]
WEBSITE = ["Flatpages", "News", "Sites"]
REST_API_ENABLED = False
# Determine the site name and server version
def set_game_name_and_slogan():
@ -31,7 +32,7 @@ def set_game_name_and_slogan():
This function is used for unit testing the values of the globals.
"""
global GAME_NAME, GAME_SLOGAN, SERVER_VERSION
global GAME_NAME, GAME_SLOGAN, SERVER_VERSION, REST_API_ENABLED
try:
GAME_NAME = settings.SERVERNAME.strip()
except AttributeError:
@ -42,6 +43,7 @@ def set_game_name_and_slogan():
except AttributeError:
GAME_SLOGAN = SERVER_VERSION
REST_API_ENABLED = settings.REST_API_ENABLED
def set_webclient_settings():
"""
@ -98,4 +100,5 @@ def general_context(request):
"websocket_enabled": WEBSOCKET_CLIENT_ENABLED,
"websocket_port": WEBSOCKET_PORT,
"websocket_url": WEBSOCKET_URL,
"rest_api_enabled": REST_API_ENABLED,
}

View file

@ -9,13 +9,12 @@ class TestGeneralContext(TestCase):
@patch("evennia.web.utils.general_context.GAME_NAME", "test_name")
@patch("evennia.web.utils.general_context.GAME_SLOGAN", "test_game_slogan")
@patch(
"evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED",
"websocket_client_enabled_testvalue",
)
@patch("evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED",
"websocket_client_enabled_testvalue")
@patch("evennia.web.utils.general_context.WEBCLIENT_ENABLED", "webclient_enabled_testvalue")
@patch("evennia.web.utils.general_context.WEBSOCKET_PORT", "websocket_client_port_testvalue")
@patch("evennia.web.utils.general_context.WEBSOCKET_URL", "websocket_client_url_testvalue")
@patch("evennia.web.utils.general_context.REST_API_ENABLED", True)
def test_general_context(self):
request = RequestFactory().get("/")
request.user = AnonymousUser()
@ -39,6 +38,7 @@ class TestGeneralContext(TestCase):
"websocket_enabled": "websocket_client_enabled_testvalue",
"websocket_port": "websocket_client_port_testvalue",
"websocket_url": "websocket_client_url_testvalue",
"rest_api_enabled": True,
},
)
@ -48,6 +48,7 @@ class TestGeneralContext(TestCase):
def test_set_game_name_and_slogan(self, mock_get_version, mock_settings):
mock_get_version.return_value = "version 1"
# test default/fallback values
mock_settings.REST_API_ENABLED = False
general_context.set_game_name_and_slogan()
self.assertEqual(general_context.GAME_NAME, "Evennia")
self.assertEqual(general_context.GAME_SLOGAN, "version 1")