sql >> Database >  >> RDS >> Database

Aan de slag met Django-kanalen

In deze tutorial zullen we Django Channels gebruiken om een ​​realtime applicatie te maken die een lijst met gebruikers bijwerkt terwijl ze in- en uitloggen.

Met WebSockets (via Django Channels) die de communicatie tussen de client en de server beheert, wordt telkens wanneer een gebruiker is geverifieerd, een gebeurtenis uitgezonden naar elke andere verbonden gebruiker. Het scherm van elke gebruiker verandert automatisch, zonder dat ze hun browser opnieuw hoeven te laden.

OPMERKING: We raden je aan enige ervaring met Django te hebben voordat je aan deze tutorial begint. U moet ook bekend zijn met het concept van WebSockets.

Gratis bonus: Klik hier om toegang te krijgen tot een gratis Django Learning Resources Guide (PDF) met tips en trucs en veelvoorkomende valkuilen die je moet vermijden bij het bouwen van Python + Django-webapplicaties.

Onze applicatie gebruikt:

  • Python (v3.6.0)
  • Django (v1.10.5)
  • Django-kanalen (v1.0.3)
  • Redis (v3.2.8)

Doelstellingen

Aan het einde van deze tutorial ben je in staat om...

  1. Voeg ondersteuning voor websockets toe aan een Django-project via Django-kanalen
  2. Stel een eenvoudige verbinding in tussen Django en een Redis-server
  3. Implementeer basisgebruikersauthenticatie
  4. Gebruik Django-signalen om actie te ondernemen wanneer een gebruiker in- of uitlogt


Aan de slag

Maak eerst een nieuwe virtuele omgeving om de afhankelijkheden van ons project te isoleren:

$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$

Installeer Django, Django Channels en ASGI Redis en maak vervolgens een nieuw Django-project en -app:

(env)$ pip install django==1.10.5 channels==1.0.2 asgi_redis==1.0.0
(env)$ django-admin.py startproject example_channels
(env)$ cd example_channels
(env)$ python manage.py startapp example
(env)$ python manage.py migrate

OPMERKING: In de loop van deze tutorial zullen we verschillende bestanden en mappen maken. Raadpleeg de mappenstructuur van de repository van het project als je vastloopt.

Download en installeer vervolgens Redis. Als je een Mac gebruikt, raden we aan om Homebrew te gebruiken:

$ brew install redis

Start de Redis-server in een nieuw terminalvenster en zorg ervoor dat deze op de standaardpoort 6379 draait. Het poortnummer is belangrijk wanneer we Django vertellen hoe hij met Redis moet communiceren.

Voltooi de installatie door INSTALLED_APPS bij te werken in de settings.py . van het project bestand:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'example',
]

Configureer vervolgens de CHANNEL_LAYERS door een standaard backend en routering in te stellen:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgi_redis.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
        'ROUTING': 'example_channels.routing.channel_routing',
    }
}

Deze gebruikt een Redis-backend die ook nodig is in de productie.



WebSockets 101

Normaal gesproken gebruikt Django HTTP om te communiceren tussen de client en de server:

  1. De client stuurt een HTTP-verzoek naar de server.
  2. Django ontleedt het verzoek, extraheert een URL en koppelt deze vervolgens aan een weergave.
  3. De weergave verwerkt het verzoek en retourneert een HTTP-antwoord naar de client.

In tegenstelling tot HTTP staat het WebSockets-protocol bidirectionele communicatie toe, wat inhoudt dat de server gegevens naar de client kan pushen zonder dat de gebruiker daarom vraagt. Bij HTTP krijgt alleen de client die een aanvraag heeft gedaan een reactie. Met WebSockets kan de server met meerdere clients tegelijk communiceren. Zoals we later in deze tutorial zullen zien, sturen we WebSockets-berichten met de ws:// prefix, in tegenstelling tot http:// .

OPMERKING: Bekijk snel de documentatie over Channels Concepts voordat je erin duikt.



Consumenten en groepen

Laten we onze eerste consument maken, die de basisverbindingen tussen de client en de server afhandelt. Maak een nieuw bestand met de naam example_channels/example/consumers.py :

from channels import Group


def ws_connect(message):
    Group('users').add(message.reply_channel)


def ws_disconnect(message):
    Group('users').discard(message.reply_channel)   

Consumenten zijn de tegenhanger van de opvattingen van Django. Elke gebruiker die verbinding maakt met onze app, wordt toegevoegd aan de groep "gebruikers" en ontvangt berichten die door de server worden verzonden. Wanneer de klant de verbinding met onze app verbreekt, wordt het kanaal uit de groep verwijderd en ontvangt de gebruiker geen berichten meer.

