<h1>Extending the REST API<aclass="headerlink"href="#extending-the-rest-api"title="Permalink to this headline">¶</a></h1>
<asideclass="sidebar">
<p>Concepts like <em>worn</em> or <em>carried</em> aren’t built into core Evennia, but it’s a common thing to add. This guide uses a <codeclass="docutils literal notranslate"><spanclass="pre">.db.worn</span></code> attribute to identify gear, but will explain how to reference your own mechanic too.</p>
</aside>
<p>By default, the Evennia <aclass="reference internal"href="../Components/Web-API.html"><spanclass="doc std std-doc">REST API</span></a> provides endpoints for the standard entities. One such endpoint is <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/</span></code>, returning information about Characters. In this tutorial, we’ll extend it by adding an <codeclass="docutils literal notranslate"><spanclass="pre">inventory</span></code> action to the <codeclass="docutils literal notranslate"><spanclass="pre">/characters</span></code> endpoint, showing all objects being <em>worn</em> and <em>carried</em> by a character.</p>
<sectionid="creating-your-own-viewset">
<h2>Creating your own viewset<aclass="headerlink"href="#creating-your-own-viewset"title="Permalink to this headline">¶</a></h2>
<asideclass="sidebar">
<pclass="sidebar-title">Views and templates</p>
<p>A <em>view</em> is the python code that tells django what data to put on a page, while a <em>template</em> tells django how to display that data. For more in-depth information, you can read the django <aclass="reference external"href="https://docs.djangoproject.com/en/4.1/topics/http/views/">docs for views</a> and <aclass="reference external"href="https://docs.djangoproject.com/en/4.1/topics/templates/">docs for templates</a>.</p>
</aside>
<p>The first thing you’ll need to do is define your own <codeclass="docutils literal notranslate"><spanclass="pre">views.py</span></code> module.</p>
<p>Create a blank file: <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/api/views.py</span></code></p>
<p>The default REST API endpoints are controlled by classes in <codeclass="docutils literal notranslate"><spanclass="pre">evennia/web/api/views.py</span></code> - you could copy that entire file and use it, but we’re going to focus on changing the minimum.</p>
<p>To start, we’ll reimplement the default <aclass="reference internal"href="../api/evennia.web.api.views.html#evennia.web.api.views.CharacterViewSet"title="evennia.web.api.views.CharacterViewSet"><spanclass="xref myst py py-class">CharacterViewSet</span></a> that handles requests from the <codeclass="docutils literal notranslate"><spanclass="pre">characters/</span></code> endpoint. This is a child of the <codeclass="docutils literal notranslate"><spanclass="pre">objects</span></code> endpoint that can only access characters.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># in mygame/web/api/views.py</span>
<spanclass="c1"># we'll need these from django's rest framework to make our view work</span>
<h2>Setting up the urls<aclass="headerlink"href="#setting-up-the-urls"title="Permalink to this headline">¶</a></h2>
<p>Now that we have a viewset of our own, we can create our own urls module and change the <codeclass="docutils literal notranslate"><spanclass="pre">characters</span></code> endpoint path to point to ours.</p>
<asideclass="sidebar">
<p>Evennia’s <aclass="reference internal"href="../Components/Website.html"><spanclass="doc std std-doc">Game website</span></a> page demonstrates how to use the <codeclass="docutils literal notranslate"><spanclass="pre">urls.py</span></code> module for the main website - if you haven’t gone over that page yet, now is a good time.</p>
</aside>
<p>The API routing is more complicated than the website or webclient routing, so you need to copy the entire module from evennia into your game instead of patching on changes. Copy the file from <codeclass="docutils literal notranslate"><spanclass="pre">evennia/web/api/urls.py</span></code> to your folder, <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/api/urls.py</span></code> and open it in your editor.</p>
<p>Import your new views module, then find and update the <codeclass="docutils literal notranslate"><spanclass="pre">characters</span></code> path to use your own viewset.</p>
<p>We’ve almost got it pointing at our new view now. The last step is to add your own API urls - <codeclass="docutils literal notranslate"><spanclass="pre">web.api.urls</span></code> - to your web root url module. Otherwise it will continue pointing to the default API router and we’ll never see our changes.</p>
<p>Open <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/urls.py</span></code> in your editor and add a new path for “api/”, pointing to <codeclass="docutils literal notranslate"><spanclass="pre">web.api.urls</span></code>. The final file should look something like this:</p>
<p>Restart your evennia game - <codeclass="docutils literal notranslate"><spanclass="pre">evennia</span><spanclass="pre">reboot</span></code> from the command line for a full restart of the game AND portal - and try to get <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/</span></code> again. If it works exactly like before, you’re ready to move on to the next step!</p>
</section>
<sectionid="adding-a-new-detail">
<h2>Adding a new detail<aclass="headerlink"href="#adding-a-new-detail"title="Permalink to this headline">¶</a></h2>
<p>Head back over to your character view class - it’s time to start adding our inventory.</p>
<p>The usual “page” in a REST API is called an <em>endpoint</em> and is what you typically access. e.g. <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/</span></code> is the “characters” endpoint, and <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/:id</span></code> is the endpoint for individual characters.</p>
<asideclass="sidebar">
<pclass="sidebar-title">What is that colon?</p>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">:</span></code> in an API path means that it’s a <em>variable</em> - you don’t directly access that exact path. Instead, you’d take your character ID (e.g. 1) and use that instead: <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/1</span></code></p>
</aside>
<p>However, an endpoint can also have one or more <em>detail</em> views, which function like a sub-point. We’ll be adding <em>inventory</em> as a detail to our character endpoint, which will look like <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/:id/inventory</span></code></p>
<p>With the django REST framework, adding a new detail is as simple as adding a decorated method to the view set class - the <codeclass="docutils literal notranslate"><spanclass="pre">@action</span></code> decorator. Since checking your inventory is just data retrieval, we’ll only want to permit the <codeclass="docutils literal notranslate"><spanclass="pre">GET</span></code> method, and we’re adding this action as an API detail, so our decorator will look like this:</p>
<div><p>There are situations where you might want a detail or endpoint that isn’t just data retrieval: for example, <em>buy</em> or <em>sell</em> on an auction-house listing. In those cases, you would use <em>put</em> or <em>post</em> instead. For further reading on what you can do with <codeclass="docutils literal notranslate"><spanclass="pre">@action</span></code> and ViewSets, visit <aclass="reference external"href="https://www.django-rest-framework.org/api-guide/viewsets/">the django REST framework documentation</a></p>
</div></blockquote>
<p>When adding a function as a detail action, the name of our function will be the same as the detail. Since we want an <codeclass="docutils literal notranslate"><spanclass="pre">inventory</span></code> action we’ll define an <codeclass="docutils literal notranslate"><spanclass="pre">inventory</span></code> function.</p>
<p>Get your character’s ID - it’s the same as your dbref but without the # - and then <codeclass="docutils literal notranslate"><spanclass="pre">evennia</span><spanclass="pre">reboot</span></code> again. Now you should be able to call your new characters action: <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/1/inventory</span></code> (assuming you’re looking at character #1) and it’ll return the string “your inventory”</p>
</section>
<sectionid="creating-a-serializer">
<h2>Creating a Serializer<aclass="headerlink"href="#creating-a-serializer"title="Permalink to this headline">¶</a></h2>
<p>A simple string isn’t very useful, though. What we want is the character’s actual inventory - and for that, we need to set up our own <em>serializer</em>.</p>
<asideclass="sidebar">
<pclass="sidebar-title">Django serializers</p>
<p>You can get a more in-depth look at django serializers in <aclass="reference external"href="https://www.django-rest-framework.org/api-guide/serializers/">the django REST framework serializer docs</a>.</p>
</aside>
<p>Generally speaking, a <em>serializer</em> turns a set of data into a specially formatted string that can be sent in a data stream - usually JSON. Django REST serializers are special classes and functions which take python objects and convert them into API-ready formats. So, just like for the viewset, django and evennia have done a lot of the heavy lifting for us already.</p>
<p>Instead of writing our own serializer, we’ll inherit from evennia’s pre-existing serializers and extend them for our own purpose. To do that, create a new file <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/api/serializers.py</span></code> and start by adding in the imports you’ll need.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># the base serializing library for the framework</span>
<p>The <codeclass="docutils literal notranslate"><spanclass="pre">Meta</span></code> class defines which fields will be used in the final serialized string. The <codeclass="docutils literal notranslate"><spanclass="pre">id</span></code> field is from the base ModelSerializer, but you’ll notice that the two others - <codeclass="docutils literal notranslate"><spanclass="pre">worn</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">carried</span></code> - are defined as properties to <codeclass="docutils literal notranslate"><spanclass="pre">SerializerMethodField</span></code>. That tells the framework to look for matching method names in the form <codeclass="docutils literal notranslate"><spanclass="pre">get_X</span></code> when serializing.</p>
<p>Which is why our next step is to add those methods! We defined the properties <codeclass="docutils literal notranslate"><spanclass="pre">worn</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">carried</span></code>, so the methods we’ll add are <codeclass="docutils literal notranslate"><spanclass="pre">get_worn</span></code> and <codeclass="docutils literal notranslate"><spanclass="pre">get_carried</span></code>. They’ll be static methods - that is, they don’t include <codeclass="docutils literal notranslate"><spanclass="pre">self</span></code> - since they don’t need to reference the serializer class itself.</p>
<divclass="highlight-python notranslate"><divclass="highlight"><pre><span></span><spanclass="c1"># these methods filter the character's contents based on the `worn` attribute</span>
<p>For this guide, we’re assuming that whether an object is being worn or not is stored in the <codeclass="docutils literal notranslate"><spanclass="pre">worn</span></code> db attribute and filtering based on that attribute. This can easily be done differently to match your own game’s mechanics: filtering based on a tag, calling a custom method on your character that returns the right list, etc.</p>
<p>If you want to add in more details - grouping carried items by typing, or dividing up armor vs weapons, you’d just need to add or change the properties, fields, and methods.</p>
<blockquote>
<div><p>Remember: <codeclass="docutils literal notranslate"><spanclass="pre">worn</span><spanclass="pre">=</span><spanclass="pre">serializers.SerializerMethodField()</span></code> is how the API knows to use <codeclass="docutils literal notranslate"><spanclass="pre">get_worn</span></code>, and <codeclass="docutils literal notranslate"><spanclass="pre">Meta.fields</span></code> is the list of fields that will actually make it into the final JSON.</p>
<h2>Using your serializer<aclass="headerlink"href="#using-your-serializer"title="Permalink to this headline">¶</a></h2>
<p>Now let’s go back to our views file, <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/api/views.py</span></code>. Add our new serializer with the rest of the imports:</p>
<p>That’ll use our new serializer to get our character’s inventory. Except… not quite.</p>
<p>Go ahead and try it: <codeclass="docutils literal notranslate"><spanclass="pre">evennia</span><spanclass="pre">reboot</span></code> and then <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/1/inventory</span></code> like before. Instead of returning the string “your inventory”, you should get an error saying you don’t have permission. Don’t worry - that means it’s successfully referencing the new serializer. We just haven’t given it permission to access the objects yet.</p>
</section>
<sectionid="customizing-api-permissions">
<h2>Customizing API permissions<aclass="headerlink"href="#customizing-api-permissions"title="Permalink to this headline">¶</a></h2>
<p>Evennia comes with its own custom API permissions class, connecting the API permissions to the in-game permission hierarchy and locks system. Since we’re trying to access the object’s data now, we need to pass the <codeclass="docutils literal notranslate"><spanclass="pre">has_object_permission</span></code> check as well as the general permission check - and that default permission class hardcodes actions into the object permission checks.</p>
<p>Since we’ve added a new action - <codeclass="docutils literal notranslate"><spanclass="pre">inventory</span></code> - to our characters endpoint, we need to use our own custom permissions on our characters endpoint as well. Create one more module file: <codeclass="docutils literal notranslate"><spanclass="pre">mygame/web/api/permissions.py</span></code></p>
<p>Like with the previous classes, we’ll be inheriting from the original and extending it to take advantage of all the work Evennia already does for us.</p>
<p>That’s the whole permission class! For our final step, we need to use it in our characters view by importing it and setting the <codeclass="docutils literal notranslate"><spanclass="pre">permission_classes</span></code> property.</p>
<p>Once you’ve done that, your final <codeclass="docutils literal notranslate"><spanclass="pre">views.py</span></code> should look like this:</p>
<p>One last <codeclass="docutils literal notranslate"><spanclass="pre">evennia</span><spanclass="pre">reboot</span></code> - now you should be able to get <codeclass="docutils literal notranslate"><spanclass="pre">/api/characters/1/inventory</span></code> and see everything your character has, neatly divided into “worn” and “carried”.</p>
</section>
<sectionid="next-steps">
<h2>Next Steps<aclass="headerlink"href="#next-steps"title="Permalink to this headline">¶</a></h2>
<asideclass="sidebar">
<pclass="sidebar-title">Django REST Framework</p>
<p>For a more in-depth look at the django REST framework, you can go through <aclass="reference external"href="https://www.django-rest-framework.org/tutorial/1-serialization/">their tutorial</a> or straight to <aclass="reference external"href="https://www.django-rest-framework.org/api-guide/requests/">the django REST framework API docs</a>.</p>
</aside>
<p>That’s it! You’ve learned how to customize your own REST endpoint for Evennia, add new endpoint details, and serialize data from your game’s objects for the REST API. With those tools, you can take any in-game data you want and make it available - or even modifiable - with the API.</p>
<p>If you want a challenge, try taking what you learned and implementing a new <codeclass="docutils literal notranslate"><spanclass="pre">desc</span></code> detail that will let you <codeclass="docutils literal notranslate"><spanclass="pre">GET</span></code> the existing character desc <em>or</em><codeclass="docutils literal notranslate"><spanclass="pre">PUT</span></code> a new desc. (Tip: check out how evennia’s REST permissions module works, and the <codeclass="docutils literal notranslate"><spanclass="pre">set_attribute</span></code> methods in the default evennia REST API views.)</p>