Здравствуйте, добро пожаловать во вторую часть этого учебника-курса, в котором вы изучите и поймете основы Django, создав клон популярного сайта Netflix.
Мы собираемся добавить функции аутентификации. По сути, мы позволим пользователям входить и регистрироваться.
Давайте приступим.
Добавьте шаблоны сайта
Для этого проекта мы не будем писать собственные файлы Html или CSS. Вместо этого мы воспользуемся работой, проделанной Карлосом Авила, которая доступна здесь: Netflix Clone. Спасибо ему.
Если вы откроете этот codepen, то увидите HTML-код и некоторые CSS.
Создайте папку templates
Django рекомендует помещать наши Html файлы или шаблоны в каталог templates
в корне проекта. Затем внутри этой директории мы можем создать папку для каждого приложения проекта, чтобы разделить шаблоны каждого приложения. Это возможно благодаря строке: 'BACKEND': 'django.template.backends.django.DjangoTemplates',
в файле настроек.
Кроме того, измените строку 58 файла настроек следующим образом:
- создайте папку
templates
в корне проекта:mkir templates
. - создайте в ней папку
netflix
для нашего приложения netflix:cd templates
mkir netflix
- создайте в ней файл index.html:
cd netflix
Django URLs
Помните, в первой части мы видели, что файл urls.py
содержит все маршруты нашего проекта.
У нас есть только один маршрут для доступа к странице администратора.
Теперь мы добавим маршрут для главной страницы с помощью этой строки:
Изменим ее следующим образом:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from netflix.views import index_view # Add this line
urlpatterns = [
path('admin/', admin.site.urls),
path('', index_view, name='home'), # Add this line
]
У нас должна возникнуть ошибка, потому что модуль index_view
, импортированный строкой from netflix.views import index_view
, еще не существует.
Это представление, которое будет выполняться, когда кто-то вызовет путь с именем home
, который мы только что добавили в строке
Поэтому нам нужно создать index_view
, который будет отображать домашнюю страницу для запрашивающих.
- Откройте файл
netflix/views.py
и добавьте этот код:
def index_view(request, path):
"""Home page view."""
return render(request, 'index.html')
Вот и все.
- запустите проект: ‘python manage.py runserver’.
- Откройте браузер и перейдите по адресу
http://localhost:8000
Вы должны увидеть что-то вроде этого:
Это означает, что мы можем получить доступ к нашей домашней странице. Но рендеринг не правильный, потому что мы еще не добавили стили.
Этим мы сейчас и займемся.
Статические файлы
В Django CSS, js и изображения, необходимые для рендеринга сайта, называются statics
, поэтому Django рекомендует помещать их в папку statics
.
Приступим:
- Во-первых, создайте каталог
static
в корне проекта. Django будет искать статические файлы там, подобно тому, как Django находит шаблоны внутриtemplates
:mkdir static
. - Создайте папку для приложения
netflix
аналогично шаблонам:cd static
mkdir netflix
- создайте в ней файл
style.css
:touch style.css
и скопируйте в него CSS часть кодепена.
Мы уже указали URL файлов статики с помощью строки:
Это означает, что мы должны получить к ним доступ следующим образом:
- Добавьте эти строки в файл
settings.py
:
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), # Add this line
]
- Теперь добавим этот
{% load static %}
в начало файлаindex.html
. Это добавляет тегstatic
, который может быть использован для вставки ссылок из статических файлов. А затем используйте этот тегstatic
для импорта нашего style.css. - Добавляет эту строку
<link rel="stylesheet" href="{% static 'netflix/style.css' %}">
в тег<head>
файла index.html. - Обновите главную страницу.
Вот и все.
Шаблоны регистрации и входа
Сначала мы добавим на главную страницу кнопки registration
и login
.
-
В файле index.html замените
<a href="#">Account</a>
на<a href="/register">Register</a><a href="/login">Login</a>
.
Нажатие на кнопкуregister
должно перенаправить пользователя на маршрутregister
, который отобразит страницу регистрации, и таким же образом нажатие на кнопкуlogin
перенаправит пользователя на маршрутlogin
, который отобразит страницуlogin
. -
Добавьте
register_view
иlogin_view
в файлnetflix/views.py
:
def register_view(request):
"""Registration view."""
return render(request, 'netflix/register.html')
def login_view(request):
"""Login view."""
return render(request, 'netflix/login.html')
- Измените файл
urls.py
, чтобы добавить маршруты регистрации и входа:
...
from netflix.views import register_view # Add this line
from netflix.views import login_view # Add this line
urlpatterns = [
...
path('register', register_view, name='register'), # Add this line
path('login', login_view, name='login'), # Add this line
]
- Создайте файлы
register.html
иlogin.html
внутриtemplates/netlfix/
.Мы создадим регистрационный HTML, вдохновленный индексным файлом.- Для файла
registration.html
нужно просто скопировать и вставить следующий html-код:
- Для файла
{% load static %}
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Netflix</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js" crossorigin="anonymous"></script>
<script src="main.js"></script>
<link rel="stylesheet" href="{% static 'netflix/style.css' %}">
</head>
<body>
<div class="wrapper">
<!-- HEADER -->
<header>
<div class="netflixLogo">
<a id="logo" href="/"><img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/logo.PNG?raw=true" alt="Logo Image"></a>
</div>
<nav class="main-nav">
<a href="/">Home</a>
</nav>
<nav class="sub-nav">
<a href="/login">Login</a>
</nav>
</header>
<section class="main-container" >
<div class="location" id="home">
<div class="box">
</div>
</div>
</section>
</div>
</body>
</html>
- Для файла
login.html
нужно просто скопировать и вставить следующий html-код:
{% load static %}
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Netflix</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js" crossorigin="anonymous"></script>
<script src="main.js"></script>
<link rel="stylesheet" href="{% static 'netflix/style.css' %}">
</head>
<body>
<div class="wrapper">
<!-- HEADER -->
<header>
<div class="netflixLogo">
<a id="logo" href="/"><img src="https://github.com/carlosavilae/Netflix-Clone/blob/master/img/logo.PNG?raw=true" alt="Logo Image"></a>
</div>
<nav class="main-nav">
<a href="/">Home</a>
</nav>
<nav class="sub-nav">
<a href="/register">Register</a>
</nav>
</header>
<section class="main-container" >
<div class="location" id="home">
<div class="box">
</div>
</div>
</section>
</div>
</body>
</html>
Django Forms: Добавьте формы регистрации и входа
Django позволяет нам легко создавать и управлять формами.
Давайте создадим файл netlfix/forms.py
для размещения форм, связанных с приложением Netflix:
- внутри папки
netlfix
создайте файлforms.py
:touch forms.py
. - Добавьте в него этот код:
from django import forms
from django.contrib.auth.models import User
class RegisterForm(forms.Form):
"""Registration form class."""
firstname = forms.CharField(label="First name")
lastname = forms.CharField(label="Last name")
email = forms.EmailField(label="Email Address")
password = forms.CharField(label="Password", widget=forms.PasswordInput)
password_conf = forms.CharField(label="Password confirmation", widget=forms.PasswordInput)
class LoginForm(forms.Form):
"""Login form class."""
email = forms.EmailField(label="Email Address")
password = forms.CharField(label="Password", widget=forms.PasswordInput)
- Измените
register_view
следующим образом:
from .forms import RegisterForm
...
...
def register_view(request):
"""Registration view."""
register_form = RegisterForm()
return render(request, 'netflix/register.html', locals())
Обратите внимание, что аргумент locals()
, переданный в render()
, отправляет все локальные переменные в шаблон HTML (здесь register.html
). Это означает, что мы можем получить доступ к нашей форме в register.html
как к переменной с именем register_form
. Эта функция настраивается.
- В
register.html
измените тег<div class="box">
следующим образом:
<div class="box">
<form id="registerForm" action="/register" method="POST">
{% csrf_token %}
{{ register_form.as_p }}
<button type="submit">Register</button>
</form>
</div>
Важное замечание: Вы заметите синтаксис {{ register_form.as_p }}
и {% csrf_token %}
. Помните, я говорил, что аргументы locals()
отправляют все локальные переменные представления регистрации в шаблон, а переменная register_form
отправляется в HTML шаблон (register.html
). Затем для доступа к этим локальным переменным в register.html
нам просто нужно обернуть имя переменной в {{ }}
, как это сделано для {{ register_form.as_p }}
. Теперь для {% csrf_token %}
все немного иначе, поскольку он отображается не как переменная, а как Tag
. Если вы хотите узнать больше, прочитайте эту часть документации. А {% csrf_token %}
позволяет нам установить значение csrf_token
пользовательской сессии для предотвращения CSRF-атак.
Обновите страницу регистрации.
Django автоматически сгенерирует HTML-код формы. Разве это не волшебство ^_^ ? И мы можем настроить его, но не в этом курсе.
Сейчас это должно выглядеть так:
- Измените
login_view
следующим образом:
...
from .forms import LoginForm # add this line
...
...
def login_view(request):
"""Login view."""
login_form = LoginForm()
return render(request, 'netflix/login.html', locals())
- В
login.html
измените тег<div class="box">
следующим образом:
<div class="box">
<form id="loginForm" action="/login" method="POST">
{% csrf_token %}
{{ login_form.as_p }}
<button type="submit">Login</button>
</form>
</div>
Обновите страницу входа в систему.
Теперь она должна выглядеть следующим образом:
Обработайте отправку форм.
Форма регистрации
Теперь, если пользователь заполнит форму и отправит ее, он должен иметь возможность зарегистрироваться.
Если вы посмотрите на HTML-код формы регистрации, то заметите, что значение свойства HTML формы action
— это /register
(маршрут регистрации в нашем url.py
).
Мы изменим register_view
для обработки отправки формы. (Но вы можете переместить пост в другое представление).
- Измените
register_view
следующим образом:
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
...
...
def register_view(request):
"""Registration view."""
if request.method == 'GET':
# executed to render the registration page
register_form = RegisterForm()
return render(request, 'netflix/register.html', locals())
else:
# executed on registration form submission
register_form = RegisterForm(request.POST)
if register_form.is_valid():
User.objects.create(
first_name=request.POST.get('firstname'),
last_name=request.POST.get('lastname'),
email=request.POST.get('email'),
username=request.POST.get('email'),
password=make_password(request.POST.get('password'))
)
return HttpResponseRedirect('/login')
return render(request, 'netflix/register.html', locals())
Теперь наш register_view
обрабатывает как отображение страницы регистрации при условии if request.method == 'GET':
, так и отправку формы регистрации при условии else
. И, как я уже сказал, вам решать, как обрабатывать их с помощью двух разных представлений.
Давайте объясним, что мы делаем в условии else
:
- Если это так, то
User.objects.create(
выполняется для создания экземпляра модели User. - И
return HttpResponseRedirect('/login')
перенаправляет пользователя наlogin.page
. - Если форма не действительна, то вместо этого выполняется условие
else:
. - Затем мы перенаправляем пользователя на страницу регистрации, и Django автоматически отобразит пользователю ошибки валидации, чтобы он мог исправить их и отправить форму снова. Это ли не волшебство ^_^.
Валидация регистрационной формы
У нас могут быть некоторые ограничения для наших данных.
-
Минимальная длина пароля равна 6, а максимальная — 20: внутри
forms.py
измените строкуpassword = forms.CharField(label="Password", widget=forms.PasswordInput)
наpassword = forms.CharField(label="Password", widget=forms.PasswordInput(render_value=True), min_length=6, max_length=20)
. (Вы заметили, что мы добавилиrender_value=True
, это для того, чтобы сказать django сохранить значение пароля, когда форма возвращается обратно с ошибками). -
подтверждение пароля должно совпадать с паролем, а email не должен существовать в базе данных. Чтобы обработать эти два ограничения валидации, мы переопределим поведение по умолчанию метода
clean
класса Django Form. Поэтому модифицируйтеRegistrationForm
следующим образом:
...
class RegisterForm(forms.Form):
"""Registration form class."""
....
# Add this methods
def clean(self):
"""Check if the form is validated."""
# call the default `clean` method to perform
# default validation of the form (max_lenght and min_length defined for password for instance)
super(RegisterForm, self).clean()
# extract user input data
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
password_conf = self.cleaned_data.get('password_conf')
# Check if the password match the password confirmation
if password != password_conf:
self._errors['password_conf'] = self.error_class([
"wrong confirmation"
])
# Check if the email used doen't already exist
if User.objects.filter(username=email).exists():
self._errors['email'] = self.error_class(['Email already exist'])
# return any errors if found
return self.cleaned_data
Метод достаточно документирован, чтобы объяснить, что он делает.
Вот и все.
Примечание: Для валидаторов паролей вы можете использовать настройки AUTH_PASSWORD_VALIDATORS
, использовать валидаторы Django по умолчанию или создать свои собственные. Но в этом руководстве мы будем использовать вместо этого Django форму. Дайте мне знать в комментариях, если вам нужен учебник по созданию собственных валидаторов паролей.
Попробуйте зарегистрировать пользователя с неправильным вводом, а затем с правильным.
После того, как вы закончите. Войдите в django admin
с правами суперпользователя и перейдите в http://localhost:8000/admin/auth/user/
. Вы увидите, что новый пользователь добавлен. Вы также можете проверить в оболочке django shell:
from django.contrib.auth.models import User
User.objects.all()
. Вы должны увидеть нового пользователя.
Форма входа в систему
Теперь зарегистрированный пользователь должен иметь возможность войти в систему.
Если вы посмотрите на HTML-код формы входа, то заметите, что значение свойства HTML формы action
— это /login
(маршрут входа в нашем url.py
).
Мы изменим login_view
для обработки отправки формы.
- Измените
login_view
следующим образом:
...
from django.contrib.auth import authenticate, login # Add this line
from .forms import LoginForm # Add this line
...
...
def login_view(request):
"""Login view."""
if request.method == 'GET':
# executed to render the login page
login_form = LoginForm()
return render(request, 'netflix/login.html', locals())
else:
# get user credentials input
username = request.POST['email']
password = request.POST['password']
# If the email provided by user exists and match the
# password he provided, then we authenticate him.
user = authenticate(username=username, password=password)
if user is not None:
# if the credentials are good, we login the user
login(request, user)
# then we redirect him to home page
return HttpResponseRedirect('/')
# if the credentials are wrong, we redirect him to login and let him know
return render(
request,
'netflix/login.html',
{
'wrong_credentials': True,
'login_form': LoginForm(request.POST)
}
)
Этот метод достаточно документирован.
- Измените файл
login.html
следующим образом:
<form id="loginForm" action="/login" method="POST">
{% csrf_token %}
{{ login_form.as_p }}
<button type="submit">Login</button>
</form>
в этот:
<form id="loginForm" action="/login" method="POST">
{% csrf_token %}
{% if wrong_credentials %}
<p style="margin-top: 60px">Invalid credentials.</p>
{% endif %}
{{ login_form.as_p }}
<button type="submit">Login</button>
</form>
Обратите внимание, что мы только что добавили:
{% if wrong_credentials %}
<p style="margin-top: 60px">Invalid credentials.</p>
{% endif %}
Важное замечание: Это называется шаблонный условный рендеринг
. Django позволяет нам отображать HTML в соответствии с некоторыми условиями. Так, здесь мы выводим сообщение Invalid credentials
, если представление возвращает переменную wrong_credentials
со значением True
.
- Теперь, когда пользователь будет аутентифицирован и перенаправлен на главную страницу, мы отобразим его имя пользователя и кнопку выхода из системы вместо кнопок
login
иregister
. Измените строку
<a href="/register">Register</a><a href="/login">Login</a>
из файла index.html
в эти:
{% if request.user.is_authenticated %}
{{ request.user }}
<a href="/logout"><i class="fas fa-power-off sub-nav-logo"></i>Logout</a>
{% else %}
<a href="/register">Register</a><a href="/login">Login</a>
{% endif %}
Примечание: Здесь мы снова использовали условный рендеринг для достижения нашей цели с помощью {% if request.user.is_authenticated %}
. Но вы можете спросить, что мы не отправили эту переменную (request) в HTML-шаблон
. И вы правы. Мы можем получить доступ к переменной request
, потому что это глобальная (контекстная) переменная, автоматически отправляемая Django, чтобы позволить нам получить полезную информацию о текущем запросе. Так, если {{ request.user.is_authenticated }}
равно True
, пользователь аутентифицирован, а request.user
является экземпляром модели User аутентифицированного пользователя. И тогда мы можем отобразить email пользователя, используя request.user.username
или непосредственно request.user
. Но если вместо этого {{ request.user.is_authenticated }}
будет False
, мы отобразим кнопки регистрации и входа.
Если вы вошли в систему с правильными учетными данными, вы должны увидеть электронную почту аутентифицированного пользователя и кнопку выхода из системы, как показано здесь:
Выход из системы
Аутентифицированный пользователь должен иметь возможность выйти из системы.
У нас уже есть кнопка выхода из системы в файле index.html:
<a href="/logout"><i class="fas fa-power-off sub-nav-logo"></i>Logout</a>
Обратите внимание, что ссылка указывает на маршрут logout
, который еще не существует.
Давайте добавим его.
- В нижней части файла
views.py
добавьте logout_view:
...
...
def logout_view(request):
"""Logout view."""
# logout the request
logout(request)
# redirect user to home page
return HttpResponseRedirect('/')
- Добавьте представление выхода из системы в файл
urls.py
:
...
...
from netflix.views import logout_view # Add this line
urlpatterns = [
...
...
path('logout', logout_view, name='logout'), # Add this line
]
Поздравляем.
Мы подошли к концу этой второй части.
Резюме
В этом руководстве вы узнали
- как управлять шаблоном Django
- как выводить переменные внутри шаблона
- как условно выводить HTML внутри шаблона
- как управлять статическими файлами Django
- как создавать формы Django и обрабатывать валидацию
- как получить доступ и использовать переменную
request
в шаблоне Django - как обрабатывать базовую аутентификацию в Django (регистрация, вход и выход).
NB: Полный исходный код доступен здесь: https://github.com/fayomihorace/django-netflix-clone.
Если вы столкнулись с какой-либо блокировкой или ошибкой при выполнении этого руководства, пожалуйста, оставьте комментарий, я отвечу и помогу вам как можно скорее. С нетерпением жду вас в третьей части этого учебника-курса.