Laten we vervolgens routes instellen, die op bijna dezelfde manier werken als de Django URL-configuratie, door de volgende code toe te voegen aan een nieuw bestand met de naam example_channels/routing.py :

from channels.routing import route
from example.consumers import ws_connect, ws_disconnect


channel_routing = [
    route('websocket.connect', ws_connect),
    route('websocket.disconnect', ws_disconnect),
]

Dus hebben we channel_routing gedefinieerd in plaats van urlpatterns en route() in plaats van url() . Merk op dat we onze consumentenfuncties hebben gekoppeld aan WebSockets.


Sjablonen

Laten we wat HTML opschrijven die via een WebSocket met onze server kan communiceren. Maak een map "templates" in "example" en voeg vervolgens een map "example" toe in "templates" - "example_channels/example/templates/example".

Voeg een _base.html . toe bestand:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <title>Example Channels</title>
</head>
<body>
  <div class="container">
    <br>
    {% block content %}{% endblock content %}
  </div>
  <script src="//code.jquery.com/jquery-3.1.1.min.js"></script>
  {% block script %}{% endblock script %}
</body>
</html>

En user_list.html :

{% extends 'example/_base.html' %}

{% block content %}{% endblock content %}

{% block script %}
  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');

    socket.onopen = function open() {
      console.log('WebSockets connection created.');
    };

    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
{% endblock script %}

Wanneer de client nu met succes een verbinding met de server opent met behulp van een WebSocket, zien we een bevestigingsbericht naar de console worden afgedrukt.



Beelden

Stel een ondersteunende Django-weergave in om onze sjabloon weer te geven in example_channels/example/views.py :

from django.shortcuts import render


def user_list(request):
    return render(request, 'example/user_list.html')

Voeg de URL toe aan example_channels/example/urls.py :

from django.conf.urls import url
from example.views import user_list


urlpatterns = [
    url(r'^$', user_list, name='user_list'),
]

Werk ook de project-URL bij in example_channels/example_channels/urls.py :

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('example.urls', namespace='example')),
]


Test

Klaar om te testen?

(env)$ python manage.py runserver

OPMERKING: U kunt ook python manage.py runserver --noworker . uitvoeren en python manage.py runworker in twee verschillende terminals om de interface en de werkservers als twee afzonderlijke processen te testen. Beide methoden werken!

Wanneer u http://localhost:8000/ bezoekt, zou u het verbindingsbericht naar de terminal moeten zien afdrukken:

[2017/02/19 23:24:57] HTTP GET / 200 [0.02, 127.0.0.1:52757]
[2017/02/19 23:24:58] WebSocket HANDSHAKING /users/ [127.0.0.1:52789]
[2017/02/19 23:25:03] WebSocket DISCONNECT /users/ [127.0.0.1:52789]



Gebruikersauthenticatie

Nu we hebben bewezen dat we een verbinding kunnen openen, is onze volgende stap het afhandelen van gebruikersauthenticatie. Onthoud:we willen dat een gebruiker kan inloggen op onze app en een lijst kan zien van alle andere gebruikers die zijn geabonneerd op de groep van die gebruiker. Ten eerste hebben we een manier nodig waarop gebruikers accounts kunnen maken en kunnen inloggen. Begin met het maken van een eenvoudige inlogpagina waarmee een gebruiker zich kan verifiëren met een gebruikersnaam en wachtwoord.

Maak een nieuw bestand met de naam log_in.html binnen “example_channels/example/templates/example”:

{% extends 'example/_base.html' %}

{% block content %}
  <form action="{% url 'example:log_in' %}" method="post">
    {% csrf_token %}
    {% for field in form %}
      <div>
        {{ field.label_tag }}
        {{ field }}
      </div>
    {% endfor %}
    <button type="submit">Log in</button>
  </form>
  <p>Don't have an account? <a href="{% url 'example:sign_up' %}">Sign up!</a></p>
{% endblock content %}

Update vervolgens example_channels/example/views.py zoals zo:

from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect


def user_list(request):
    return render(request, 'example/user_list.html')


def log_in(request):
    form = AuthenticationForm()
    if request.method == 'POST':
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            return redirect(reverse('example:user_list'))
        else:
            print(form.errors)
    return render(request, 'example/log_in.html', {'form': form})


def log_out(request):
    logout(request)
    return redirect(reverse('example:log_in'))

