Tutorial: Create a real-time web game with Django Channels and React

Jan 20, 2017
Django
dj-react

Django Rest Framework

The Django Rest Framework (DRF) is a very popular Django package that makes it very easy to create an API for your site. It has built-in serializers that make passing Django querysets down to the browser in JSON format a breeze.

In our LobbyBase.jsx, we’re trying to call the /player-games/ api endpoint, but it doesn’t exist yet. Let’s install DRF and set that endpoint up.

Install DRF:

and then add it to your INSTALLED_APPS in settings.py:

That’s all we need to set up DRF, so let’s create that missing endpoint now.

Inside the views folder, create a new python file named api_views.py. We could just as easily have added these alongside the standard views in views.py, but as I mentioned earlier, I like to organize the views a bit. Since we’ve added a new .py file in that folder, we also need to import it in on the __init__.py file in that folder. Update that file like this:

And fill your new api_views.py file with this:

Basically what we’ve added here are three DRF “views”: UserViewSet, PlayerGameViewSet, and CurrentUserView. These are similar to standard Django Views, but here we will get data from models and send it to the serializer to be converted into JSON, which is what we’re expecting on client-side in React. Now, we’ve yet to create these serializers, so let’s do that.

Create a file name serializers.py inside your game app directory with the following code:

These methods simply define what fields, from which models, will be serialized.

Ok, we have our ViewSet ready to get the user’s list of games, and then return the list in a JSON format to the client. So now, update your urls.py file with the endpoint that will call the ViewSet. Change it to look like this (changes are highlighted):

All we’re doing here is adding in a DRF router and adding in the url endpoint for player-games, which calls the new ViewSet we created. We also added a standard URL for /current-user/.

That current-user endpoint will be used to get the…well…current user from the server. Since we need a user to be logged in before we render the lobby, we’ll update the Lobby component to use this endpoint. Replace the code in components/lobby/index.jsx with this :

In the code above, we just removed the render_component() call at the bottom of the page and put it inside a jQuery method, which will return with the current user before rendering the lobby components.

If you runserver now and visit the lobby, you’ll see that the API call was successful, although it returns nothing at this point….let’s change that.

You’ll recall from the PlayerGames.jsx file that we had a button click handler method called onCreateGameClick. That method simply calls the Channels backend, sending a message with an action of “create_game”. Channels backend calls go through the consumers.py file, so we’re going to update it and have it handle that particular message, creating a game in the database.

Modify the receive method of the LobbyConsumer in your consumers.py file to look like this:

Here, the receive() method takes the call to Channels from the client. We set channel_session_user to True so we can use the user just as we can in the request in standard Django. Instead of request.user, we use message.user. The method then takes the action we passed in and handles it appropriately, creating a new game from our create_new() model method.

Now, we have a new game that has been created, but if we click the “Start New Game” button in the Lobby, nothing appears to happen. This is because the client doesn’t know about the change. You can refresh the page, which would re-render the components, calling the getPlayerGames() method we messed with earlier. But we want this to update automatically after the game is created. We could do this a few ways, but we’ll handle this through Channels.

Updating the Client from Channels

Communicating with Channels is as simple as sending messages. We just need to send a message to the client to let it know about any updates. But how do we want to do that in this case and where does that code go?

Django Channels documentation suggests several ways to handle this — for example, we could send the message directly from the view/consumer, or have a cron run that processes and sends messages. Another way would be to use Django’s post_save signal on the Game model, which is how we’re going to do it.

post_save Signal

If you’re not familiar with Django’s model signals, you can think of them as broadcast messages that are sent out when a model event occurs. In our case, we’re interested in knowing when a new Game object is created. So we’ll create a signal listener that will handle this.

Create a file named signals.py in your game app directory and fill it with:

 

So what we’re doing there is simply listening to the post_save event for the Game model. If “created” is in the kwargs sent to the handler, a new Game was just created, so the Lobby needs to know. We then just get a list of available games, serialize it, and then send it down to the “lobby” Channels group.

If we wanted, we really could have everything handled within signals going forward. And if this were a real project, it would probably be good to stay consistent with how we do this. But later, we’ll try out different ways to get updates down to the client.

OK, to make signals work from a separate signals.py file (instead of say, directly in the models.py file), we need to add a couple of lines to our apps.py file. So, change your game/apps.py file to this:

And then add the following to your currently empty game/__init__.py file:

default_app_config = 'game.apps.GameConfig'

With all of that in place, you should now be able to click “Start New Game” in the Lobby and the games will immediately show up in your list…waiting for other players to join. Other players can’t join at this point, though, because they have no clue the games are available.

Let’s create another React component now that will show everyone in the Lobby what games are waiting for a player…

1 2 3 4 5 6 7 8 9

