Heim >Backend-Entwicklung >Python-Tutorial >Leitfaden zum Erstellen einer vollständigen Blog-App mit Django unter Verwendung der TDD-Methodik und PostgreSQL (Teilweise sichere Benutzerauthentifizierung).
Willkommen zurück, alle zusammen! Im vorherigen Teil haben wir einen sicheren Benutzerregistrierungsprozess für unsere Django-Bloganwendung eingerichtet. Nach erfolgreicher Registrierung wurden wir jedoch auf die Startseite weitergeleitet. Dieses Verhalten wird geändert, sobald wir die Benutzerauthentifizierung implementieren. Durch die Benutzerauthentifizierung wird sichergestellt, dass nur autorisierte Benutzer auf bestimmte Funktionen zugreifen können, und vertrauliche Informationen werden geschützt.
In dieser Serie erstellen wir eine vollständige Blog-Anwendung, die sich am folgenden Entity-Relationship-Diagramm (ERD) orientiert. Unser Fokus liegt dieses Mal auf der Einrichtung eines sicheren Benutzerauthentifizierungsprozesses. Wenn Sie diesen Inhalt hilfreich finden, mögen Sie ihn bitte, kommentieren Sie ihn und abonnieren Sie ihn, um auf dem Laufenden zu bleiben, wenn der nächste Teil veröffentlicht wird.
Dies ist eine Vorschau darauf, wie unsere Anmeldeseite aussehen wird, nachdem wir die Anmeldefunktion implementiert haben. Wenn Sie die vorherigen Teile der Serie noch nicht gelesen haben, empfehle ich Ihnen, dies zu tun, da dieses Tutorial eine Fortsetzung der vorherigen Schritte ist.
Okay, fangen wir an!!
Django verfügt über eine integrierte App namens contrib.auth, die uns die Benutzerauthentifizierung vereinfacht. Sie können die Datei blog_env/settings.py überprüfen. Unter INSTALLED_APPS sehen Sie, dass Auth bereits aufgeführt ist.
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <-- Auth app "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
Die Authentifizierungs-App bietet uns mehrere Authentifizierungsansichten für die Handhabung von Anmeldung, Abmeldung, Passwortänderung, Passwortzurücksetzung usw. Das bedeutet, dass die wesentlichen Authentifizierungsfunktionen, wie Benutzeranmeldung, Registrierung und Berechtigungen, ohne Notwendigkeit einsatzbereit sind alles von Grund auf neu zu bauen.
In diesem Tutorial konzentrieren wir uns ausschließlich auf die Anmelde- und Abmeldeansichten und behandeln die restlichen Ansichten in späteren Teilen der Serie.
Nach unserem TDD-Ansatz beginnen wir mit der Erstellung von Tests für das Anmeldeformular. Da wir noch kein Anmeldeformular erstellt haben, navigieren Sie zur Datei „users/forms.py“ und erstellen Sie eine neue Klasse, die von AuthenticationForm erbt.
# users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
Sobald das Formular definiert ist, können wir Testfälle in „users/tests/test_forms.py“ hinzufügen, um seine Funktionalität zu überprüfen.
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
Diese Tests decken Szenarien wie die erfolgreiche Anmeldung mit gültigen Anmeldeinformationen, die fehlgeschlagene Anmeldung mit ungültigen Anmeldeinformationen und den angemessenen Umgang mit Fehlermeldungen ab.
Die AuthenticationForm-Klasse bietet standardmäßig einige grundlegende Validierungen. Mit unserem LoginForm können wir jedoch sein Verhalten anpassen und alle erforderlichen Validierungsregeln hinzufügen, um unsere spezifischen Anforderungen zu erfüllen.
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <-- Auth app "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
Wir haben ein benutzerdefiniertes Anmeldeformular erstellt, das die folgenden Felder enthält: E-Mail, Passwort und Angemeldet bleiben. Das Kontrollkästchen „member_me“ ermöglicht es Benutzern, ihre Anmeldesitzung über mehrere Browsersitzungen hinweg aufrechtzuerhalten.
Da unser Formular das AuthenticationForm erweitert, haben wir einige Standardverhalten überschrieben:
# users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
Da wir noch keine Ansicht für die Anmeldung haben, navigieren wir zur Datei „users/views.py“ und erstellen eine neue Klasse, die von der LoginView der Authentifizierungs-App erbt
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
Fügen Sie am Ende der Datei „users/tests/test_views.py“ diese Testfälle hinzu
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
Wir müssen sicherstellen, dass diese Tests zu diesem Zeitpunkt fehlschlagen.
Fügen Sie in der Datei „users/views.py“ am Ende der Datei den folgenden Code hinzu:
(.venv)$ python3 manage.py test users.tests.test_forms Found 9 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). ......... ---------------------------------------------------------------------- Ran 9 tests in 3.334s OK Destroying test database for alias 'default'...
Im obigen Code erreichen wir Folgendes:
# -- other code from .forms import CustomUserCreationForm, LoginForm from django.contrib.auth import get_user_model, views # -- other code class CustomLoginView(views.LoginForm):
Um Ihre benutzerdefinierte Anmeldefunktion zu verbinden und Benutzern den Zugriff auf die Anmeldeseite zu ermöglichen, definieren wir URL-Muster in der Datei „users/urls.py“. Diese Datei ordnet bestimmte URLs (in diesem Fall /log_in/) den entsprechenden Ansichten (CustomLoginView) zu. Darüber hinaus fügen wir einen Pfad für die Abmeldefunktion mithilfe von Djangos integriertem LogoutView hinzu.
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <-- Auth app "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
Alles scheint in Ordnung zu sein, aber wir sollten angeben, wohin Benutzer nach erfolgreicher Anmeldung und Abmeldung weitergeleitet werden sollen. Dazu verwenden wir die Einstellungen LOGIN_REDIRECT_URL und LOGOUT_REDIRECT_URL. Fügen Sie am Ende Ihrer blog_app/settings.py-Datei die folgenden Zeilen hinzu, um Benutzer zur Startseite umzuleiten:
# users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
Da wir nun die Anmelde-URL haben, aktualisieren wir unsere SignUpView in der Datei „users/views.py“, um bei erfolgreicher Anmeldung zur Anmeldeseite umzuleiten.
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
Wir werden auch unsere SignUpTexts, insbesondere test_signup_correct_data(self), aktualisieren, um das neue Verhalten widerzuspiegeln und sicherzustellen, dass unsere Änderungen ordnungsgemäß getestet werden.
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
Erstellen Sie dann mit Ihrem Texteditor eine Datei „users/templates/registration/login.html“ und fügen Sie den folgenden Code ein:
(.venv)$ python3 manage.py test users.tests.test_forms Found 9 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). ......... ---------------------------------------------------------------------- Ran 9 tests in 3.334s OK Destroying test database for alias 'default'...
Wir werden die Funktion „Passwort vergessen“ später in dieser Serie hinzufügen, aber jetzt ist es nur noch ein toter Link.
Lassen Sie uns nun unsere Vorlage „layout.html“ aktualisieren, um die Anmelde-, Anmelde- und Abmeldelinks einzuschließen.
# -- other code from .forms import CustomUserCreationForm, LoginForm from django.contrib.auth import get_user_model, views # -- other code class CustomLoginView(views.LoginForm):
In unserer Vorlage prüfen wir, ob der Benutzer authentifiziert ist. Wenn der Benutzer angemeldet ist, zeigen wir den Abmeldelink und den vollständigen Namen des Benutzers an. Andernfalls zeigen wir die Anmelde- und Registrierungslinks an.
Lassen Sie uns nun alle Tests durchführen
# users/tests/test_views.py # -- other code class LoginTests(TestCase): def setUp(self): User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) self.valid_credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } def test_login_url(self): """User can navigate to the login page""" response = self.client.get(reverse('users:login')) self.assertEqual(response.status_code, 200) def test_login_template(self): """Login page render the correct template""" response = self.client.get(reverse('users:login')) self.assertTemplateUsed(response, template_name='registration/login.html') self.assertContains(response, '<a class="btn btn-outline-dark text-white" href="/users/sign_up/">Sign Up</a>') def test_login_with_valid_credentials(self): """User should be log in when enter valid credentials""" response = self.client.post(reverse('users:login'), self.valid_credentials, follow=True) self.assertEqual(response.status_code, 200) self.assertRedirects(response, reverse('home')) self.assertTrue(response.context['user'].is_authenticated) self.assertContains(response, '<button type="submit" class="btn btn-danger"><i class="bi bi-door-open-fill"></i> Log out</button>') def test_login_with_wrong_credentials(self): """Get error message when enter wrong credentials""" credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } response = self.client.post(reverse('users:login'), credentials, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Invalid email or password') self.assertFalse(response.context['user'].is_authenticated)
Da wir nun die An- und Abmeldefunktion konfiguriert haben, ist es an der Zeit, alles in unserem Webbrowser zu testen. Starten wir den Entwicklungsserver
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <-- Auth app "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ]
Navigieren Sie zur Registrierungsseite und geben Sie gültige Anmeldeinformationen ein. Nach erfolgreicher Registrierung sollten Sie zur Anmeldeseite weitergeleitet werden. Geben Sie die Benutzerinformationen in das Anmeldeformular ein und klicken Sie nach der Anmeldung auf die Schaltfläche „Abmelden“. Anschließend sollten Sie abgemeldet und zur Startseite weitergeleitet werden. Stellen Sie abschließend sicher, dass Sie nicht mehr angemeldet sind und dass die Registrierungs- und Anmeldelinks erneut angezeigt werden.
Alles funktioniert einwandfrei, aber mir ist aufgefallen, dass ein Benutzer, wenn er angemeldet ist und die Registrierungsseite unter http://127.0.0.1:8000/users/sign_up/ besucht, immer noch Zugriff auf das Registrierungsformular hat. Sobald ein Benutzer angemeldet ist, sollte er im Idealfall nicht mehr auf die Anmeldeseite zugreifen können.
Dieses Verhalten kann zu mehreren Sicherheitslücken in unserem Projekt führen. Um dieses Problem zu beheben, müssen wir SignUpView aktualisieren, um alle angemeldeten Benutzer auf die Startseite umzuleiten.
Aber zuerst aktualisieren wir unseren LoginTest, um einen neuen Test hinzuzufügen, der das Szenario abdeckt. Fügen Sie also in der Datei „users/tests/test_views.py“ diesen Code hinzu.
# users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
Jetzt können wir unsere SignUpView aktualisieren
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
Im obigen Code überschreiben wir die Methode „dispatch()“ unseres SignUpView, um jeden Benutzer umzuleiten, der bereits angemeldet ist und versucht, auf die Registrierungsseite zuzugreifen. Diese Weiterleitung verwendet die in unserer Datei „settings.py“ festgelegte LOGIN_REDIRECT_URL, die in diesem Fall auf die Startseite verweist.
Okay! Lassen Sie uns noch einmal alle unsere Tests durchführen, um zu bestätigen, dass unsere Updates wie erwartet funktionieren
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
Ich weiß, dass es noch viel zu erreichen gibt, aber nehmen wir uns einen Moment Zeit, um zu würdigen, was wir bisher erreicht haben. Gemeinsam haben wir unsere Projektumgebung eingerichtet, eine PostgreSQL-Datenbank angebunden und ein sicheres Benutzerregistrierungs- und Anmeldesystem für unsere Django-Bloganwendung implementiert. Im nächsten Teil befassen wir uns mit der Erstellung einer Benutzerprofilseite, die es Benutzern ermöglicht, ihre Informationen zu bearbeiten und das Passwort zurückzusetzen! Seien Sie gespannt auf weitere spannende Entwicklungen, während wir unsere Reise zur Django-Blog-App fortsetzen!
Ihr Feedback ist uns immer wichtig. Bitte teilen Sie Ihre Gedanken, Fragen oder Vorschläge in den Kommentaren unten. Vergessen Sie nicht, zu liken, einen Kommentar zu hinterlassen und sich zu abonnieren, um über die neuesten Entwicklungen auf dem Laufenden zu bleiben!
Das obige ist der detaillierte Inhalt vonLeitfaden zum Erstellen einer vollständigen Blog-App mit Django unter Verwendung der TDD-Methodik und PostgreSQL (Teilweise sichere Benutzerauthentifizierung).. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!