Django wordt geleverd met formulieren die algemene authenticatiefunctionaliteit ondersteunen. We kunnen het AuthenticationForm . gebruiken om de gebruikersaanmelding af te handelen. Dit formulier controleert de opgegeven gebruikersnaam en het wachtwoord en retourneert vervolgens een User object als een gevalideerde gebruiker wordt gevonden. We loggen de gevalideerde gebruiker in en leiden deze door naar onze homepage. Een gebruiker moet ook de mogelijkheid hebben om uit te loggen bij de applicatie, dus we creëren een uitlogweergave die die functionaliteit biedt en brengt de gebruiker vervolgens terug naar het inlogscherm.

Update vervolgens example_channels/example/urls.py :

from django.conf.urls import url
from example.views import log_in, log_out, user_list


urlpatterns = [
    url(r'^log_in/$', log_in, name='log_in'),
    url(r'^log_out/$', log_out, name='log_out'),
    url(r'^$', user_list, name='user_list')
]

We hebben ook een manier nodig om nieuwe gebruikers te creëren. Maak een aanmeldingspagina op dezelfde manier als de aanmelding door een nieuw bestand toe te voegen met de naam sign_up.html naar “example_channels/example/templates/example”:

{% extends 'example/_base.html' %}

{% block content %}
  <form action="{% url 'example:sign_up' %}" method="post">
    {% csrf_token %}
    {% for field in form %}
      <div>
        {{ field.label_tag }}
        {{ field }}
      </div>
    {% endfor %}
    <button type="submit">Sign up</button>
    <p>Already have an account? <a href="{% url 'example:log_in' %}">Log in!</a></p>
  </form>
{% endblock content %}

Merk op dat de aanmeldingspagina een link naar de aanmeldingspagina heeft en dat de aanmeldingspagina een link naar de aanmeldingspagina heeft.

Voeg de volgende functie toe aan de weergaven:

def sign_up(request):
    form = UserCreationForm()
    if request.method == 'POST':
        form = UserCreationForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('example:log_in'))
        else:
            print(form.errors)
    return render(request, 'example/sign_up.html', {'form': form})

We gebruiken een ander ingebouwd formulier voor het maken van gebruikers. Na succesvolle formuliervalidatie, verwijzen we door naar de inlogpagina.

Zorg ervoor dat u het formulier importeert:

from django.contrib.auth.forms import AuthenticationForm, UserCreationForm

Update example_channels/example/urls.py nogmaals:

from django.conf.urls import url
from example.views import log_in, log_out, sign_up, user_list


urlpatterns = [
    url(r'^log_in/$', log_in, name='log_in'),
    url(r'^log_out/$', log_out, name='log_out'),
    url(r'^sign_up/$', sign_up, name='sign_up'),
    url(r'^$', user_list, name='user_list')
]

Op dit punt moeten we een gebruiker maken. Voer de server uit en ga naar http://localhost:8000/sign_up/ in uw browser. Vul het formulier in met een geldige gebruikersnaam en wachtwoord en verzend het om onze eerste gebruiker aan te maken.

OPMERKING: Probeer michael . te gebruiken als de gebruikersnaam en johnson123 als het wachtwoord.

De sign_up view leidt ons door naar de log_in bekijken, en van daaruit kunnen we onze nieuw aangemaakte gebruiker authenticeren.

Nadat we zijn ingelogd, kunnen we onze nieuwe authenticatieweergaven testen.

Gebruik het aanmeldingsformulier om verschillende nieuwe gebruikers aan te maken ter voorbereiding op het volgende gedeelte.



Inlogmeldingen

We hebben basisgebruikersauthenticatie werkend, maar we moeten nog steeds een lijst met gebruikers weergeven en we hebben de server nodig om de groep te vertellen wanneer een gebruiker in- en uitlogt. We moeten onze consumentenfuncties aanpassen zodat ze een bericht sturen direct na een client maakt verbinding en vlak voordat een client de verbinding verbreekt. De berichtgegevens bevatten de gebruikersnaam en verbindingsstatus van de gebruiker.

Update example_channels/example/consumers.py zoals zo:

import json
from channels import Group
from channels.auth import channel_session_user, channel_session_user_from_http


@channel_session_user_from_http
def ws_connect(message):
    Group('users').add(message.reply_channel)
    Group('users').send({
        'text': json.dumps({
            'username': message.user.username,
            'is_logged_in': True
        })
    })


@channel_session_user
def ws_disconnect(message):
    Group('users').send({
        'text': json.dumps({
            'username': message.user.username,
            'is_logged_in': False
        })
    })
    Group('users').discard(message.reply_channel)

Merk op dat we decorateurs aan de functies hebben toegevoegd om de gebruiker uit de Django-sessie te halen. Alle berichten moeten ook JSON-serializeerbaar zijn, dus we dumpen onze gegevens in een JSON-tekenreeks.

