Por mais de uma década após o Django ter sido lançado em 2005, as páginas eram em sua maioria estáticas, AJAX era usado apenas em casos de uso limitados e as coisas eram relativamente simples. Nos últimos cinco anos, os aplicativos da web em tempo real evoluíram, tendendo para mais interação cliente-servidor e ponto a ponto. Este tipo de comunicação é alcançável com WebSockets , um novo protocolo que fornece comunicação full-duplex e mantém uma conexão aberta e persistente entre cliente e servidor.
Canais Django facilita o suporte a WebSockets no Django de uma maneira semelhante às visualizações HTTP tradicionais. Ele envolve o suporte de visualização assíncrona nativa do Django, permitindo que os projetos do Django lidem não apenas com HTTP, mas também com protocolos que requerem conexões de longa duração, como WebSockets, MQTT, chatbots, etc.
Neste tutorial, mostraremos como criar um aplicativo em tempo real com canais Django. Para demonstrar com um exemplo ao vivo, criaremos um jogo da velha para dois jogadores, conforme ilustrado abaixo. Você pode acessar o código-fonte completo em meu repositório GitHub .
Configurando um projeto Django
Siga as etapas descritas abaixo para configurar seu projeto Django.
Primeiro, instale o Django e os canais. Você também deve instalar channels_redis
para que os canais saibam como interagir com o Redis.
Execute o seguinte comando:
pip install django==3.1 canais de instalação pip==3.0 pip install channels_redis==3.2
Você deve usar pip3 para Linux/mac em vez de pip e python3 no lugar de python. Eu usei django==3.1
e channels==3.0
, channels_redis==3.2.0
para este guia.
Inicie o projeto Django:
django-admin startproject tic_tac_toe
A seguir, crie um aplicativo com o nome game
:
jogo python manage.py startapp
Adicione canais
e jogo
no INSTALLED_APPS
dentro de seu settings.py
:
## settings.py INSTALLED_APPS=[ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'canais', 'jogos' ]
Execute migrate
para aplicar migrações não aplicadas:
python manage.py migrate
Além disso, adicione STATICFILES_DIRS
dentro de settings.py
:
## settings.py importar os STATICFILES_DIRS=[ os.path.join (BASE_DIR,"estático"), ]
Agora é hora de criar os arquivos necessários para nosso projeto Django. Ao longo do guia, você pode consultar a seguinte estrutura de diretório:
├── db.sqlite3 ├── jogo │ ├── consumer.py │ ├── routing.py │ ├── modelos │ │ ├── base.html │ │ ├── game.html │ │ └── index.html │ └── views.py ├── manage.py ├── requisitos.txt ├── estático │ ├── css │ │ └── main.css │ └── js │ └── game.js └── tic_tac_toe ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py
Integre a biblioteca de canais Django
Agora vamos integrar os canais ao projeto Django.
Django> 2 não tem suporte integrado para ASGI, então você precisa usar a alternativa substituta do Channel.
Atualize o asgi.py
conforme mostrado abaixo:
## tic_tac_toe/asgi.py importar os importar django from channels.http import AsgiHandler from channels.routing import ProtocolTypeRouter os.environ.setdefault ('DJANGO_SETTINGS_MODULE','tic_tac_toe.settings') django.setup () application=ProtocolTypeRouter ({ "http": AsgiHandler (), ## IMPORTANTE:: Apenas HTTP por enquanto. (Podemos adicionar outros protocolos posteriormente.) })
Atualize settings.py
e altere o aplicativo Django de WSGI para ASGI fazendo as seguintes alterações. Isso irá apontar os canais para a configuração de roteamento raiz.
## settings.py # WSGI_APPLICATION='tic_tac_toe.wsgi.application' # Canais ASGI_APPLICATION="tic_tac_toe.asgi.application"
Em seguida, ative a camada do canal , que permite várias instâncias do consumidor para se comunicarem.
Observe que você pode usar o Redis como sua loja de apoio. Para habilitar o Redis, você pode usar o Método 1 se quiser o Redis Cloud ou o Método 2 para o Redis local. Neste guia, usei o Método 3- Camada de canal In-memory
-que é útil para testes e para fins de desenvolvimento local.
Para ativar a camada do canal, adicione o seguinte CHANNEL_LAYERS
em settings.py
:
## settings.py CHANNEL_LAYERS={ 'predefinição': { ### Método 1: Via redis lab #'BACKEND':'channels_redis.core.RedisChannelLayer', #'CONFIG': { #"hosts": [ #'redis://h: & lt; password & gt; @ & lt; redis Endpoint & gt;: & lt; port & gt;' #], #}, ### Método 2: Via Redis local #'BACKEND':'channels_redis.core.RedisChannelLayer', #'CONFIG': { #"hosts": [('127.0.0.1', 6379)], #}, ### Método 3: Via camada de canal In-memory ## Usando este método. "BACKEND":"channels.layers.InMemoryChannelLayer" }, }
Certifique-se de que o servidor de desenvolvimento de canais está funcionando corretamente. Execute o seguinte comando:
python manage.py runserver
Projetando a página de índice
Vamos começar construindo a página de índice, onde é solicitado ao usuário o código do quarto e a escolha do personagem (X ou O).
Crie a visualização baseada em função em game/views.py
:
# game/views.py de django.shortcuts importar render, redirecionar índice def (pedido): if request.method=="POST": room_code=request.POST.get ("room_code") char_choice=request.POST.get ("character_choice") return redirect ( '/play/% s? & amp; choice=% s' % (room_code, char_choice) ) renderização de retorno (solicitação,"index.html", {})
A seguir, crie a rota para a visualização do índice em tic_tac_toe/urls.py
:
## urls.py do caminho de importação django.urls do índice de importação game.views urlpatterns=[ ##... Outros URLS caminho ('', índice), ]
Agora, crie o template base em game/templates/base.html
(ignore se você já o criou). Este modelo será herdado para outras visualizações de modelo.
<{% comment%} base.html {% endcomment%} {% load static%} & lt;! DOCTYPE html & gt; & lt; html lang="en"& gt; & lt; head & gt; & lt; meta charset="UTF-8"& gt; & lt; meta name="janela de visualização"content="largura=largura do dispositivo, escala inicial=1,0"& gt; & lt; title & gt; Jogo da velha & lt;/title & gt; & lt; link rel='stylesheet'href='{% static"/css/main.css"%}'& gt; & lt;/head & gt; & lt; body & gt; {% block content%} {% endblock content%} & lt; script src="{% static'js/game.js'%}"& gt; & lt;/script & gt; {% block javascript%} {% endblock javascript%} & lt;/body & gt; & lt;/html & gt;
Crie o modelo de visualização para a visualização do índice em game/templates/index.html
:
{% comment%} index.html {% endcomment%} {% extends'base.html'%} {% block content%} & lt; div class="wrapper"& gt; & lt; h1 & gt; Bem-vindo ao jogo Tic Tac Toe & lt;/h1 & gt; & lt; form method="POST"& gt; {% csrf_token%} & lt; div class='form-control'& gt; & lt; label for="room"& gt; Room id & lt;/label & gt; & lt; input id="room"type="text"name="room_code"obrigatório/& gt; & lt;/div & gt; & lt; div class='form-control'& gt; & lt; label for="character_choice"& gt; Seu personagem & lt;/label & gt; & lt; selecione para="escolha_caractere"name="escolha_caractere"& gt; & lt; valor da opção="X"& gt; X & lt;/opção & gt; & lt; valor da opção="O"& gt; O & lt;/opção & gt; & lt;/select & gt; & lt;/div & gt; & lt; input type="submit"class="button"value="Start Game"/& gt; & lt;/div & gt; & lt;/form & gt; {% endblock content%}
Inicie o servidor de desenvolvimento Django e navegue até http://127.0.0.1:8000 para verificar se o índice a página está funcionando:
Projetando a página do jogo
Agora que a página de índice está pronta, vamos construir a página do jogo.
Comece criando game/views.py
:
## game/views.py de django.shortcuts importar render, redirecionar de django.http import Http404 def game (request, room_code): escolha=solicitação.GET.get ("escolha") se a escolha não estiver em ['X','O']: raise Http404 ("A escolha não existe") contexto={ "char_choice": escolha, "room_code": room_code } retornar render (solicitação,"game.html", contexto)
Adicione a rota de URL da visualização acima:
## urls.py do caminho de importação django.urls do jogo de importação game.views urlpatterns=[ ## outras rotas de url caminho ('play/& lt; room_code & gt;', jogo), ]
Agora que o back-end está pronto, vamos criar o front-end do tabuleiro do jogo. Abaixo está o game/templates/game.html
template Django:
{% extends'base.html'%} {% comment%} game.html {% endcomment%} {% load static%} {% block content%} & lt; div class="wrapper"& gt; & lt; div class="head"& gt; & lt; h1 & gt; TIC TAC TOE & lt;/h1 & gt; & lt; h3 & gt; Bem-vindo à sala _ {{room_code}} & lt;/h3 & gt; & lt;/div & gt; & lt; div id="game_board"room_code={{room_code}} char_choice={{char_choice}} & gt; & lt; div class=índice de dados"quadrado"='0'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='1'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='2'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='3'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='4'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='5'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='6'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='7'& gt; & lt;/div & gt; & lt; div class=índice de dados"quadrado"='8'& gt; & lt;/div & gt; & lt;/div & gt; & lt; div id="alert_move"& gt; Sua vez. Faça sua jogada & lt; strong & gt; {{char_choice}} & lt;/strong & gt; & lt;/div & gt; & lt;/div & gt; {% endblock content%}
Para fazer com que a grade e a página de índice tenham uma boa aparência, adicione o CSS, conforme mostrado abaixo:
/* static/css/main.css */ corpo { /* largura: 100%; */ altura: 90vh; plano de fundo: # f1f1f1; display: flex; justificar-conteúdo: centro; alinhar-itens: centro; } #game_board { display: grade; grid-gap: 0.5em; colunas de modelo de grade: repetir (3, 1fr); largura: 16em; altura: automático; margem: 0,5em 0; } .quadrado{ plano de fundo: # 2f76c7; largura: 5em; altura: 5em; display: flex; justificar-conteúdo: centro; alinhar-itens: centro; raio da borda: 0,5em; peso da fonte: 500; cor branca; sombra da caixa: 0,025em 0,125em 0,25em rgba (0, 0, 0, 0,25); } .cabeça{ largura: 16em; alinhamento de texto: centro; } .wrapper h1, h3 { cor: # 0a2c1a; } rótulo { tamanho da fonte: 20px; cor: # 0a2c1a; } entrada, selecione { margin-bottom: 10px; largura: 100%; preenchimento: 15px; borda: 1px sólido # 125a33; tamanho da fonte: 14px; cor de fundo: # 71d19e; cor branca; } .botão{ cor branca; espaço em branco: nowrap; cor de fundo: # 31d47d; preenchimento: 10px 20px; fronteira: 0; raio da borda: 2px; transição: todos os 150ms atenuados; }
Ao executar o servidor de desenvolvimento, você verá o tabuleiro do jogo, conforme mostrado abaixo:
Adicionando WebSockets ao seu aplicativo Django
Agora que as páginas foram criadas, vamos adicionar os WebSockets a elas.
Insira o seguinte código em game/consumidor.py
:
## game/consumer.py import json from channels.generic.websocket import AsyncJsonWebsocketConsumer classe TicTacToeConsumer (AsyncJsonWebsocketConsumer): async def connect (self): self.room_name=self.scope \ ['url_route'\] ['kwargs'] ['room_code'] self.room_group_name='room_% s'% self.room_name # Entrar no grupo de salas esperar self.channel_layer.group_add ( self.room_group_name, self.channel_name ) aguarde self.accept () desconexão por defeito assíncrono (self, close_code): imprimir ("Desconectado") # Sair do grupo da sala esperar self.channel_layer.group_discard ( self.room_group_name, self.channel_name ) async def receive (self, text_data): """ Receba mensagem do WebSocket. Receba o evento e envie o evento apropriado """ resposta=json.loads (text_data) event=response.get ("evento", Nenhum) message=response.get ("mensagem", Nenhum) se evento=='MOVE': # Envia mensagem para o grupo da sala esperar self.channel_layer.group_send (self.room_group_name, { 'type':'send_message', 'mensagem': mensagem, "evento":"MOVE" }) se evento=='INICIAR': # Envia mensagem para o grupo da sala esperar self.channel_layer.group_send (self.room_group_name, { 'type':'send_message', 'mensagem': mensagem, 'evento':"INICIAR" }) se evento=='END': # Envia mensagem para o grupo da sala esperar self.channel_layer.group_send (self.room_group_name, { 'type':'send_message', 'mensagem': mensagem, 'evento':"END" }) async def send_message (self, res): """Receber mensagem do grupo de salas""" # Enviar mensagem para WebSocket esperar self.send (text_data=json.dumps ({ "carga útil": res, }))
Crie uma configuração de roteamento para o aplicativo de jogo que tem uma rota para o consumidor. Crie um novo arquivo game/routing.py
e cole o seguinte código:
## game/routing.py de django.conf.urls import url from game.consumers import TicTacToeConsumer websocket_urlpatterns=[ url (r'^ ws/play/(? P & lt; room_code & gt; \ w +)/$', TicTacToeConsumer.as_asgi ()), ]
A próxima etapa é apontar a configuração de roteamento raiz no módulo game.routing
. Atualize tic_tac_toe/asgi.py
da seguinte maneira:
## tic_tac_toe/asgi.py importar os de django.core.asgi import get_asgi_application from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter import game.routing os.environ.setdefault ('DJANGO_SETTINGS_MODULE','tic_tac_toe.settings') # application=get_asgi_application () application=ProtocolTypeRouter ({ "http": get_asgi_application (), "websocket": AuthMiddlewareStack ( URLRouter ( game.routing.websocket_urlpatterns ) ), })
Vamos construir a parte final do código criando o JavaScript, que é o lado do cliente que se comunica com o servidor de forma assíncrona. Coloque o seguinte código em static/js/game.js
:
//static/js/game.js var roomCode=document.getElementById ("game_board"). getAttribute ("room_code"); var char_choice=document.getElementById ("game_board"). getAttribute ("char_choice"); var connectionString='ws://'+ window.location.host +'/ws/play/'+ roomCode +'/'; var gameSocket=novo WebSocket (connectionString); //Tabuleiro de jogo para manter o estado do jogo var gameBoard=[ -1,-1,-1, -1,-1,-1, -1,-1,-1, ]; //Índices vencedores. winIndices=[ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ] deixe moveCount=0;//Número de movimentos feitos let myturn=true;//Variável booleana para obter a vez do jogador. //Adicione o ouvinte de evento de clique em cada bloco. let elementArray=document.getElementsByClassName ('square'); para (var i=0; i & lt; elementArray.length; i ++) { elementArray [i].addEventListener ("click", event=& gt; { const index=event.path [0].getAttribute ('data-index'); if (gameBoard [index]==-1) { if (! myturn) { alerta ("Espere que outro faça o movimento") } outro{ myturn=false; document.getElementById ("alert_move"). style.display='none';//Esconder make_move (índice, char_choice); } } }) } //Fazer um movimento function make_move (index, player) { índice=parseInt (índice); let data={ "evento":"MOVE", "mensagem": { "índice": índice, "jogador": jogador } } if (gameBoard [index]==-1) { //se o movimento for válido, atualize o tabuleiro do jogo //indique e envie a mudança para o servidor. moveCount ++; if (jogador=='X') gameBoard [índice]=1; else if (player=='O') gameBoard [índice]=0; outro{ alert ("Escolha de personagem inválida"); retorna falso; } gameSocket.send (JSON.stringify (data)) } //coloque o movimento na caixa do jogo. elementArray [index].innerHTML=player; //verifique o vencedor const win=checkWinner (); if (myturn) { //se o jogador for vencedor, envie o evento END. if (win) { dados={ "evento":"END", "message": `$ {player} é o vencedor. Jogar de novo? ` } gameSocket.send (JSON.stringify (data)) } else if (! win & amp; & amp; moveCount==9) { dados={ "evento":"END", "message":"Empate. Jogar de novo?" } gameSocket.send (JSON.stringify (data)) } } } //função para reiniciar o jogo. function reset () { gameBoard=[ -1,-1,-1, -1,-1,-1, -1,-1,-1, ]; moveCount=0; myturn=true; document.getElementById ("alert_move"). style.display='inline'; para (var i=0; i & lt; elementArray.length; i ++) { elementArray [i].innerHTML=""; } } //verifique se o movimento deles está ganhando const check=(winIndex)=& gt; { E se ( gameBoard [winIndex [0]]!==-1 & amp; & amp; gameBoard [winIndex [0]]===gameBoard [winIndex [1]] & amp; & amp; gameBoard [winIndex [0]]===gameBoard [winIndex [2]] ) retornar verdadeiro; retorna falso; }; //função para verificar se o jogador é o vencedor. function checkWinner () { deixe win=false; if (moveCount & gt;=5) { winIndices.forEach ((w)=& gt; { if (verifique (w)) { win=true; windex=w; } }); } retorno vitória; } //Função principal que lida com a conexão //do websocket. function connect () { gameSocket.onopen=função open () { console.log ('Conexão WebSockets criada.'); //no websocket aberto, envie o evento START. gameSocket.send (JSON.stringify ({ "evento":"INICIAR", "mensagem":"" })); }; gameSocket.onclose=function (e) { console.log ('O soquete está fechado. Haverá uma tentativa de reconexão em 1 segundo.', e.reason); setTimeout (function () { conectar(); }, 1000); }; //Enviando as informações sobre a sala gameSocket.onmessage=function (e) { //Ao receber a mensagem do servidor //Execute as etapas apropriadas em cada evento. deixe dados=JSON.parse (e.data); dados=dados ["carga útil"]; let message=data ['message']; let event=data ["evento"]; switch (evento) { case"START": Redefinir(); intervalo; case"END": alerta (mensagem); Redefinir(); intervalo; case"MOVE": if (mensagem ["jogador"]!=char_choice) { make_move (mensagem ["index"], mensagem ["player"]) myturn=true; document.getElementById ("alert_move"). style.display='inline'; } intervalo; predefinição: console.log ("Sem evento") } }; if (gameSocket.readyState==WebSocket.OPEN) { gameSocket.onopen (); } } //chame a função de conexão no início. conectar();
Agora finalmente terminamos a codificação e estamos prontos para jogar nosso jogo da velha!
Conclusão
Cobrimos muitos tópicos neste tutorial: Canais Django, WebSockets e alguns front-end. Nosso jogo até agora tem apenas funcionalidades básicas mínimas. Você está convidado a usar seu novo conhecimento básico para brincar e adicionar mais funcionalidades a ele. Alguns exercícios adicionais que você pode fazer incluem:
- Adicionando autenticação de usuário
- Mostrando usuários on-line
- Adicionando dados de jogos ao banco de dados
- Tornando o aplicativo escalonável adicionando Redis em vez da camada de canal na memória
- Adicionando AI
Verifique meu repositório GitHub para o código-fonte completo usado neste exemplo.
A postagem Django Channels and WebSockets apareceu primeiro em LogRocket Blog .