sql >> Database teknologi >  >> RDS >> Database

Kom godt i gang med Django-kanaler

I denne vejledning vil vi bruge Django Channels til at skabe en realtidsapplikation, der opdaterer en liste over brugere, når de logger ind og ud.

Med WebSockets (via Django Channels), der administrerer kommunikationen mellem klienten og serveren, vil en begivenhed blive udsendt til hver anden tilsluttet bruger, når en bruger er autentificeret. Hver brugers skærm ændres automatisk, uden at de skal genindlæse deres browsere.

BEMÆRK: Vi anbefaler, at du har lidt erfaring med Django, før du begynder denne vejledning. Du bør også være bekendt med konceptet WebSockets.

Gratis bonus: Klik her for at få adgang til en gratis Django Learning Resources Guide (PDF), der viser dig tips og tricks samt almindelige faldgruber, du skal undgå, når du bygger Python + Django-webapplikationer.

Vores applikation bruger:

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

Mål

Ved slutningen af ​​denne øvelse vil du være i stand til at...

  1. Tilføj web-sockets-understøttelse til et Django-projekt via Django-kanaler
  2. Opret en simpel forbindelse mellem Django og en Redis-server
  3. Implementer grundlæggende brugergodkendelse
  4. Udnyt Django-signaler til at handle, når en bruger logger ind eller ud


Kom godt i gang

Først skal du oprette et nyt virtuelt miljø for at isolere vores projekts afhængigheder:

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

Installer Django, Django Channels og ASGI Redis, og opret derefter et nyt Django-projekt og -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

BEMÆRK: I løbet af denne øvelse vil vi oprette en række forskellige filer og mapper. Se venligst mappestrukturen fra projektets repository, hvis du går i stå.

Derefter skal du downloade og installere Redis. Hvis du er på en Mac, anbefaler vi at bruge Homebrew:

$ brew install redis

Start Redis-serveren i et nyt terminalvindue, og sørg for, at den kører på standardporten, 6379. Portnummeret vil være vigtigt, når vi fortæller Django, hvordan man kommunikerer med Redis.

Fuldfør opsætningen ved at opdatere INSTALLED_APPS i projektets settings.py fil:

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

Konfigurer derefter CHANNEL_LAYERS ved at indstille en standard backend og routing:

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

Dette bruger en Redis-backend, som også er nødvendig i produktionen.



WebSockets 101

Normalt bruger Django HTTP til at kommunikere mellem klienten og serveren:

  1. Klienten sender en HTTP-anmodning til serveren.
  2. Django analyserer anmodningen, udtrækker en URL og matcher den derefter med en visning.
  3. Visningen behandler anmodningen og returnerer et HTTP-svar til klienten.

I modsætning til HTTP tillader WebSockets-protokollen tovejskommunikation, hvilket betyder, at serveren kan skubbe data til klienten uden at blive bedt om det af brugeren. Med HTTP er det kun den klient, der har lavet en anmodning, der modtager et svar. Med WebSockets kan serveren kommunikere med flere klienter samtidigt. Som vi vil se senere i denne vejledning, sender vi WebSockets-beskeder ved hjælp af ws:// præfiks, i modsætning til http:// .

BEMÆRK: Inden du dykker ind, skal du hurtigt gennemgå kanalkonceptdokumentationen.



Forbrugere og grupper

Lad os skabe vores første forbruger, som håndterer de grundlæggende forbindelser mellem klienten og serveren. Opret en ny fil kaldet 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)   

Forbrugerne er modstykket til Django synspunkter. Enhver bruger, der opretter forbindelse til vores app, vil blive føjet til "brugere"-gruppen og vil modtage beskeder sendt af serveren. Når klienten afbryder forbindelsen til vores app, fjernes kanalen fra gruppen, og brugeren stopper med at modtage beskeder.

Lad os derefter opsætte ruter, som fungerer på næsten samme måde som Django URL-konfiguration, ved at tilføje følgende kode til en ny fil kaldet 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),
]

Så vi definerede channel_routing i stedet for urlpatterns og route() i stedet for url() . Bemærk, at vi har knyttet vores forbrugerfunktioner til WebSockets.


Skabeloner

Lad os skrive noget HTML op, der kan kommunikere med vores server via en WebSocket. Opret en "skabeloner"-mappe i "eksempel", og tilføj derefter en "eksempel"-mappe i "skabeloner" - "eksempel_kanaler/eksempel/skabeloner/eksempel".

Tilføj en _base.html fil:

<!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>

Og 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 %}

Nu, når klienten med succes åbner en forbindelse med serveren ved hjælp af en WebSocket, vil vi se en bekræftelsesmeddelelse udskrevet til konsollen.