Update vervolgens example_channels/example/templates/example/user_list.html :

{% extends 'example/_base.html' %}

{% block content %}
  <a href="{% url 'example:log_out' %}">Log out</a>
  <br>
  <ul>
    {% for user in users %}
      <!-- NOTE: We escape HTML to prevent XSS attacks. -->
      <li data-username="{{ user.username|escape }}">
        {{ user.username|escape }}: {{ user.status|default:'Offline' }}
      </li>
    {% endfor %}
  </ul>
{% endblock content %}

{% block script %}
  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');

    socket.onopen = function open() {
      console.log('WebSockets connection created.');
    };

    socket.onmessage = function message(event) {
      var data = JSON.parse(event.data);
      // NOTE: We escape JavaScript to prevent XSS attacks.
      var username = encodeURI(data['username']);
      var user = $('li').filter(function () {
        return $(this).data('username') == username;
      });

      if (data['is_logged_in']) {
        user.html(username + ': Online');
      }
      else {
        user.html(username + ': Offline');
      }
    };

    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
{% endblock script %}

Op onze homepage breiden we onze gebruikerslijst uit om een ​​lijst met gebruikers weer te geven. We slaan de gebruikersnaam van elke gebruiker op als een data-attribuut om het gemakkelijk te maken om het gebruikersitem in de DOM te vinden. We voegen ook een gebeurtenislistener toe aan onze WebSocket die berichten van de server kan verwerken. Wanneer we een bericht ontvangen, ontleden we de JSON-gegevens, vinden de <li> element voor de gegeven gebruiker, en update de status van die gebruiker.

Django houdt niet bij of een gebruiker is ingelogd, dus we moeten een eenvoudig model maken om dat voor ons te doen. Maak een LoggedInUser model met een één-op-één verbinding met onze User model in example_channels/example/models.py :

from django.conf import settings
from django.db import models


class LoggedInUser(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, related_name='logged_in_user')

Onze app maakt een LoggedInUser instantie wanneer een gebruiker inlogt, en de app verwijdert de instantie wanneer de gebruiker uitlogt.

Voer de schemamigratie uit en migreer vervolgens onze database om de wijzigingen toe te passen.

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

Werk vervolgens onze gebruikerslijstweergave bij, inexample_channels/example/views.py , om een ​​lijst met gebruikers op te halen die moeten worden weergegeven:

from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect


User = get_user_model()


@login_required(login_url='/log_in/')
def user_list(request):
    """
    NOTE: This is fine for demonstration purposes, but this should be
    refactored before we deploy this app to production.
    Imagine how 100,000 users logging in and out of our app would affect
    the performance of this code!
    """
    users = User.objects.select_related('logged_in_user')
    for user in users:
        user.status = 'Online' if hasattr(user, 'logged_in_user') else 'Offline'
    return render(request, 'example/user_list.html', {'users': users})


def log_in(request):
    form = AuthenticationForm()
    if request.method == 'POST':
        form = AuthenticationForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            return redirect(reverse('example:user_list'))
        else:
            print(form.errors)
    return render(request, 'example/log_in.html', {'form': form})


@login_required(login_url='/log_in/')
def log_out(request):
    logout(request)
    return redirect(reverse('example:log_in'))


def sign_up(request):
    form = UserCreationForm()
    if request.method == 'POST':
        form = UserCreationForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('example:log_in'))
        else:
            print(form.errors)
    return render(request, 'example/sign_up.html', {'form': form})

Als een gebruiker een bijbehorende LoggedInUser . heeft , dan registreren we de status van de gebruiker als "Online", en zo niet, dan is de gebruiker "Offline". We voegen ook een @login_required . toe decorateur naar zowel onze gebruikerslijst als uitlogweergaven om de toegang alleen te beperken tot geregistreerde gebruikers.

Voeg ook de invoer toe:

from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.decorators import login_required

Op dit punt kunnen gebruikers in- en uitloggen, waardoor de server berichten naar de client stuurt, maar we hebben geen manier om te weten welke gebruikers zijn ingelogd wanneer de gebruiker voor het eerst inlogt. De gebruiker ziet alleen updates wanneer de statuswijzigingen. Dit is waar de LoggedInUser komt in het spel, maar we hebben een manier nodig om een ​​LoggedInUser te maken bijvoorbeeld wanneer een gebruiker inlogt, en verwijder deze vervolgens wanneer die gebruiker uitlogt.

De Django-bibliotheek bevat een functie die signalen wordt genoemd en die meldingen uitzendt wanneer bepaalde acties plaatsvinden. Applicaties kunnen naar die meldingen luisteren en er vervolgens naar handelen. We kunnen gebruik maken van twee handige, ingebouwde signalen (user_logged_in en user_logged_out ) om onze LoggedInUser . af te handelen gedrag.

