User Registrierung

Bisher mussten wir als Administrator die User anlegen. Das ist natürlich für den Produktiv-Betrieb wie einem Online-Shop ungünstig. Es ist seit vielen Jahren Gang und Gebe, dass man sich auf einer Website selber registrieren kann. https://python.plainenglish.io/introduction-to-login-and-logout-in-django-85e6b71cbfca

DSGVO-konformes Registrierungsformular

An User, die sich auf der Website registrieren, sollte das System auf alle Fälle eine Bestätigungs-Email gesendet werden, um zu überprüfen, ob sich auch wirklich der User mit dieser Email angemeldet hat. Das geht technisch gesehen mit einem Token, der bei Erstellung des Users gespeichert wird und dann mit der Bestätigungsemail abgeglichen wird. Stimmen die beiden Token überein, wird der User „aktiviert“, dh. seit is_active-Status wird auf True gesetzt.

Da diese Überprüfung den Rahmen dieses Buches sprengen würde, haben wir an dieser Stelle darauf verzichtet, diese aufwändige Überprüfung zu implementieren.

User Registrierungs URL

für die Registrierung eines Users am System, also dem signup gibt es von Django keine Default Möglichkeiten, wie das noch bei Login und Passwort ändern der Fall war. Wir müssen hier jetzt alles selber implementieren.

Zuerst einmal passen wir die URLs unter event_manager/event_manager/urls.py an. Da wir unsere Registieruns-URL auch unter accounts laufen lassen wollen, dafür aber später unsere eigene User-App ansprechen müssen, legen wir einen zweiten Eintrag mit path('accounts/') an.

urlpatterns = [
    # [..]
    path("accounts/", include("user.urls")),
    path("accounts/", include("django.contrib.auth.urls")),
    # [..]
]

Auf den ersten Blick sieht das ungewöhnlich aus, ist aber legales Vorgehen im URL-Design. Während also http://127.0.0.1/accounts/login in der Auth-App von Django zu finden ist, werden wir http://127.0.0.1/accounts/signup in der User-App implementieren.

Die URLs für die User-App

in den Projekt-URLs unter event_manager/event_manager/urls.py haben wir schon auf unsere User-App referenziert. Aus diesem Grund legen wir die Datei jetzt mal unter event_manager/user/urls.py an und fügen folgenden Inhalt ein:

from django.urls import path
from .views import SignUpView

app_name = "user"

urlpatterns = [
     path("signup/", SignUpView.as_view(), name="signup"),
]

Unser Registierungsformular soll also unter http://127.0.0.1/accounts/signup aufrufbar sein. die SignUpView gibt es bisher noch nicht, die müssen wir im folgenden anlegen. Bevor wir das tun, müssen wir aber noch das Formular selbst definieren.Bisher hatten wir ja forms.ModelForm gentutz, da unsere Formulare immer auf dem Model selbst aufgebaut haben.

Das Registrierungs-Formular

Das Anlegen eines Users über ein Formular ist so eine Standard-Aufgabe, dass es dafür sogar ein Built-In Formular gibt, das UserCreationForm. Wir bräuchten also kein eigenes Formular für diese Aufgabe anlegen.

Wir wollen das Standard Formular aber um ein Feld erweitern. Und zwar wollen wir eine Checkbox einbauen, die angegklickt werden muss, um die Datenschutzbestimmungen zu bestätigen. Wird diese Checkbox vom User nicht angeglickt, wird ein Validation-Fehler erhoben und der Registrierungsprozess nicht abgeschossen. Dazu legen wir eine neue Forms-Datei in der User-App an: event_manager/user/forms.py.

So sieht das fertige Formular unter event_manager/user/forms.py aus:

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError

User = get_user_model()


class SignUpForm(UserCreationForm):
    class Meta:
        model = User
        fields = (
            "username",
            "email",
            "password1",
            "password2",
        )
        labels = {"privacy": "Datenschutzerklärung"}

    privacy = forms.BooleanField(required=False)

    def clean_privacy(self):
        """Die Datenschutzerklärung muss angeglickt werden, ansonsten wird ein
        ValidationError ausgelöst"""

        privacy = self.cleaned_data["privacy"]
        if not privacy:
            raise ValidationError(
                """Bitte bestätigen Sie, dass Sie die
                Datenschutzerklärung gelesen haben!"""
            )
        return privacy

Die Views für die User-App

Nun fehlt neben dem Template noch die View, in der wir das Formular an den User durchreichen. Legen wir jetzt unter event_manager/user/views.py die Views für die User-App an und fügen folgenden Inhalt ein:

from django.urls import reverse_lazy
from django.views import generic
from .forms import SignUpForm


class SignUpView(generic.CreateView):
    form_class = SignUpForm
    success_url = reverse_lazy("login")
    template_name = "registration/signup.html"

Was passiert hier? Wir importieren neben den generischen Views auch noch das User-Registierungsformular aus der eben erstellten forms.py. Dann erstellen wir eine class-based SignUpView, die von generic.CreateView erbt. Bei der CreateView handelt es sich um eine generische View, die für das Anlegen von Usern genutzt werden kann. Wir passen sie nur unseren Ansprüchen etwas an. Das heisst, nach erfolgreicher Registierungen werden wir an das Login-Formular weitergeleteitet (success_url) und unser Template soll das signup.html sein, dass wir noch anlegen müssen.

Fast fertig, es fehlt nur noch das Template, dann kann sich der erste User bei uns registrieren!

Das Template für das Registrierungs-Formular

Das Registrierungs-Template lebe wie die anderen Templates der Authentifizierungsaktionen ebenfalls unter event_manager/templates. Leben wir dort eine neue Datei namens signup.html an und füllen sie mit diesem Inhalt.

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block head%}
Restriere dich jetzt!
{% endblock %}

{% block content %}
<form method="post">
  {% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">Registrieren!</button>
</form>
{% endblock %}

Die Registrierung testen

Falls Du eingeloggt bist, logge dich jetzt aus und klicke dann auf den Registrierungslink. Du solltest an http://127.0.0.1/accounts/signup weitergeleitet werden. Führe den Registrierungsvorgang aus und logge dich mit dem neu erstellten User ein.

Der Schönheitsfehler von vorhin

Wir hatten ja festgestellt, dass wir uns, obwohl wir eingeloggt sind, auf die Login-URL unter http://127.0.0.1/accounts/login begeben können. Das heisst, wir können uns einloggen, obwohl wir schon eingeloggt sind. Das ist verwirrend, und das wollen wir ändern.

Dazu müssen wir die event_manager/user/urls.py Datei anpassen. Die Default-Login-Route der Auth-App verhindert nicht, dass wir sie aufrufen können, obwohl wir eingeloggt sind. Deshalb überschreiben wir die Login-Route wie folgt:

from django.contrib.auth import views as auth_views
from django.urls import path

from .views import SignUpView

app_name = "user"

urlpatterns = [
    # diese Route überschreiben wir
    path(
        "login/",
        auth_views.LoginView.as_view(redirect_authenticated_user=True),
        name="login",
    ),
    path("signup/", SignUpView.as_view(), name="signup"),
]

Die auth_views.LoginView ist die Standard-Login-View von Django. Per default ist hier das Attribut redirect_authenticated_user auf False gesetzt, d.h. auch eingeloggte User werden auf das Login-Formular weitergeleitet. Das verhindern wir, indem wir den Wert auf True setzen.