MyGear - Login, Logout, Registrieren und Passwort Reset
Nachdem wir nun die Möglichkeit haben, User anzulegen, müssen die sich auch neu einloggen, ausloggen, registrieren und ihr Passwort reseten können. Damit sie das können und wir Platz für die Links / Menüpunkte auf der Seite haben, integrieren wir erst mal einen Header auf der Startseite.
In den body-Teil meiner home.html kommt folgendes:
<div class="head">
{% include "header.html" %}
</div>
<div class="main">
{% block login %}
{% endblock %}
{% block content %}
<h1>Willkommen bei MyGear</h1>
{% endblock %}
</div>
Mit der div class bereite ich gleich die Formatierungen per CSS vor. include ruft eine Datei header.html auf, die gleich noch erstellt wird. Und block bereitet Bereiche vor, die bei Bedarf eingeblendet werden.
Die header.html bekommt erst mal ein Grundgerüst:
<div class="header">
<div class="menubar">
<p><a href="{% url 'home' %}">MyGear</a></p>
</div>
<div class="user">
{% if user.is_authenticated %}
{{ user.email }}
{% else %}
<p><a href="{% url 'login' %}">Login / Register</a></p>
{% endif %}
</div>
</div>
Der Teil {% url 'home' %} ersetzt eine feste Link-Codierung. Der Link darauf ruft einfach den definierten View home auf, den wir mit der home.html verknüpft haben. Im weiteren wird abgefragt, ob ein User eingeloggt ist, dann wird der Login-Name, hier die eMail, angezeigt. mit user.get_full_name würde z.B. dann der volle Name angezeigt werden.
Was machen wir aber nun, wenn der Name nur einen Vornamen oder einen Nachnamen hat? Oder wenn gar keiner eingetragen ist, weil es optional ist? In home/views.py ergänze ich den View:
def home_view(request):
if request.user.is_authenticated:
if request.user.first_name=="":
display_name=request.user.last_name
if request.user.last_name=="":
display_name=request.user.email
else:
display_name=request.user.get_full_name
else:
display_name=""
context={'user':request.user,'display_name':display_name}
return render(request, 'home.html', context=context)
Und aus {{ user.email }} oben wird {{ display_name }}.
Nun denn, die Vorbereitungen sind getroffen. Dann wollen wir uns einloggen. In der urls.py des Projektes füge ich bei den urlpatterns zusätzlich path('accounts/', include('accounts.urls')), ein. Im Ordner der App accounts lege ich eine urls.py an und befülle sie mit
from django.urls import path, include
from .views import mylogin
urlpatterns = [
path('login/', mylogin, name='login'),
]
Und in der views.py lege ich den Login-View an:
def mylogin(request):
return render(request, 'login.html')
Im Ordner accounts/templates lege ich die login.html an.
{% extends 'home.html' %}
{% block title %}Login{% endblock %}
{% block login %}
{% if not request.user.is_authenticated %}
<h2>Login to MyGear</h2>
{% if form.errors %}
<p class="error">Invalid username or password.</p>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>
<p>You don't have an account? <a href="">register here.</a></p>
<p><a href="">Forgot your password?</a></p>
<p><a href="{% url 'home' %}">Back to Home</a></p>
{% endif %}
{% endblock %}
Diese baut auf die home.html auf und wird nur aufgerufen, wenn kein User eingeloggt ist. Aktuell fehlt noch das Formular auf der Seite. Dafür erstellen wir in accounts eine Datei forms.py.
from django import forms
class LoginForm(forms.Form):
email=forms.EmailField()
password=forms.CharField(widget=forms.PasswordInput)
In der views.py muss ich jetzt meine login-Funktion ändern. Zum einen, um das Formular anzuzeigen mit if request.method=='GET': und um den User einzuloggen mit dem Abschnitt elif request.method=='POST':.
def mylogin(request):
if request.method=='GET':
form=LoginForm()
return render(request, 'login.html', {'form':form})
elif request.method=='POST':
form=LoginForm(request.POST)
if form.is_valid():
email=form.cleaned_data['email']
password=form.cleaned_data['password']
user=authenticate(request,email=email,password=password)
if user:
login(request,user)
return redirect('home')
return render(request, 'login.html', {'form':form})
Weiter geht es mit dem Logout. Im Prinzip ergänze ich die gleichen Dateien, wie beim Login. Hier nur grob das Gerüst ohne viele Wiederholungen von oben.
urls.py
from django.urls import path, include
from .views import mylogin, mylogout
urlpatterns = [
path('login/', mylogin, name='login'),
path('logout/', mylogout, name='logout'),
]
views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
def mylogout(request):
if request.method=='POST':
logout(request)
return redirect('home')
Und in der header.html noch den Logout-Button:
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<button type="submit" class="btn-link">Logout</button>
</form>
Jetzt wird es wieder schwieriger. User sollen sich neu registrieren können. Dafür brauche ich ein Registrierungsformular.
Die forms.py mit dem Login-Formular ergänze ich um das Registrierungsformular:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import MyUser
class LoginForm(forms.Form):
email=forms.EmailField()
password=forms.CharField(widget=forms.PasswordInput)
class RegisterForm(UserCreationForm):
email=forms.EmailField(required=True)
required_css_class = 'required'
class Meta:
model=MyUser
fields=['email', 'first_name', 'last_name', 'password1','password2']
In der urls.py lege ich den Pfad an:
from django.urls import path, include
from .views import mylogin, mylogout, signup
urlpatterns = [
path('login/', mylogin, name='login'),
path('logout/', mylogout, name='logout'),
path("signup/", signup, name="signup"),
]
In der views.py die Funktion für die Registrierung:
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from .forms import LoginForm, RegisterForm
def signup(request):
if request.method=="POST":
form=RegisterForm(request.POST)
if form.is_valid():
user=form.save()
login(request,user)
return render(request, 'home.html', {'user':request.user})
else:
return render(request, 'signup.html', {'form':form})
else:
form=RegisterForm()
return render(request, 'signup.html', {'form':form})
Und in den templates lege ich die signup.html an.
{% extends 'home.html' %}
{% block title %}Signup{% endblock %}
{% block login %}
<h2>Sign up</h2>
<form method="post">
{% csrf_token %}
{% for field in form %}
<p>
{{ field.label_tag }} {{ field }}
{% if field.errors %}
<ul class="errorlist">
{% for error in field.errors %}
<p>{{ error }}</p>
{% endfor %}
</ul>
{% endif %}
</p>
{% endfor %}
<ul>
<li>Your password can’t be too similar to your other personal information.</li>
<li>Your password must contain at least 8 characters.</li>
<li>Your password can’t be a commonly used password.</li>
<li>Your password can’t be entirely numeric.</li>
</ul>
<button type="submit">Sign Up</button>
</form>
<p>If you already have an account, please <a href="{% url 'login' %}">Log In</a>. <a href="{% url 'home' %}">Back to Home</a></p>
{% endblock %}
Die letzte nächste fehlende Funktion ist jetzt das ändern des Passworts. Um ein Passwort zu reseten, muss eine eMail verschickt werden. Das emuliere ich erst ein mal in der Konsole. In der settings.py wird eingetragen:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
Hier verwende ich die Systemfunktionen von Django, um das Passwort zu ändern. In der accounts/urls.py ergänze ich:
from django.contrib.auth import views as auth_views
urlpatterns = [
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='password_reset.html', html_email_template_name='password_reset_email.html'), name='password_reset'),
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='password_reset_done.html'), name='password_reset_done'),
path('password_reset/reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='password_reset_confirm.html'),name='password_reset_confirm'),
path('password_reset/complete/', auth_views.PasswordResetCompleteView.as_view(template_name='password_reset_complete.html'),name='password_reset_complete'),]
Damit lege ich die URLs fest und sage ihm der PasswordResetView soll das Formular dafür anzeigen. Mit template_name= lege ich die Datei fest, die beim Aufruf des Views aufgerufen wird, html_email_template_name= gibt die Vorlage bei Mail-Versand vor.
Im Idealfall, wenn alles funktioniert, geht der Password-Reset die vier Schritte durch.
password_reset.html
{% extends "home.html" %}
{% block login %}
<h2>Send password reset link</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send Reset Link" />
</form>
<a href="{% url 'home' %}">Back to Home</a>
{% endblock %}
password_reset_done.html
{% extends "home.html" %}
{% block login %}
<h2>Password Reset Email Sent</h2>
<p>We've sent you instructions for resetting your password to the email address you submitted.</p>
<p>You should receive the email shortly. If you don't see it, please check your spam folder.</p>
<p>If you don't receive an email, make sure you've entered the address you registered with.</p>
<a href="{% url 'home' %}">Back to Home</a>
{% endblock %}
password_reset_confirm.html
{% extends "home.html" %}
{% block login %}
{{ title }}
<h2>Enter Your New Password</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Reset Password</button>
</form>
<h2>Invalid Reset Link</h2>
<p>The password reset link was invalid, possibly because it has already been used or has expired.</p>
<p>Please request a new password reset.</p>
<a href="{% url 'password_reset' %}">Request New Reset Link</a>
<a href="{% url 'home' %}">Back to Home</a>
{% endblock %}
password_reset_complete.html
{% extends "home.html" %}
{% block login %}
<h2>Password reset completed</h2>
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'home' %}">Back to Home</a>
{% endblock %}
Und noch die eMail-Vorlage password_reset_email.html
{% autoescape off %}
You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.
Please go to the following page and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
Your email, in case you've forgotten: {{ user.email }}
Thanks for using our site!
The {{ site_name }} team.
{% endautoescape %}
In die urlpatterns der urls.py des Projektes muss noch ein Eintrag, damit die entsprechenden URLs in accounts auch gefunden werden.
path('accounts/', include('accounts.urls')),