Voeg binnen "example_channels/example" een nieuw bestand toe met de naam signals.py :

from django.contrib.auth import user_logged_in, user_logged_out
from django.dispatch import receiver
from example.models import LoggedInUser


@receiver(user_logged_in)
def on_user_login(sender, **kwargs):
    LoggedInUser.objects.get_or_create(user=kwargs.get('user'))


@receiver(user_logged_out)
def on_user_logout(sender, **kwargs):
    LoggedInUser.objects.filter(user=kwargs.get('user')).delete()

We moeten de signalen beschikbaar maken in onze app-configuratie, example_channels/example/apps.py :

from django.apps import AppConfig


class ExampleConfig(AppConfig):
    name = 'example'

    def ready(self):
        import example.signals

Update example_channels/example/__init__.py ook:

default_app_config = 'example.apps.ExampleConfig'


Gezondheidscontrole

Nu zijn we klaar met coderen en zijn we klaar om verbinding te maken met onze server met meerdere gebruikers om onze app te testen.

Start de Django-server, log in als gebruiker en bezoek de startpagina. We zouden een lijst moeten zien van alle gebruikers in onze app, elk met de status "Offline". Open vervolgens een nieuw incognitovenster en log in als een andere gebruiker en bekijk beide schermen. Meteen wanneer we inloggen, werkt de reguliere browser de gebruikersstatus bij naar "Online". Vanuit ons incognitovenster zien we dat de ingelogde gebruiker ook de status "Online" heeft. We kunnen de WebSockets testen door in en uit te loggen op onze verschillende apparaten met verschillende gebruikers.

Als we de ontwikkelaarsconsole op de client en de serveractiviteit in onze terminal observeren, kunnen we bevestigen dat WebSocket-verbindingen worden gevormd wanneer een gebruiker inlogt en vernietigd wanneer een gebruiker uitlogt.

[2017/02/20 00:15:23] HTTP POST /log_in/ 302 [0.07, 127.0.0.1:55393]
[2017/02/20 00:15:23] HTTP GET / 200 [0.04, 127.0.0.1:55393]
[2017/02/20 00:15:23] WebSocket HANDSHAKING /users/ [127.0.0.1:55414]
[2017/02/20 00:15:23] WebSocket CONNECT /users/ [127.0.0.1:55414]
[2017/02/20 00:15:25] HTTP GET /log_out/ 302 [0.01, 127.0.0.1:55393]
[2017/02/20 00:15:26] HTTP GET /log_in/ 200 [0.02, 127.0.0.1:55393]
[2017/02/20 00:15:26] WebSocket DISCONNECT /users/ [127.0.0.1:55414]

OPMERKING :U kunt ook ngrok gebruiken om de lokale server veilig op internet te zetten. Als je dit doet, kun je de lokale server bereiken vanaf verschillende apparaten, zoals je telefoon of tablet.



Afsluitende gedachten

We hebben veel behandeld in deze tutorial:Django-kanalen, WebSockets, gebruikersauthenticatie, signalen en wat front-end ontwikkeling. Het belangrijkste is dit:Channels breidt de functionaliteit van een traditionele Django-app uit door ons via WebSockets berichten van de server naar groepen gebruikers te laten pushen.

Dit is krachtig spul!

Denk aan enkele van de toepassingen. We kunnen chatrooms, multiplayer-games en samenwerkings-apps maken waarmee gebruikers in realtime kunnen communiceren. Zelfs alledaagse taken worden verbeterd met WebSockets. In plaats van bijvoorbeeld de server periodiek te pollen om te zien of een langlopende taak is voltooid, kan de server een statusupdate naar de client sturen wanneer deze is voltooid.

Deze tutorial geeft slechts een indruk van wat we ook met Django-kanalen kunnen doen. Verken de documentatie van Django Channels en kijk wat je nog meer kunt maken.

Gratis bonus: Klik hier om toegang te krijgen tot een gratis Django Learning Resources Guide (PDF) met tips en trucs en veelvoorkomende valkuilen die je moet vermijden bij het bouwen van Python + Django-webapplicaties.

Pak de laatste code uit de django-voorbeeld-kanalen repo. Proost!



  1. Een inleiding tot de Java Security API

  2. Oracle SQL-clausule evaluatievolgorde

  3. Hoe u de op één na grootste of de op twee na grootste invoer van een tafel kunt krijgen?

  4. Wat is het verschil tussen RANK en DENSE_RANK in SQL?