The Game!
To start, we need a new set of components that will work as our Game. Create a new folder under templates/components named game. Inside that folder, create index.jsx, GameBoard.jsx and game.html files.
Add the following to each of those files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React from 'react'; import GameBoard from './GameBoard.jsx' import ReactDOM from 'react-dom' import $ from 'jquery' let current_user = null const game = $("#game_component").data("game") const game_sock = 'ws://' + window.location.host + "/game/" + game + "/" $.get('http://localhost:8080/current-user/?format=json', function(result){ // gets the current user information from Django current_user = result render_component() }) function render_component(){ ReactDOM.render(<GameBoard current_user={current_user} game_id={game} socket={game_sock}/>, document.getElementById("game_component")) } |
Just as with the Lobby components, we need an index.jsx file that Webpack will use as an entry point. Here are the key points in that file:
- Imports “GameBoard.jsx” component (yet to be created)
- Creates a “game socket” (game_sock) to handle Channels communication unique to the game instance
- Gets the current user and Renders the GameBoard base component once a user is found
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
{% extends "base.html" %} {% load render_bundle from webpack_loader %} {% block page_javascript %} {% render_bundle 'game' %} {% endblock %} {% block main_content %} <div class="row"> <div class="col-lg-12"> <h3>Play Obstruction! Now!</h3> <hr> <div class="row"> <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-header"> <h3>Playing {{ game }}</h3> </div> <div class="panel-body"> <div class="row "> <div class="col-sm-2 col-sm-offset-3"> <div> <div><h4>{{game.creator}}</h4></div> <span><img src="/static/img/blue-player.png"></span> </div> </div> <div class="col-sm-2 text-center"> <h2 style="height:100px;line-height:100px;">VS</h2> </div> <div class="col-sm-2"> <div> <div><h4>{{game.opponent}}</h4></div> <span><img src="/static/img/red-player.png"></span> </div> </div> </div> </div> </div> </div> </div> </div> <div class="row"> <div class="col-md-12"> <div class="panel panel-default"> <div class="panel-body" data-game={{game.id}} id="game_component"> <!-- game is rendered here --> </div> </div> </div> </div> </div> </div> {% endblock %} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
import React, {Component, PropTypes} from 'react' import $ from 'jquery' import Websocket from 'react-websocket' import GameSquare from './GameSquare' class GameBoard extends Component { // lifecycle methods constructor(props) { super(props) this.state = { game: null, squares: null, } // bind button click this.sendSocketMessage = this.sendSocketMessage.bind(this) this.isPlayerTurn = this.isPlayerTurn.bind(this) } componentDidMount() { this.getGame() } componentWillUnmount() { this.serverRequest.abort(); } // custom methods getGame(){ const game_url = 'http://localhost:8080/game-from-id/' + this.props.game_id this.serverRequest = $.get(game_url, function (result) { this.setState({ game: result.game, squares: result.squares }) }.bind(this)) } getSquares(){ const squares_url = 'http://localhost:8080/game-squares/' + this.props.game_id this.serverRequest = $.get(squares_url, function (result) { this.setState({ squares: result }) }.bind(this)) } handleData(data) { //receives messages from the connected websocket let result = JSON.parse(data) this.setState({game: result.game, squares: result.squares }) } sendSocketMessage(message){ // sends message to channels back-end const socket = this.refs.socket socket.state.ws.send(JSON.stringify(message)) } isPlayerTurn(){ if (this.props.current_user.id == this.state.game.current_turn.id){ return true }else{ return false } } // ---- RENDER FUNCTIONS ---- // // --------------------------- // renderRow(row_num, cols) { let row = cols.map(function(square){ // for each col, render a square for this row return <GameSquare game_creator={this.state.game.creator.id} key={square.id} owner={square.owner} square_id={square.id} possession_type={square.status} loc_x={parseInt(square.col)} loc_y={parseInt(square.row)} sendSocketMessage={this.sendSocketMessage} isPlayerTurn={this.isPlayerTurn} /> }.bind(this)) return ( <tr key={row_num}>{row}</tr> ) } renderBoard() { // renders the obstruction grid/board // build by row and then by col, based on the height and width values let board = [] let cur_row = -1 // for each rown if (this.state.game != null && this.state.squares != null){ // build the squares // if this is a new row, get the cols board = this.state.squares.map(function(square){ if (square.row != cur_row){ // new row cur_row = square.row // get just current row cols let row_cols = this.state.squares.filter(function(c){ if (c.row == cur_row){ return c } }) // with array of cols for this row, render it out //board.push(this.renderRow(cur_row, row_cols)) return this.renderRow(cur_row, row_cols ) } }, this) }else{ board = <tr><td>'LOADING...'</td></tr> } return board } currentTurn(){ if (this.state.game){ if (this.state.game.completed != null){ // game is over return <h3>The Winner: <span className="text-primary">{(this.state.game.current_turn.username)}</span></h3> }else{ return <h3>Current Turn: <span className="text-primary">{(this.state.game.current_turn.username)}</span> </h3> } } } render() { return ( <div className="row"> <div className="col-sm-6"> {this.currentTurn()} <table> <tbody> { this.renderBoard() } </tbody> </table> </div> <div className="col-sm-6"></div> <Websocket ref="socket" url={this.props.socket} onMessage={this.handleData.bind(this)} reconnect={true}/> </div> ) } } GameBoard.propTypes = { game_id: PropTypes.number, socket: PropTypes.string, current_user: PropTypes.object } export default GameBoard |
7 8 9 10 11 12 13 14 15 16 |
module.exports = { context: __dirname, entry: { lobby: './templates/components/lobby/index', game: './templates/components/game/index' }, output: { path: path.resolve('./static/bundles/'), filename: "[name]-[hash].js" }, |
GameBoard and GameSquare
Obstruction is a game of claiming squares. The game board can be an even number of squares like 6×6 square or a rectangle with dimensions like 10×11.
Our Game model already creates a 6×6 board when a game object is created. This is defined by the cols and rows fields in the model. If you take a look at the create_new model method, you see that we just loop through the rows and create X number of columns for each row as defined by those fields. For each of these, we create a GameSquare object that will keep track of its owner and status throughout the game.
So, we know that each game already has a board ready to display. A board is essentially a collection of squares. We already have a board component, so let’s create a square component. In a new GameSquare.jsx file under the templates/components/game/ folder, add this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
import React, {Component, PropTypes} from 'react' const creator_icon = <img src="/static/img/blue-player.png" /> const creator_surround_icon = <img src="/static/img/blue-surrounding.png"/> const opponent_icon = <img src="/static/img/red-player.png" /> const opponent_surround_icon = <img src="/static/img/red-surrounding.png"/> const empty_icon = <img src="/static/img/empty.png" /> class GameSquare extends Component { constructor(props) { super(props) this.state = { owner: props.owner, possession_type: props.possession_type } this.squareClicked = this.squareClicked.bind(this) } componentWillReceiveProps(newProps){ this.setState({ owner: newProps.owner, possession_type: newProps.possession_type }) } getStatus(){ let selected_icon, surrounding_icon = null // check to see if this square has an owner` if (this.state.owner){ // there's an ower, so check which owner and then the // "posession type" of the square now: // selected = properly owned square // surrounding = next to a properly owned square if (this.state.owner == this.props.game_creator){ // owned by the game creator, so it'll be blue selected_icon = creator_icon surrounding_icon = creator_surround_icon }else{ selected_icon = opponent_icon surrounding_icon = opponent_surround_icon } } switch (this.state.possession_type){ case "Surrounding": return surrounding_icon case "Selected": return selected_icon default: return empty_icon } } checkAvailable(){ if (this.props.isPlayerTurn()){ // check if a valid place to make a move if (this.state.owner == null){ // no owner return true }else{ // owned either as a core spot or a surrounding spot return false } } } takeOwnership(){ // make the user that clicked, the owner and claim the surrounding spots that are available this.props.sendSocketMessage({action: "claim_square", square_id: this.props.square_id }) } squareClicked(square){ // player clicked a square if (this.checkAvailable()){ // available to take ownership this.takeOwnership() } } render() { return ( <td onClick={this.squareClicked} height="60" width="60"> {this.getStatus()} <div className="coords">({this.props.loc_x}, {this.props.loc_y}) </div> </td> ) } } GameSquare.propTypes = { loc_x: PropTypes.number, loc_y: PropTypes.number, square_id: PropTypes.number, owner: PropTypes.number, possession_type: PropTypes.string, game_creator: PropTypes.number, sendSocketMessage: PropTypes.func, isPlayerTurn: PropTypes.func } export default GameSquare |
A brief breakdown of that component:
- Lines 3-7: Icon images loaded as variables so React can reference them as needed
- Lines 10-17: The constructor that sets the owner of the square and the type of possession – either a direct select or surrounding square. More on that later.
- getStatus() determines which icon to use to indicate the status
- checkAvailable() is a function that determines if it’s a valid square on which to move.
- takeOwnership() informs the server of a move through Channels.
- squareClicked() is a click handler which calls the two previous methods to claim a square.
- render() is the React lifecycle method that will finally render the square.
After creating that file if you run Webpack again ( ./node_modules/.bin/webpack --config webpack.config.js --watch ), it should pack the Game components into a separate bundle from our Lobby components. This allows us to reference only that specific bundle in the Django template using the webpack_loader (line 4 in game.html).
If you run the Django server now and visit the Game page, you’ll see that it lists both players and where the game board will be, it just says “Loading…”. This is a placeholder for the game board if it’s not loaded or if the board doesn’t exist. If we take a look at the browser javascript console, you’ll see there is a 404 error with the client attempting to call the /game-from-id/ endpoint. So, it’s attempting to get the game, but never can.
You may be wondering how, if we don’t have the game object, we’re able to show the players and the game name. This is because those parts are rendered in the standard Django template in game.html.
This goes back to what we talked about earlier. Using Django, Channels, and the Rest Framework, we have options on how to deliver the data to the client. I wanted to show each of the different ways in this tutorial, but in real practice, you would have a much more consistent way of using these features. Another good option would be to serialize the game into JSON and the deliver it in the normal Django context from the view. It just needs to be JSON so React can easily work with it.
Anyway, let’s wire up the endpoint now. Update your urls.py to look like this (new lines highlighted):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from django.conf.urls import url from django.contrib import admin from game.views import * from rest_framework.routers import DefaultRouter 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'^lobby/$', LobbyView.as_view()), url(r'^game/(?P<game_id>\d+)/$', GameView.as_view()), url(r'^$', HomeView.as_view()) ] # urls for api - django rest framework urlpatterns += [ url(r'^current-user/', CurrentUserView.as_view()), url(r'^game-from-id/(?P<game_id>\d+)/$', SingleGameViewSet.as_view()), ] router = DefaultRouter() router.register(r'player-games', PlayerGameViewSet, 'player_games') router.register(r'available-games', AvailableGameViewSet, 'available_games') router.register(r'game-squares', GameSquaresViewSet, 'game_squares') urlpatterns += router.urls |
What we’ve added there is the /game-from-id/ endpoint and two additional URLs endpoint for /game-squares/ which we’ll also need.
Now, we need the ViewSets for those, so add the following to your api_views.py file:
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
class SingleGameViewSet(APIView): """ Get all data for a game: Game Details, Squares """ def get(self, request, **kwargs): game = Game.get_by_id(kwargs['game_id']) squares = game.get_all_game_squares() game_serializer = GameSerializer(game) square_serializer = GameSquareSerializer(squares, many=True) return_data = {'game': game_serializer.data, 'squares': square_serializer.data} return Response(return_data) class GameSquaresViewSet(viewsets.ViewSet): def retrieve(self, request, pk=None): game = get_object_or_404(Game, pk=pk) squares = game.get_all_game_squares() serializer = GameSquareSerializer(squares, many=True) return Response(serializer.data) 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) |
And those ViewSets are looking to use some DRF serializers, so we need to add those too. Add these two classes to the bottom of your serializers.py file:
12 13 14 15 16 17 18 19 20 21 22 23 |
class GameSerializer(serializers.ModelSerializer): class Meta: model = Game fields = ('id', 'winner', 'creator', 'opponent', 'cols', 'rows', 'completed', 'created', 'current_turn') depth = 1 class GameSquareSerializer(serializers.ModelSerializer): class Meta: model = GameSquare fields = ('id', 'game', 'owner', 'status', 'row', 'col') |
So we have the DRF code in place, but we’re also using Channels in this new component, so we need supporting Consumer code for that. Add the following to the bottom of your Consumers.py file:
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
class GameConsumer(JsonWebsocketConsumer): # Set to True to automatically port users from HTTP cookies # (you don't need channel_session_user, this implies it) http_user = True def connection_groups(self, **kwargs): """ Called to return the list of groups to automatically add/remove this connection to/from. """ # this sets the game group name, so we can communicate directly with # those channels in the game return ["game-{0}".format(kwargs['game_id'])] def connect(self, message, **kwargs): """ Perform things on connection start """ self.message.reply_channel.send({"accept": True}) pass def receive(self, content, **kwargs): """ Called when a message is received with either text or bytes filled out. """ # include the Django user in the request channel_session_user = True action = content['action'] # handle based on the specific action called if action == 'claim_square': # get the square object square = GameSquare.get_by_id(content['square_id']) # claim it for the user square.claim('Selected', self.message.user) if action == 'chat_text_entered': # chat text game = Game.get_by_id(content['game_id']) game.add_log(content['text'], self.message.user) game.send_game_update() def disconnect(self, message, **kwargs): """ Perform things on connection close """ |
Now with Webpack and a runserver, you can visit your game page again you should now have a nice, little, ugly 6×6 grid of squares. Each square will just be gray to start because no players have claimed it.
In our game, when the game object is created, the “creator” of the game is the first player to make a move. You can see the current player just above the board and this is the only player that can make a move. However, you’ll notice that when you click a square, nothing happens. A clicked square needs to make a call through the websocket, and the websocket needs an socket url to send the message. We’ve added the necessary lines to the Consumer to receive the message, so now let’s add the route to routing.py. Add the highlighted line:
7 8 9 10 11 |
channel_routing = [ route_class(consumers.LobbyConsumer, path=r"^/lobby/"), route_class(consumers.GameConsumer, path=r"^/game/(?P<game_id>\d+)/$") ] |
Great! Now refresh both browser windows and let’s see this in action! If you click a gray square as a “Current Player”, you should see it claim immediately on that window, as well as on the other player’s browser! It’s a functional game at this point!
Now, you may be wondering how the client is getting the latest updates. We didn’t add any code to serialize the data and send it down to the client this time. Well, when we did this before, we added code in the signals.py to listen for changes in the Game model. We could do that again, but as I mentioned before, there are several ways we can send an update through Channels. I wanted to show yet another way to do this in this tutorial, so you’ll find a method named send_game_update in the Game model code.
The only thing Channels needs to send the message to a group, is the group name. Our game’s group name is “group-{id of game}”. So this is easy as we know the game ID in the model. Also, by adding it to a model method, we can call it only when we want to update the client. A signal receiver is called every time a model is saved. So doing it this way, may work better for you in your projects. But there is not necessarily a “best way” that covers every situation.
Play the game out…
Go ahead and take turns claiming squares for each player until there are no more squares to claim. When the last one is taken, “Current Turn” changes to “The Winner”, listing the last player who took a turn.
So, it’s a functional game at this point, but there are MANY things we can add to spice it up and polish it off. Let’s do one thing now.
To show how we can further extend the game experience, I thought would be nice to show a log of game actions and to give a place for the two players to chat back-and-forth. Django Channels gives us an easy way to tie live updating functionality! And if you take a look at the models.py file, you’ll see we’ve already had some Log code in place. How convenient!
hello,
when I run your code, but the web don’t show the game, what’s happend?
Are you going through the tutorial? Or are you running the example project from github?
Do you have a similar project but with channels 2.0? Do you plan to update this one?
Yes, I’m actually working on one now. Hopefully I’ll have it ready in October, but my free time is tight right now.
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.
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.
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?
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?
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.
When will the index.jsx be called ?
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).
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!
Yes, each of those suggestions would work just fine:
Thanks for this django/react tutorial. It is awesome helping me get my head around React and putting all the pieces together!!
Great to hear! I’m glad you found it useful. Thanks for letting me know.
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
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.
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 befrom .views import *
just to avoid any namespace mismatches. I recommend you keep the default views.py.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.
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!
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.
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())
]
Yep – you’re right, thanks for letting me know! I’ve updated the urls.py code.
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.
Thank you for the nice comments! I’m glad you found the tutorial helpful.
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?
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.
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!
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.
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
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.
on page 7 views.py also should import
from django.contrib import messages
Added it, thank you.
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 *
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.
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
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.
Thanks, confirm MIDDLEWARE_CLASSES fixed the issue with ‘AsgiRequest’ object has no attribute ‘session’
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)
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?
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”
Yep, you’re right – a little copy-paste issue on my part. It’s fixed now. Thanks for letting me know!
I have not yet finished your tutorial, but for what I see I can tell you huge THANK YOU!
Thank you, I hope it you find it useful. Please let me know if you have any issues!