Visninger

Konfigurer en understøttende Django-visning for at gengive vores skabelon i example_channels/example/views.py :

from django.shortcuts import render


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

Tilføj webadressen til 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'),
]

Opdater også projektets URL i 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

Klar til at teste?

(env)$ python manage.py runserver

BEMÆRK: Du kan alternativt køre python manage.py runserver --noworker og python manage.py runworker i to forskellige terminaler for at teste grænsefladen og arbejdsservere som to separate processer. Begge metoder virker!

Når du besøger http://localhost:8000/, bør du se forbindelsesmeddelelsen udskrevet til terminalen:

[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]



Brugergodkendelse

Nu hvor vi har bevist, at vi kan åbne en forbindelse, er vores næste skridt at håndtere brugergodkendelse. Husk:Vi ønsker, at en bruger skal kunne logge ind på vores app og se en liste over alle de andre brugere, der abonnerer på denne brugers gruppe. For det første har vi brug for en måde, hvorpå brugere kan oprette konti og logge ind. Begynd med at oprette en simpel login-side, der giver en bruger mulighed for at godkende med et brugernavn og en adgangskode.

Opret en ny fil kaldet log_in.html i "eksempel_kanaler/eksempel/skabeloner/eksempel":

{% 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 %}

Opdater derefter example_channels/example/views.py sådan:

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 kommer med formularer, der understøtter almindelig godkendelsesfunktionalitet. Vi kan bruge AuthenticationForm til at håndtere brugerlogin. Denne formular kontrollerer det angivne brugernavn og kodeord og returnerer derefter en Bruger objekt, hvis en valideret bruger findes. Vi logger på den validerede bruger og omdirigerer dem til vores hjemmeside. En bruger skal også have mulighed for at logge ud af applikationen, så vi opretter en logout-visning, der giver denne funktionalitet og derefter fører brugeren tilbage til login-skærmen.

Opdater derefter 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')
]

Vi har også brug for en måde at skabe nye brugere på. Opret en tilmeldingsside på samme måde som login ved at tilføje en ny fil kaldet sign_up.html til "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 %}

Bemærk, at loginsiden har et link til tilmeldingssiden, og tilmeldingssiden har et link tilbage til login.

Tilføj følgende funktion til visningerne:

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})

Vi bruger en anden indbygget formular til brugeroprettelse. Efter vellykket formularvalidering omdirigerer vi til login-siden.

Sørg for at importere formularen:

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

Opdater example_channels/example/urls.py igen:

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')
]

På dette tidspunkt skal vi oprette en bruger. Kør serveren og besøg http://localhost:8000/sign_up/ i din browser. Udfyld formularen med et gyldigt brugernavn og adgangskode og indsend det for at oprette vores første bruger.

BEMÆRK: Prøv at bruge michael som brugernavn og johnson123 som adgangskode.

tilmelding view omdirigerer os til log_in visning, og derfra kan vi autentificere vores nyoprettede bruger.

Når vi har logget ind, kan vi teste vores nye godkendelsesvisninger.

Brug tilmeldingsformularen til at oprette flere nye brugere som forberedelse til næste afsnit.



Loginadvarsler

Vi har grundlæggende brugergodkendelse, der fungerer, men vi skal stadig vise en liste over brugere, og vi skal bruge serveren til at fortælle gruppen, hvornår en bruger logger ind og ud. Vi skal redigere vores forbrugerfunktioner, så de sender en besked lige efter en klient forbinder og lige før en klient afbryder forbindelsen. Beskeddataene vil omfatte brugerens brugernavn og forbindelsesstatus.

Opdater example_channels/example/consumers.py sådan:

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)

Bemærk, at vi har tilføjet dekoratører til funktionerne for at få brugeren fra Django-sessionen. Alle meddelelser skal også være JSON-serialiserbare, så vi dumper vores data i en JSON-streng.

Opdater derefter 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 %}

