Django a Ajax:Robustní autentizační a autorizační systém s ověřováním formulářů v reálném čase pro webové aplikace - 4

V poslední části jsme zahájili budování registračního systému studentů. Zastavili jsme se u bodu vytváření některých dalších souborů, tasks.py a tokens.py . V této části budeme pokračovat v implementaci.

Zdrojový kód

Zdrojový kód je k tomuto bodu hostován na githubu, zatímco zdrojový kód celé aplikace je:

django_real_time_validation

Django a Ajax:Robustní systém ověřování a autorizace s ověřováním formulářů pro webové aplikace v reálném čase


Zobrazit na GitHubu

Krok 7:tokens.py a tasks.py soubory

Při uzavírání 3. části této série jsme vytvořili tokens.py a tasks.py soubory. Zatímco první se zabývá vytvářením jedinečných tokenů pro ověřování uživatelů, druhá obsahuje logiku pro odesílání e-mailů prostřednictvím celery . V tomto projektu celery , distribuovaná fronta úloh, zpracovává všechny úlohy na pozadí, které zahrnují odesílání e-mailů. Tímto splníme tento segment požadavků:

Obsah tokens.py je docela jednoduché:

# accounts > tokens.py

from django.contrib.auth.tokens import PasswordResetTokenGenerator

from six import text_type


class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
    def _make_hash_value(self, user, timestamp):
        return (
            text_type(user.pk)
            + text_type(timestamp)
            + text_type(user.is_student)
            + text_type(user.is_lecturer)
        )


account_activation_token = AccountActivationTokenGenerator()

V podstatě zdědíme django PasswordResetTokenGenerator a poté hašování na základě id uživatele (v našem případě UUID), času a dalších specifických uživatelských atributů. Je to poměrně bezpečné a jedinečné! To pak přiřadíme account_activation_token které jsme později nazvali v našem student_signup funkce.

Chcete-li implementovat tasks.py , musíme nainstalovat celery s Redis backend. Ujistěte se, že máte plně funkční nastavení pro redis.

Ve virtuálním prostředí pro tento projekt nainstalujte buď pomocí pip nebo pipenv (pokud používáte pipenv od začátku) a nastavte jej:

┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ pipenv install "celery[redis]"

Poté vytvořte celery.py soubor v adresáři vašeho projektu. Mělo by to být v adresáři jako settings.py vašeho projektu soubor.

┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$ touch authentication/celery.py

a naplňte jej:

# authentication > celery.py
import os

from celery import Celery

# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentication.settings")

app = Celery("authentication")

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
#   should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()


@app.task(bind=True)
def debug_task(self):
    print(f"Request: {self.request!r}")

Toto bylo zkopírováno z použití celeru s django s drobnými úpravami vložením názvu mé aplikace do řádků 6 a 8.

Chcete-li zajistit, aby se aplikace načetla při spuštění Django, aby ji mohl použít dekoratér @shared_task, importujte tuto aplikaci do svého project_name/__init__.py :

# authentication > __init__.py
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ("celery_app",)

Nyní k tasks.py :

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.template.loader import render_to_string
from django.utils.html import strip_tags

from celery import shared_task


@shared_task
def send_email_message(subject, template_name, user_id, ctx):
    html_message = render_to_string(template_name, ctx)
    plain_message = strip_tags(html_message)
    mail.send_mail(
        subject=subject,
        message=plain_message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=[get_user_model().objects.get(id=user_id).email],
        fail_silently=False,
        html_message=html_message,
    )

Je to jednoduchá funkce zdobená celerovým shared_task . Používá django mail k odeslání zpráv. Je velmi důležité zajistit, abyste nepředávali objekt uživatele do úlohy celery. Předání pouze jednoho atributu uživatelského modelu, v tomto případě user_id , je řešení. Předávání objektů nebo instancí modelu vede ke společnému Object not serializable chyba. Abychom uzavřeli konfigurace, připojte se k settings.py tento úryvek:

CELERY_BROKER_URL = config("REDIS_URL", default="")
CELERY_RESULT_BACKEND = config("REDIS_URL", default="")
CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"

