We’ve integrated Channels into our app in a basic way, now we need to dive into handling the websockets events. In this video, we’ll flesh out our websockets handlers, and complete the backend for our dashboard.
Now that we have channels configured for a Django project, it's time to write 0:00 the code that will send web socket events to the client when we save a vote object. 0:03 To make this happen, we'll use Django's signals framework. 0:08 And we'll listen for the post save signal on our vote model. 0:11 Now using the signals framework is a little controversial in the Django world 0:15 right now, because it's so easy for signals to be abused. 0:18 The way that we're using them here though, they fit the use case and 0:22 it's not abuse of signals. 0:25 If we were building a front end for a vote creation and 0:27 had something like a form view. 0:30 We probably want to be doing this work there in the form valid method 0:31 instead of as a signal, but for now we're going to it this way. 0:35 So in our votes app we're gonna start by creating a new file 0:38 that is named signals.py. 0:43 And we're gonna add some code in here that 0:45 will let us test that our handlers are working correctly. 0:48 So from django.db.models.signals, 0:52 we need to import post save, which is the signal that we want to use. 0:55 And from django.dispatch, we need to import receiver and 1:00 then from models, we need to import Vote and then we'll create our receiver. 1:06 So it's going to be listening to the post_save signal and 1:14 the sender is going to be the vote model and then we'll create our vote_save. 1:19 Let's call this vote_save_handler function and (sender, 1:25 **kwargs):, and we're just gonna print out Saved. 1:29 Now in order to make sure this code gets called when the vote model is saved. 1:36 We need to make sure the code is initialized when our vote app is 1:39 initialized. 1:42 Now inside the votes app, there is a file named apps.py. 1:44 So inside of there, we're going to add the following inside of this votes config. 1:49 So we're going to a new method called ready. 1:55 Which just takes self and 1:57 what we're gonna do here is we're gonna import votes.signals. 1:59 So that guarantee that our signal handler gets registered whenever 2:04 the app is loaded. 2:07 Or it will once you make sure that the votes config actually gets run. 2:09 We do that by changing the app installation in our settings.py. 2:15 So in settings.py and installed apps where we have votes right now, 2:20 we need to change this to be votes.apps.VotesConfig. 2:25 And that will make sure that the ready method is called. 2:29 Our signal is set up to listen to the post save, our app registers and 2:33 make sure that signal is registered and waiting whenever the app is started. 2:38 So let's make sure the servers running and 2:45 then let's go over here to the admin. 2:53 And if you haven't you'll want to go ahead and run all your migrations and 2:57 create a super user and all that kind of stuff. 3:01 Now I have a few voters already set up and then I'm gonna go ahead and 3:05 I'm gonna create a new vote. 3:09 So I'm gonna say this is issue number 15. 3:12 It's in the housing category. 3:16 And let's just say that everyone voted yes on this one, it was passed unanimously. 3:19 I'm gonna hit Save and 3:23 if we look here in the console we see the saved message showed up. 3:24 So we know that our vote handler is working. 3:29 Okay, so that's great. 3:33 We know everything is wired up, so 3:34 now we can have channels send a message via web sockets. 3:37 So we're going to do that again over here in our signals.py. 3:40 Instead of printing Saved, we're now going to send that message. 3:43 So we need to import one new thing and we need to stay from channels import Channel. 3:48 Which is how we specify which channel we want it to send a message on. 3:56 And then instead of printing Saved, 4:00 I want to do channel and the channel name which is gonna be vote-saved. 4:06 And I want to send a message, which is just going to be a dictionary. 4:12 And it's going to be one key which is vote and that will be the sender.id, so 4:15 that's the votes ID that was sent in. 4:20 Now the reason we have to do it like this with the vote saved and 4:24 the send and the dictionary. 4:28 Is because channels messages are sent over channels layers. 4:31 And it's not uncommon that one of the back ends the things 4:37 behind one of the channels layers is a mechanism that doesn't speak Python. 4:42 So because of that we want to send everything 4:48 as something that's serializable to JSON. 4:51 In practice, this means that you should really only try to pass things 4:54 that look like numbers, strings, lists or 4:58 maybe dictionaries since not all Python objects are going to translate cleanly. 5:00 Right now however, we're sending to a channel that doesn't exist. 5:06 So we need to make that exist, right? 5:10 In our votes app we need to create a consumers.py file. 5:12 Let's make a new file here, consumers.py. 5:17 And in this file, we're going to create the handlers for 5:22 getting our messages back and forth. 5:26 A consumer,which is what we're making here. 5:28 There are pieces of code that take incoming messages and 5:30 then they act on them in some way. 5:32 In our case, that action's going to be gathering data and 5:34 sending a web sockets message to our clients. 5:37 So let's import json, because we have to deal with JSON. 5:39 From channels we're going to import the group object and 5:44 then from .models we're going to import Vote and Voter. 5:48 And then let's make a new function that's named vote_saved. 5:54 And it's going to receive a message. 5:58 We're going to have data which is an empty dictionary and then for our voter, 6:01 in Voter.objects.all, We're gonna 6:06 make a new variable called blank_data which is also an empty dictionary. 6:12 And then for choice in Vote.categories, 6:17 Blank_data[choice] Is 6:25 equal to a new dictionary with yes set to 0 and no to 0. 6:31 And if you haven't looked at the models yet, Vote categories 6:37 is just be different categories the votes can be in like schools or 6:40 public safety or transportation. 6:43 And zero is the value stored in the database and 6:46 then we'll do data[voter.id] is equal to whatever the blank_data now is. 6:50 Okay, so now for 6:58 vote in Vote.objects.all, 7:00 for voter in vote.yes_votes.all. 7:06 Data[voter.id] [vote.category]. 7:12 Yes plus equals one and then let's do 7:19 the same thing here for the no votes. 7:24 So that way we just were running a calculation on what these are. 7:36 And then finally we want to do group('dashboard').send and 7:42 we're going to send a new messages. 7:47 We're just gonna call this one text and it'll be json.dumps(data). 7:50 So let's look this over just for 7:58 a second to make sure that we understand everything that's going on here. 8:01 So we start by importing things that we need, 8:05 the stuff from the channel's package and our models. 8:08 And then inside of our consumer here, we set up a couple of bits 8:12 of placeholder data, we're just saying yes a zero a no a zero for each of the voters. 8:17 And then we go through each of the votes which is the different 8:22 issues that they're voting on, right? 8:26 So increasing the number of teachers or re-paving a certain road or whatever. 8:27 And then we record for each voter in each category, each time they said yes and 8:33 each time they said no. 8:37 And then finally we send this out to this dashboard group. 8:39 Now groups are a little different. 8:43 A channel's group is a collection of clients that should receive the same 8:46 messages from the server and we need a consumer that will register those 8:49 listeners into that group. 8:56 So any message they that get sent to the dashboard group 8:58 goes to everyone in that group. 9:01 But to register somebody to that group, we're gonna make a new method or 9:03 a new function here, which we'll call ws_connect. 9:07 It's also going to receive a message and 9:12 all that we're going to do is we're gonna say 9:14 Group('dashboard').add(message.reply_chan- nel). 9:16 So the reply channel on the message gets added to the group and 9:25 the group sends messages to all the reply channels that are registered with it. 9:28 So we should have one last consumer and 9:32 that is that we should allow people to leave a group. 9:35 So def ws_disconnect and so 9:38 also take a message and we'll say 9:43 group('dashboard').discard(message.reply_- channel). 9:46 So why are those spouse of having a signal send a message to a consumer that 9:53 then sends the web sockets message? 9:57 Well, it's because the secret to channels is that channels 10:00 solves the many connection problems of web sockets. 10:03 By speeding up multiple workers and 10:06 then allocating those workers to handle events as they arise. 10:07 A consumer will often run in a different worker than the normal request 10:11 response site. 10:14 So they have to communicate with each other over channels messages. 10:15 Finally, we need to connect these consumers to routes, 10:19 which means we have to update our routing. 10:22 So back over in routing.py, let's add a new import here. 10:25 And we'll say from votes.consumers 10:30 import ws_connect ws_disconnect and 10:34 vote_saved and then let's make our routes. 10:39 So we'll have a route here for a web socket.connect which as you can 10:46 imagine is fired when the web socket connects. 10:51 This will be ws_connect. 10:54 We'll add a route for websocket.disconnect which as you can imagine is for 10:57 when a websocket disconnects. 11:02 And we'll run ws_disconnect. 11:04 And finally, we will add a new route which is the vote-saved route 11:07 and that will run our_vote saved consumer. 11:13 And that's it. 11:18 The back end of our dashboard is done and 11:19 we're ready to start sending data to our front end over web sockets. 11:21 In the next video, we'll build that front end and 11:24 we'll get everything running together. 11:26
You need to sign up for Treehouse in order to download course files.Sign up