Comments
  • hello,
    when I run your code, but the web don’t show the game, what’s happend?

    Gia Cat 09/27/2018 12:34 pm Reply
    • Are you going through the tutorial? Or are you running the example project from github?

      codyparker 09/28/2018 8:46 am Reply
  • Do you have a similar project but with channels 2.0? Do you plan to update this one?

    marsel 08/30/2018 9:53 am Reply
    • Yes, I’m actually working on one now. Hopefully I’ll have it ready in October, but my free time is tight right now.

      codyparker 09/28/2018 8:47 am Reply
  • I am getting an error, “ImportError: cannot import name ‘Group'”, while trying to import Group from channels in models.py. I am using channels 2.0.2. Please help.

    don 03/28/2018 8:31 am Reply
    • Yeah, this tutorial is based on a very early version of channels – 0.17, I believe. Version 2.0.2 has quite of few changes from that old version, including requiring Python3. Here is an issue on the official github that deals with the error you’re seeing: https://github.com/django/channels/issues/989

      I will try to get the tutorial updated to Django 2.0, Python 3, and the latest channels version soon.

      codyparker 04/23/2018 9:15 pm Reply
  • Hey,

    I’m trying to follow your tutorial, but I keep running into the following error:

    OSError at /lobby/
    Error reading [PATH]\webpack-stats.json. Are you sure webpack has generated the file and the path is correct?

    Any idea how to fix it?

    Graeme 03/14/2018 10:32 am Reply
    • Sorry, I didn’t reply sooner. If the file is being generated, check your STATS_FILE setting under WEBPACK_LOADER in settings.py. It’s possible that it isn’t pointing to the correct location for the file.

      If the file isn’t being generated, webpack may not be running properly. Are you seeing errors when you run wepback?

      codyparker 04/23/2018 9:24 pm Reply
  • That is very good, I was making similar stuff as a video on my language and this sharing is very useful.
    Power of sharing, thanks again.

    ozan 10/29/2017 3:48 pm Reply
  • When will the index.jsx be called ?

    Orang 10/08/2017 6:30 pm Reply
    • The index.jsx files are bundled with webpack (set on lines 8 and 9 in webpack.config.js). Those are the bundles that are loaded with the Django Webpack Loader and rendered on the related html pages (lobby.html and game.html).

      codyparker 10/09/2017 10:07 am Reply
  • I got a few questions to make the game a little bit more complex

    Is it possible to allow to the user give a name for the room?
    Is it possible to allow more than 2 players? (obviously adding the correct img stuff)
    If the answer from above is yes, can the user choose a “limit” for ppl to connect before starting the game?

    Cheers!

    Leonardo 10/06/2017 7:19 pm Reply
    • Yes, each of those suggestions would work just fine:

      1.  Name of the room: Adding a “name” CharField to the Game model (models.py) would hold the value and then you would just need to add a bit to the create_new model method to set it. You would also need to allow the game creator to provide that name when creating it, instead of just clicking a button. But that name value would then just be passed along in the JSON to Channels in the sendSocketMessage call in the onCreateGameClick() method of PlayerGames.jsx.
      2. This could be added with a bit more work, but essentially you would want to change the Game model to have a ManyToMany relationship to Users. Aside from that, several other parts of the code and GUI would need to be updated because it’s pretty hardcoded to two players at the moment. But if there were multiple users in a game, Channels would work the same – everyone would get game updates without a problem.
      3. And yes, once the game was converted to use multiple players in a game, you could just have the game creator specify the max limit and then store that as a max_players IntegerField in the Game model….and then allow users to join up to that limit.

       

      codyparker 10/09/2017 10:20 am Reply
  • Thanks for this django/react tutorial. It is awesome helping me get my head around React and putting all the pieces together!!

    johnedstone 08/12/2017 8:05 am Reply
    • Great to hear! I’m glad you found it useful. Thanks for letting me know.

      codyparker 08/12/2017 4:21 pm Reply
  • Thank you so much for writing this tutorial!
    From your description, it seems like moves and player messages should update automatically, however when I run it, they only update after a manual refresh of the page. Am I misunderstanding how the website works?
    Thanks

    Missa 07/26/2017 12:58 pm Reply
    • The log should update when new chat messages are created or moves are made. I would check to make sure the game.send_game_update() method is being called at the appropriate times. Also, check the browser console for any errors on the client-side. That may give you more of a clue to the issue as well.

      codyparker 07/26/2017 1:48 pm Reply
  • Thanks for writing this tutorial! However, some things that may be confusing to a beginner: startapp game is going to create a game/views.py by default, and anyone that forgets to move views.py is going to have a package conflict. Also, from views import * inside __init__.py should be from .views import * just to avoid any namespace mismatches. I recommend you keep the default views.py.

    Daniel 06/11/2017 2:41 pm Reply
    • Thanks for your comments, Daniel. Yes, I thought that splitting the views could be confusing, but it’s something I like to do to organize views of different types. So here, I didn’t want to combine the DRF API views with the “standard” Django views. Also, the tutorial wasn’t exactly intended for beginners, but maybe I can clarify the views split a little more in the post. Also, thanks for catching the import fix. I had already updated the imports in the git project, but missed it in the post.

      codyparker 06/13/2017 10:11 am Reply
  • Hello! Great tutorial! I have a question, I want to use foundation-sites in my project. I am following you tutorial and instead of using bootstrap precompiled css I would like to install foundation with bower maybe? I know how to use the “foundation new” command to create a new project but I would like not to create a new project but integrate foundation sites with mine!

    David Anchorena 06/05/2017 8:45 am Reply
    • Hey David, sorry I haven’t worked with Foundation yet. But it looks like you can just use the CSS itself and avoid the CLI site generation: http://foundation.zurb.com/sites/download/

      You could just include this as you would any other CSS, and my guess is that if you install the full Foundation package with NPM, you could just reference the CSS there as well.

      codyparker 06/06/2017 2:38 pm Reply
  • not sure if it’s a django versioning thing or what, but on page 1 of this tutorial you are no longer allowed to specify views with strings and they must be callable, suggested edit follows:

    original:

    from django.conf.urls import url
    from django.contrib import admin
    from game.views import *

    urlpatterns = [
    url(r’^admin/’, admin.site.urls),
    url(r’^register/’, CreateUserView.as_view()),
    url(r’^login/$’, ‘django.contrib.auth.views.login’, {‘template_name’: ‘login.html’}),
    url(r’^logout/$’, ‘django.contrib.auth.views.logout’, {‘next_page’: ‘/’}),

    url(r’^$’, HomeView.as_view())
    ]

    edit:

    from django.conf.urls import url
    from django.contrib import admin
    from django.contrib.auth.views import login, logout
    from game.views import *

    urlpatterns = [
    url(r’^admin/’, admin.site.urls),
    url(r’^register/’, CreateUserView.as_view()),
    url(r’^login/$’, login, {‘template_name’: ‘login.html’}),
    url(r’^logout/$’, logout, {‘next_page’: ‘/’}),

    url(r’^$’, HomeView.as_view())
    ]

    will 05/23/2017 9:21 am Reply
    • Yep – you’re right, thanks for letting me know! I’ve updated the urls.py code.

      codyparker 05/23/2017 12:30 pm Reply
  • Thank you so much, one of the most complete tutorials I have seen. Not all persons are willing to teach this things together, and the complexity of the scenario gives us good bases. This types of tutorials (even paid) are hard to find. Again, thank you.

    Pepe 05/07/2017 9:14 pm Reply
    • Thank you for the nice comments! I’m glad you found the tutorial helpful.

      codyparker 05/08/2017 8:35 am Reply
  • Can you please show one example on how I can make api post call from react to django drf?

    Is there any reason why all “post” calls are done via sockets not api in this tutorial?

    Said 02/07/2017 3:47 am Reply
    • You can see a few examples of calls from React to the DRF backend in my post. For example, take a look at the getGame() method on the GameBoard.jsx component. That method calls the DRF SingleGameViewSet endpoint to get game details.

      And as I mentioned in the post, I tried to mix up different ways of getting data from the Django backend to the React frontend. I wanted to show different ways of achieving the same thing: sending data through the standard Django response via context, DRF calls to the backend, and Django Channels websocket calls. In reality, this isn’t how I would structure a production app, but I was hoping it would be informative. Hopefully not confusing at the same time.

      codyparker 02/07/2017 2:40 pm Reply
  • Wow! Thanks again for sharing this tutorial. I am amazed by your generosity. The tutorial is intense.

    Let me give some suggesting for how you can improve it. I found that very often the flow of tutorial is going from big concepts (code snippets) to a smaller ones. For example several times you are first putting together some views, react components or api_views and then go to show some serializers, consumers, routers, urls and so on. This can be sometimes confusing, since a student can receive error messages that those small parts are not yet existed. I think going from small concepts to bigger would be more easily to understand. Also, please check your github code. I think it does not work if you just download it and want to use. Several imports are configured improperly (signal in apps.py, for instance)

    Thank you again!

    Said 02/07/2017 3:44 am Reply
    • Thanks for suggestions. Yes this was my first large tutorial so it definitely could be optimized and improved. I did the GitHub project well before the post, but it worked for me when I last tried it. It could be a python2/python3 import issue. I’ll update that tonight and get it working.

      codyparker 02/07/2017 1:16 pm Reply
  • not sure if the instruction on page 8 is correct

    class ClaimSquareView(APIView):

    def get_object(self, pk):
    try:
    return Game.objects.get(pk=pk)
    except Game.DoesNotExist:
    raise Http404

    def put(self, request, pk):
    game = self.get_object(pk)
    # update the owner
    print(game)
    return Response(serializer.errors)

    – no import for Http404
    – serializer is not defined

    Said 02/07/2017 1:36 am Reply
    • You’re right! That view isn’t even needed… I think I started going that direction to claim a square, but moved it to a Channels call using the consumer instead. I’ve removed that reference and the url reference.

      Thank you very much for your suggestions and bug reports! I’ve added you to the “Thank you” section at the bottom of the post.

      codyparker 02/07/2017 3:50 pm Reply
  • on page 7 views.py also should import

    from django.contrib import messages

    Said 02/07/2017 12:31 am Reply
    • Added it, thank you.

      codyparker 02/07/2017 3:51 pm Reply
  • Also, in my setup in view/__init__.py instead of

    from views import *
    from api_views import *

    I need to enter

    from .views import *
    from .api_views import *

    Said 02/06/2017 10:34 pm Reply
    • Yes this is probably because you’re on Python 3 and implicit relative imports like that won’t work. I’m on 2.7 and they work with it. Thanks for pointing that out. I’ll update the post to note this.

      codyparker 02/07/2017 1:12 pm Reply
  • on the page 2, it is very important to highlights this setting in the settings.py

    STATICFILES_DIRS = [
    os.path.join(BASE_DIR, “static”),
    ]

    it is not something that is added by default if start project with django-admin tools

    Said 02/06/2017 9:37 pm Reply
    • Thanks, yes when I first talk about the settings file, I recommended overwriting all of the default code with what I show in the post. I’ll make sure that it’s more clear.

      codyparker 02/07/2017 5:17 pm Reply
  • Thanks, confirm MIDDLEWARE_CLASSES fixed the issue with ‘AsgiRequest’ object has no attribute ‘session’

    Said 02/06/2017 9:23 pm Reply
  • after following all instruction on the page 4, cannot login, getting this error

    AttributeError at /login/
    ‘AsgiRequest’ object has no attribute ‘session’
    Request Method: POST
    Request URL: http://127.0.0.1:8080/login/
    Django Version: 1.9.12
    Exception Type: AttributeError
    Exception Value:
    ‘AsgiRequest’ object has no attribute ‘session’
    Exception Location: /home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages/django/contrib/auth/__init__.py in login, line 101
    Python Executable: /home/gideon/virtualenvs/vEnv_my_obstruct_game/bin/python
    Python Version: 3.4.3
    Python Path:
    [‘/home/gideon/PycharmProjects/my_obstruct_game’,
    ‘/home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages/setuptools-18.1-py3.4.egg’,
    ‘/home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages/pip-7.1.0-py3.4.egg’,
    ‘/home/gideon/PycharmProjects/my_obstruct_game’,
    ‘/usr/lib/python3.4’,
    ‘/usr/lib/python3.4/plat-x86_64-linux-gnu’,
    ‘/usr/lib/python3.4/lib-dynload’,
    ‘/home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages’]
    Server time: Mon, 6 Feb 2017 15:53:42 +0000
    Traceback Switch to copy-and-paste view

    /home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages/django/core/handlers/base.py in get_response
    response = self.process_exception_by_middleware(e, request) …
    ▶ Local vars
    /home/gideon/virtualenvs/vEnv_my_obstruct_game/lib/python3.4/site-packages/channels/handler.py in process_exception_by_middleware
    return super(AsgiHandler, self).process_exception_by_middleware(exception, request)

    Said 02/06/2017 9:55 am Reply
    • One thing that could cause this with Django 1.9+ is if you have MIDDLEWARE instead of MIDDLEWARE_CLASSES in your settings.py file. Can you check that?

      codyparker 02/06/2017 10:28 am Reply
  • It seems there is an error in this instruction

    npm install –save-dev react react-dom webpack webpack-bundle-tracker babel-core babel babel-loadernpm babel-preset-es2015 react-websocket babel-preset-es2015 babel-preset-react jquery

    “babel-loadernpm” should read “babel-loader”

    Said Akhmedbayev 02/05/2017 11:28 pm Reply
    • Yep, you’re right – a little copy-paste issue on my part. It’s fixed now. Thanks for letting me know!

      codyparker 02/06/2017 9:06 am Reply
  • I have not yet finished your tutorial, but for what I see I can tell you huge THANK YOU!

    Said 02/05/2017 10:33 am Reply
    • Thank you, I hope it you find it useful. Please let me know if you have any issues!

      codyparker 02/06/2017 9:07 am Reply

Leave a Comment

Your email address will not be published. Required fields are marked *