Vaše REDIS_URL je váš místní hostitel a port redis (ve tvaru redis://host:port ). Osvědčeným postupem je vložit toto do .env a nikdy jej nenahrávejte do GitHub zahrnutím cesty k souboru do .gitignore soubor, abyste jej nenahráli pro ostatní.

Krok 8:Znovu navštivte a připojte funkci registrace studentů k urls.py

Nyní, když jsou přípravné kroky vyřízeny, prozkoumejte student_signup funkce zobrazení napsaná v minulém díle. Nejprve jsme inicializovali StudentRegistrationForm a poté zkontroloval, že příchozí požadavek je POST . Pokud je pravda, vytvořili jsme kopii dat požadavku a následně načetli email , username a password uživatel zadal požadavek. Pokud email vyhovuje pravidlům vytvořeným v poslední části, vytvoří se uživatelská instance a poté otestujeme password uživatele a email proti jiným validacím. Pokud se škálují, vložíme do vytvořené instance další uživatelské parametry a přistoupíme k odeslání e-mailu uživateli k potvrzení. Všimněte si kontextu, který jsme předali úkolu s celerem:

...
ctx = {
    "fullname": user.get_full_name(),
    "domain": str(get_current_site(request)),
    "uid": urlsafe_base64_encode(force_bytes(user.pk)),
    "token": account_activation_token.make_token(user),
            }

Ujistěte se, že jste strigifikovali get_current_site(request) , pokud ne, narazíte na celery problém s nemožností serializovat request data.

Pokud heslo a uživatelské jméno uživatele neodpovídají našim pravidlům, bude takový uživatel smazán z databáze:get_user_model().objects.get(email=post_data.get("email")).delete() . Pojďme to nyní přidat k našemu urls.py soubor:

# accounts > urls.py
...
urlpatterns = [
   ...
    path("student-sign-up/", views.student_signup, name="student_signup"),
]

Potřebujeme také některé funkce, abychom informovali uživatele, že potřebují zkontrolovat svůj e-mail, a další pro aktivaci uživatele po kliknutí na odkaz:

# accounts > views.py
...
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
...

def activate(request, uidb64, token):
    try:
        uid = force_text(urlsafe_base64_decode(uidb64))
        user = get_user_model().objects.get(pk=uid)
    except (TypeError, ValueError, OverflowError):
        user = None
    # checking if the user exists, if the token is valid.
    if user is not None and account_activation_token.check_token(user, token):
        # if valid set active true
        user.is_active = True
        user.save()
        messages.success(
            request, f"Your email has been verified successfully! You are now able to log in."
        )
        return redirect("accounts:login")
    else:
        return render(request, "accounts/activation_invalid.html")


def activation_sent_view(request):
    return render(request, "accounts/activation_sent.html")

activate funkce používá hodnotu z uidb64 získat uživatele, kterému token patří, a poté před aktivací uživatele zkontroluje platnost tokenu:

# accounts > views.py
...
user.is_active = True
user.save()
...

Pojďme je zahrnout do našeho urls.py soubor:

# accounts > urls.py
...
urlpatterns = [
   ...
    path("sent/", views.activation_sent_view, name="activation_sent"),
    path("activate/<uidb64>/<token>/", views.activate, name="activate"),
]

Krok 9:Vytvoření přihlašovacích a dalších šablon

Abychom viděli, co jsme zatím udělali, vložme nějaké html a css. Vytvořte accounts/activation_sent.html (šablona oznámení odeslaná poštou), accounts/activation_invalid.html (neplatná šablona tokenu), accounts/student_signup.html (registrace studentů), accounts/activation_request.txt (pro textové e-maily) a accounts/activation_request.html (e-mail založený na html).

┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_real_time_validation]
└─$[sirneij@sirneij django_real_time_validation]$  touch templates/accounts/activation_sent.html templates/accounts/activation_invalid.html templates/accounts/student_signup.html templates/accounts/activation_request.txt templates/accounts/activation_request.html

activation_request.txt by měl vypadat takto:

<!--templates/accounts/activation_request.txt-->

{% autoescape off %}
Hi {{ fullname }},
    Thank you for joining us on this great platform.
    Please click the following button to confirm your registration...


    By the way, if the above button is not clickable, paste the following link in your browser.
    http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}


Django Authentication Webmaster
{% endautoescape %}

Udělejte activation_request.html se objeví následovně:

