次はユーザ登録後に検証用のメールを送信する機能を実装します.ただし,メールの検証を行ってアカウントを有効化する処理は次のページで行います.
まず,送信したメールのリンクから開かれるページの URL を定義します.
users/urls.py
from django.urls import path
from . import views
app_name = 'users'
urlpatterns = [
    path('', views.users_index, name='index'),
    path('<int:user_id>/', views.users_profile, name='profile'),
    path('<int:user_id>/update/', views.users_update, name='update'),
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),
    path('password/', views.PasswordChangeView.as_view(), name='password_change_form'),
    path('password_change_done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),
    # ユーザ登録
    path('create/', views.UserCreateView.as_view(), name='create'),
    # メールリンクから呼び出される
    path('create/<uidb64>/<token>/', views.creation_confirm, name='create_confirm'),
    # パスワードリセットのメール送信画面
    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    # メール送信後の画面
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    # メールリンクから呼び出される
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]
ユーザ登録時にメールを送信するように forms.py を修正します.
/users/forms.py
class UserCreationForm(forms.ModelForm):
    """
    A form that creates a user, with no privileges, from the given username and
    password.
    """
    error_messages = {
        "password_mismatch": _("The two password fields didn’t match."),
    }
    password1 = forms.CharField(
        label=_("Password"),
        strip=False,
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password", 'class': 'form-control'}),
        help_text=password_validation.password_validators_help_text_html(),
    )
    password2 = forms.CharField(
        label=_("Password confirmation"),
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password", 'class': 'form-control'}),
        strip=False,
        help_text=_("Enter the same password as before, for verification."),
    )
    class Meta:
        model = UserModel
        fields = ("username", 'first_name', 'last_name', 'email')
        field_classes = {"username": UsernameField}
        widgets = {
            'first_name': forms.TextInput(attrs={
                'class': 'form-control',
                'required' : True
            }),
            'last_name': forms.TextInput(attrs={
                'class': 'form-control',
                'required' : True
            }),
            'email': forms.EmailInput(attrs={
                "autocomplete": "email",
                'class': 'form-control',
                'required' : True
            }),
        }
        labels = {
            'first_name': 'First Name',
            'last_name': 'Last Name',
            'email': 'Email',
        }
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self._meta.model.USERNAME_FIELD in self.fields:
            self.fields[self._meta.model.USERNAME_FIELD].widget.attrs[
                "autofocus"
            ] = True
    def clean(self):
        data = super().clean()
        first_name = data.get('first_name')
        last_name = data.get('last_name')
        if len(first_name) == 0:
            msg = "First Name が入力されていません"
            self.add_error('first_name', msg)
        elif len(first_name) > 20:
            msg = "First Name の最大文字数は20文字です"
            self.add_error('first_name', msg)
        if len(last_name) == 0:
            msg = "Last Name が入力されていません"
            self.add_error('last_name', msg)
        elif len(last_name) > 20:
            msg = "Last Name の最大文字数は20文字です"
            self.add_error('last_name', msg)
    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise ValidationError(
                self.error_messages["password_mismatch"],
                code="password_mismatch",
            )
        return password2
    def _post_clean(self):
        super()._post_clean()
        # Validate the password after self.instance is updated with form data
        # by super().
        password = self.cleaned_data.get("password2")
        if password:
            try:
                password_validation.validate_password(password, self.instance)
            except ValidationError as error:
                self.add_error("password2", error)
    def send_mail(
        self,
        subject_template_name,
        email_template_name,
        context,
        from_email,
        to_email,
        html_email_template_name=None,
    ):
        """
        Send a django.core.mail.EmailMultiAlternatives to `to_email`.
        """
        subject = loader.render_to_string(subject_template_name, context)
        # Email subject *must not* contain newlines
        subject = "".join(subject.splitlines())
        body = loader.render_to_string(email_template_name, context)
        email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
        if html_email_template_name is not None:
            html_email = loader.render_to_string(html_email_template_name, context)
            email_message.attach_alternative(html_email, "text/html")
        email_message.send()
    def save(
        self,
        # domain_override=None,
        subject_template_name="users/user_creation_subject.txt",
        email_template_name="users/user_creation_email.html",
        use_https=False,
        token_generator=default_token_generator,
        from_email=None,
        request=None,
        html_email_template_name=None,
        extra_email_context=None,
    ):
        user = super().save(commit=False)
        user.is_active = False  # まだログインできないようにする
        # user.is_active = True  # すぐにログインできるようにする
        user.set_password(self.cleaned_data["password1"])
        user.save()
        """
        Generate a one-use only link for user creation and send it to the
        user.
        """
        current_site = get_current_site(request)
        site_name = current_site.name
        domain = current_site.domain
        user_email = user.email
        context = {
            "email": user_email,
            "domain": domain,
            "site_name": site_name,
            "uid": urlsafe_base64_encode(force_bytes(user.pk)),
            "user": user,
            "token": token_generator.make_token(user),
            "protocol": "https" if use_https else "http",
            **(extra_email_context or {}),
        }
        # print("email : ", context["email"])
        # print("domain : ", context["domain"])
        # print("site_name : ", context["site_name"])
        # print("uid : ", context["uid"])
        # print("user : ", context["user"])
        # print("token : ", context["token"])
        # print("protocol : ", context["protocol"])
        # メールを送信する
        self.send_mail(
            subject_template_name,
            email_template_name,
            context,
            from_email,
            user_email,
            html_email_template_name=html_email_template_name,
        )
        return user
また,views.py も編集します.
users/views.py
class UserCreateView(CreateView):
    template_name = 'users/create_form.html'
    form_class = UserCreationForm
    from_email = settings.EMAIL_FROM
    email_template_name = 'users/user_creation_email.html'
    subject_template_name = 'users/user_creation_subject.txt'
    html_email_template_name = None
    extra_email_context = None
    token_generator = default_token_generator
    def form_valid(self, form):
        opts = {
            'use_https': self.request.is_secure(),
            'token_generator': self.token_generator,
            'from_email': self.from_email,
            'email_template_name': self.email_template_name,
            'subject_template_name': self.subject_template_name,
            'request': self.request,
            'html_email_template_name': self.html_email_template_name,
            'extra_email_context': self.extra_email_context,
        }
        user = form.save(**opts)
        messages.success(self.request, f'ユーザ登録しましたが,まだログインはできません {user.email}')
        return HttpResponseRedirect("/")
def creation_confirm(request, uidb64, token):
    messages.success(request, f'メールを確認しました uidb64 : {uidb64}, :token : {token}')
    return HttpResponseRedirect("/")
メールの件名と本文のテンプレートを作成します.
users/templates/users/user_creation_subject.txt
{% load i18n %}{% autoescape off %}
【{{ site_name }}】ユーザ登録
{% endautoescape %}
users/templates/users/user_creation_email.html
{% autoescape off %}
このメールは {{ site_name }} で,ユーザ登録が要求されたため送信されました.
次のリンク先のページで登録を完了してください.
{{ protocol }}://{{ domain }}{% url 'users:create_confirm' uidb64=uid token=token %}
{% endautoescape %}
受信できるメールアドレスを使ってユーザ登録を行います.受信したメールのリンクを開きます.
  メールのリンクから検証ページを開くことができました.メールのリンクに含まれる uidb64 と token を取得できていることにも注意してください.実際の検証は次のページで行います.