På vores hjemmeside udvider vi vores brugerliste for at vise en liste over brugere. Vi gemmer hver brugers brugernavn som en dataattribut for at gøre det nemt at finde brugerelementet i DOM. Vi tilføjer også en event listener til vores WebSocket, der kan håndtere beskeder fra serveren. Når vi modtager en besked, analyserer vi JSON-dataene, finder

  • element for den givne bruger, og opdatere denne brugers status.

    Django sporer ikke, om en bruger er logget ind, så vi skal lave en simpel model til at gøre det for os. Opret en LoggedInUser model med en en-til-en forbindelse til vores Bruger model i 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')
    

    Vores app opretter en LoggedInUser forekomst, når en bruger logger på, og appen sletter forekomsten, når brugeren logger ud.

    Foretag skemamigreringen, og migrér derefter vores database for at anvende ændringerne.

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

    Opdater derefter vores brugerlistevisning iexample_channels/example/views.py , for at hente en liste over brugere, der skal gengives:

    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})
    

    Hvis en bruger har en tilknyttet LoggedInUser , så registrerer vi brugerens status som "Online", og hvis ikke, er brugeren "Offline". Vi tilføjer også en @login_required dekorator til både vores brugerliste og log ud visninger for kun at begrænse adgangen til registrerede brugere.

    Tilføj også importerne:

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

    På dette tidspunkt kan brugere logge ind og ud, hvilket vil udløse serveren til at sende beskeder til klienten, men vi har ingen mulighed for at vide, hvilke brugere der er logget ind, når brugeren logger på første gang. Brugeren ser kun opdateringer, når en anden brugers statusændringer. Det er her LoggedInUser kommer i spil, men vi har brug for en måde at oprette en LoggedInUser på forekomst, når en bruger logger på, og slet det derefter, når denne bruger logger ud.

    Django-biblioteket indeholder en funktion kendt som signaler, der udsender meddelelser, når visse handlinger forekommer. Applikationer kan lytte efter disse meddelelser og derefter reagere på dem. Vi kan udnytte to nyttige, indbyggede signaler (user_logged_in og user_logged_out ) for at håndtere vores LoggedInUser adfærd.

    Inden for "example_channels/example" skal du tilføje en ny fil kaldet 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()
    

    Vi er nødt til at gøre signalerne tilgængelige i vores app-konfiguration, example_channels/example/apps.py :

    from django.apps import AppConfig
    
    
    class ExampleConfig(AppConfig):
        name = 'example'
    
        def ready(self):
            import example.signals
    

    Opdater example_channels/example/__init__.py også:

    default_app_config = 'example.apps.ExampleConfig'
    


    Sanity Check

    Nu er vi færdige med at kode og er klar til at oprette forbindelse til vores server med flere brugere for at teste vores app.

    Kør Django-serveren, log ind som bruger og besøg hjemmesiden. Vi skulle se en liste over alle brugerne i vores app, hver med statussen "Offline". Åbn derefter et nyt inkognitovindue og log ind som en anden bruger og se begge skærme. Lige når vi logger ind, opdaterer den almindelige browser brugerstatus til "Online". Fra vores inkognitovindue ser vi, at den bruger, der er logget på, også har status som "Online". Vi kan teste WebSockets ved at logge ind og ud på vores forskellige enheder med forskellige brugere.

    Ved at observere udviklerkonsollen på klienten og serveraktiviteten i vores terminal kan vi bekræfte, at WebSocket-forbindelser dannes, når en bruger logger på, og ødelægges, når en bruger logger ud.

    [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]
    

    BEMÆRK :Du kan også bruge ngrok til at eksponere den lokale server til internettet sikkert. Hvis du gør dette, kan du ramme den lokale server fra forskellige enheder, såsom din telefon eller tablet.



    Afsluttende tanker

    Vi dækkede meget i denne tutorial - Django-kanaler, WebSockets, brugergodkendelse, signaler og noget frontend-udvikling. Det vigtigste er dette:Channels udvider funktionaliteten af ​​en traditionel Django-app ved at lade os pushe beskeder fra serveren til grupper af brugere via WebSockets.

    Det her er kraftfulde ting!

    Tænk på nogle af applikationerne. Vi kan oprette chatrum, multiplayer-spil og samarbejdsapps, der giver brugerne mulighed for at kommunikere i realtid. Selv hverdagslige opgaver forbedres med WebSockets. For eksempel, i stedet for periodisk at spørge serveren for at se, om en langvarig opgave er fuldført, kan serveren sende en statusopdatering til klienten, når den er færdig.

    Denne tutorial ridser bare overfladen af, hvad vi også kan gøre med Django Channels. Udforsk Django Channels-dokumentationen og se, hvad du ellers kan oprette.

    Gratis bonus: Klik her for at få adgang til en gratis Django Learning Resources Guide (PDF), der viser dig tips og tricks samt almindelige faldgruber, du skal undgå, når du bygger Python + Django-webapplikationer.

    Få fat i den endelige kode fra django-example-channels repo. Skål!



    1. Sådan opsummeres tidsfeltet i SQL Server

    2. Sådan fungerer age() i PostgreSQL

    3. Cast fra VARCHAR til INT - MySQL

    4. Hvornår er det bedre at gemme flag som en bitmaske i stedet for at bruge en associativ tabel?