<!--templates/accounts/activation_request.html-->

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width" />
    <style>
      * {
        margin: 0;
        padding: 0;
        font-size: 100%;
        font-family: "Avenir Next", "Helvetica Neue", "Helvetica", Helvetica,
          Arial, sans-serif;
        line-height: 1.65;
      }

      img {
        max-width: 100%;
        margin: 0 auto;
        display: block;
      }

      body,
      .body-wrap {
        width: 100% !important;
        height: 100%;
        background: #f8f8f8;
      }

      a {
        color: #206bc4;
        text-decoration: none;
      }

      a:hover {
        text-decoration: underline;
      }

      .text-center {
        text-align: center;
      }

      .text-right {
        text-align: right;
      }

      .text-left {
        text-align: left;
      }

      .button {
        display: inline-block;
        color: #ffffff;
        background: #206bc4;
        border: solid #206bc4;
        border-width: 10px 20px 8px;
        font-weight: bold;
        border-radius: 4px;
      }

      .button:hover {
        text-decoration: none;
        color: #ffffff;
        background-color: #1b59a3;
        border-color: #195398;
      }

      h1,
      h2,
      h3,
      h4,
      h5,
      h6 {
        margin-bottom: 20px;
        line-height: 1.25;
      }

      h1 {
        font-size: 32px;
      }

      h2 {
        font-size: 28px;
      }

      h3 {
        font-size: 24px;
      }

      h4 {
        font-size: 20px;
      }

      h5 {
        font-size: 16px;
      }

      p,
      ul,
      ol {
        font-size: 16px;
        font-weight: normal;
        margin-bottom: 20px;
      }

      .container {
        display: block !important;
        clear: both !important;
        margin: 0 auto !important;
        max-width: 580px !important;
      }

      .container table {
        width: 100% !important;
        border-collapse: collapse;
      }

      .container .masthead {
        margin-top: 20px;
        padding: 80px 0;
        background: #206bc4;
        color: #ffffff;
      }

      .container .masthead h1 {
        margin: 0 auto !important;
        max-width: 90%;
        text-transform: uppercase;
      }

      .container .content {
        background: #ffffff;
        padding: 30px 35px;
      }

      .container .content.footer {
        background: none;
      }

      .container .content.footer p {
        margin-bottom: 0;
        color: #888;
        text-align: center;
        font-size: 14px;
      }

      .container .content.footer a {
        color: #888;
        text-decoration: none;
        font-weight: bold;
      }

      .container .content.footer a:hover {
        text-decoration: underline;
      }
    </style>
    <title>Verify your email address.</title>
  </head>

  <body>
    <!-- auto -->
    {% autoescape off %}
    <table class="body-wrap">
      <tr>
        <td class="container">
          <!-- Message start -->
          <table>
            <tr>
              <td align="center" class="masthead">
                <h1>Welcome to Django Authentication System...</h1>
              </td>
            </tr>
            <tr>
              <td class="content">
                <h2>
                  Hi
                  <strong style="text-transform: capitalize"
                    >{{ fullname }}</strong
                  >,
                </h2>

                <p>Thank you for joining us on this great platform.</p>

                <p>
                  Please click the following button to confirm your
                  registration...
                </p>

                <table>
                  <tr>
                    <td align="center">
                      <p>
                        <a
                          href="http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}"
                          class="button"
                          >Yes, I'm in!</a
                        >
                      </p>
                    </td>
                  </tr>
                </table>

                <p>
                  By the way, if the above button is not clickable, paste the
                  following link in your browser.
                  <!-- email link -->
    http://{{ domain }}{% url 'accounts:activate' uidb64=uid token=token %}
                </p>

                <p><em>– Django Authentication Webmaster</em></p>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      <tr>
        <td class="container">
          <!-- Message start -->
          <table>
            <tr>
              <td class="content footer" align="center">
                <p>
                  Sent by <a href="{{ domain }}">Django Authentication</a>,
                  Federal University of Technology, Akure, South Gate, Ondo
                  State, Nigeria.
                </p>
                <p>
                  <a href="mailto:[email protected]"
                    >[email protected]</a
                  >
                </p>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </table>
    <!-- end auto -->
    {% endautoescape %}
  </body>
</html>

Stačí jednoduchý html soubor. Zahrnuje některé osvědčené postupy pro e-maily ve formátu HTML.

activation_sent.html má toto:


<!--templates/accounts/activation_sent.html-->

{% extends 'base.html' %}
<!-- title -->
{% block title %} Verification email sent {% endblock title %}
<!-- static files -->
{% load static %}
<!-- content starts -->
{% block content %}

<div class="row center-content">
  <div class="col s12" style="max-width: 30rem">
    <div class="card blue-grey darken-1">
      <div class="card-content white-text">
        <span class="card-title">Thank you for creating an account!</span>
        <p>
          An email has been sent to the e-mail address you provided during
          registeration for confirmation.
        </p>
        <p>
          Make sure you visit the link provided in mail as it will soon be
          revoked.
        </p>
      </div>
    </div>
  </div>
</div>

<!-- content ends -->
{% endblock content %}

Pokud jde o activation_invalid.html , mělo by to být takto:

{% extends 'base.html' %}
<!-- title -->
{% block title %} Verification email failed {% endblock title %}
<!-- static files -->
{% load static %}
<!-- content starts -->
{% block content %}

<div class="row center-content">
  <div class="col s12" style="max-width: 30rem">
    <div class="card blue-grey darken-1">
      <div class="card-content white-text">
        <span class="card-title">Invalid activation link!!</span>
        <p>
          Oops! There were issues with the activation link, it was highly
          perceived to have been used before... Please, consider requesting for
          an
          <a
            href="{% url 'accounts:resend_email' %}"
            class="btn waves-effect waves-light"
          >
            activate link resend </a
          >.
        </p>
      </div>
    </div>
  </div>
</div>
<!-- content ends -->
{% endblock content %}

Řekněme tomu den. Příště budeme pokračovat odtud!

Outro

Všechno nejlepší k narozeninám 🎂✨🥳🤩.

Tento článek se vám líbil, zvažte, zda mě neoslovíte kvůli práci, něčemu, co stojí za to nebo si koupíte kávu ☕.