Web Character View Tutorial

Before doing this tutorial you will probably want to read the intro in Changing The Web Page tutorial.

In this tutorial we will create a web page that displays the stats of a game character. For this, and all other pages we want to make specific to our game, we’ll need to create our own Django “app”. We’ll call our app character, since it will be dealing with character information. From your game dir, run

evennia startapp character

This will create a new directory named character inside mygame. To keep things tidy, let’s move it into the web/ subdirectory.

mv character web  (linux/mac)
move character web  (windows)

We put it in web/ to keep things tidy, but you could place it wherever you like. It contains all basic files that a Django app needs.

Note that we will not edit all files in this new directory, many of the generated files are outside the scope of this tutorial.

In order for Django to find our new web app, we’ll need to add it to the INSTALLED_APPS setting. Evennia’s default installed apps are already set, so in server/conf/settings.py, we’ll just extend them:

INSTALLED_APPS += ('web.character',)

Note: That end comma is important. It makes sure that Python interprets the addition as a tuple instead of a string.

The first thing we need to do is to create a view and an URL pattern to point to it. A view is a function that generates the web page that a visitor wants to see, while the URL pattern lets Django know what URL should trigger the view. The pattern may also provide some information of its own as we shall see.

Here is our character/urls.py file (Note: you may have to create this file if a blank one wasn’t generated for you):

# URL patterns for the character app

from django.urls import path
from web.character.views import sheet

urlpatterns = [
    path("sheet/<int:object_id>", sheet, name="sheet")
]

This file contains all of the URL patterns for the application. The url function in the urlpatterns list are given three arguments. The first argument is a pattern-string used to identify which URLs are valid. Patterns are specified as regular expressions. Regular expressions are used to match strings and are written in a special, very compact, syntax. A detailed description of regular expressions is beyond this tutorial but you can learn more about them here. For now, just accept that this regular expression requires that the visitor’s URL looks something like this:

sheet/123/

That is, sheet/ followed by a number, rather than some other possible URL pattern. We will interpret this number as object ID. Thanks to how the regular expression is formulated, the pattern recognizer stores the number in a variable called object_id. This will be passed to the view (see below). We add the imported view function (sheet) in the second argument. We also add the name keyword to identify the URL pattern itself. You should always name your URL patterns, this makes them easy to refer to in html templates using the {% url %} tag (but we won’t get more into that in this tutorial).

Security Note: Normally, users do not have the ability to see object IDs within the game (it’s restricted to superusers only). Exposing the game’s object IDs to the public like this enables griefers to perform what is known as an account enumeration attack in the efforts of hijacking your superuser account. Consider this: in every Evennia installation, there are two objects that we can always expect to exist and have the same object IDs– Limbo (#2) and the superuser you create in the beginning (#1). Thus, the griefer can get 50% of the information they need to hijack the admin account (the admin’s username) just by navigating to sheet/1!

Next we create views.py, the view file that urls.py refers to.

# Views for our character app

from django.http import Http404
from django.shortcuts import render
from django.conf import settings

from evennia.utils.search import object_search
from evennia.utils.utils import inherits_from

def sheet(request, object_id):
    object_id = '#' + object_id
    try:
        character = object_search(object_id)[0]
    except IndexError:
        raise Http404("I couldn't find a character with that ID.")
    if not inherits_from(character, settings.BASE_CHARACTER_TYPECLASS):
        raise Http404("I couldn't find a character with that ID. "
                      "Found something else instead.")
    return render(request, 'character/sheet.html', {'character': character})

As explained earlier, the URL pattern parser in urls.py parses the URL and passes object_id to our view function sheet. We do a database search for the object using this number. We also make sure such an object exists and that it is actually a Character. The view function is also handed a request object. This gives us information about the request, such as if a logged-in user viewed it - we won’t use that information here but it is good to keep in mind.

On the last line, we call the render function. Apart from the request object, the render function takes a path to an html template and a dictionary with extra data you want to pass into said template. As extra data we pass the Character object we just found. In the template it will be available as the variable “character”.

The html template is created as templates/character/sheet.html under your character app folder. You may have to manually create both template and its subfolder character. Here’s the template to create:

{% extends "base.html" %}
{% block content %}

    <h1>{{ character.name }}</h1>

    <p>{{ character.db.desc }}</p>

    <h2>Stats</h2>
    <table>
      <thead>
        <tr>
          <th>Stat</th>
          <th>Value</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Strength</td>
          <td>{{ character.db.str }}</td>
        </tr>
        <tr>
          <td>Intelligence</td>
          <td>{{ character.db.int }}</td>
        </tr>
        <tr>
          <td>Speed</td>
          <td>{{ character.db.spd }}</td>
        </tr>
      </tbody>
    </table>

    <h2>Skills</h2>
    <ul>
      {% for skill in character.db.skills %}
        <li>{{ skill }}</li>
      {% empty %}
        <li>This character has no skills yet.</li>
      {% endfor %}
    </ul>

    {% if character.db.approved %}
      <p class="success">This character has been approved!</p>
    {% else %}
      <p class="warning">This character has not yet been approved!</p>
    {% endif %}
{% endblock %}

In Django templates, {% ... %} denotes special in-template “functions” that Django understands. The {{ ... }} blocks work as “slots”. They are replaced with whatever value the code inside the block returns.

The first line, {% extends "base.html" %}, tells Django that this template extends the base template that Evennia is using. The base template is provided by the theme. Evennia comes with the open-source third-party theme prosimii. You can find it and its base.html in evennia/web/templates/prosimii. Like other templates, these can be overwritten.

The next line is {% block content %}. The base.html file has blocks, which are placeholders that templates can extend. The main block, and the one we use, is named content.

We can access the character variable anywhere in the template because we passed it in the render call at the end of view.py. That means we also have access to the Character’s db attributes, much like you would in normal Python code. You don’t have the ability to call functions with arguments in the template– in fact, if you need to do any complicated logic, you should do it in view.py and pass the results as more variables to the template. But you still have a great deal of flexibility in how you display the data.

We can do a little bit of logic here as well. We use the {% for %} ... {% endfor %} and {% if %} ... {% else %} ... {% endif %} structures to change how the template renders depending on how many skills the user has, or if the user is approved (assuming your game has an approval system).

The last file we need to edit is the master URLs file. This is needed in order to smoothly integrate the URLs from your new character app with the URLs from Evennia’s existing pages. Find the file web/website/urls.py and update its patterns list as follows:

# web/website/urls.py

urlpatterns = [
    # ...
    path("character/", include('web.character.urls'))
   ]

Now reload the server with evennia reload and visit the page in your browser. If you haven’t changed your defaults, you should be able to find the sheet for character #1 at http://localhost:4001/character/sheet/1/

Try updating the stats in-game and refresh the page in your browser. The results should show immediately.

As an optional final step, you can also change your character typeclass to have a method called ‘get_absolute_url’.

# typeclasses/characters.py

    # inside Character
    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('character:sheet', kwargs={'object_id':self.id})

Doing so will give you a ‘view on site’ button in the top right of the Django Admin Objects changepage that links to your new character sheet, and allow you to get the link to a character’s page by using {{ object.get_absolute_url }} in any template where you have a given object.

Now that you’ve made a basic page and app with Django, you may want to read the full Django tutorial to get a better idea of what it can do. You can find Django’s tutorial here.