# 11、Django邮箱||验证码||登录

创建 django 项目， APP 略.....

### 准备工作

#### 1. Model的编写

model.py

```python
from django.db import models

gender = (
        ('男','男'),
        ('女','女'),
    )

class Before_User(models.Model):
    name = models.CharField(max_length=128, null=False, verbose_name='用户名')
    password = models.CharField(max_length=256, null=False, verbose_name='密码')
    email = models.EmailField(unique=True, null=False, verbose_name='邮箱')
    sex = models.CharField(max_length=32, choices=gender, default='男', verbose_name='性别')
    is_active = models.BooleanField(default=False)
    c_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('-c_time',)
        db_table = 'bf_user'
        verbose_name = '前端注册用户'
        verbose_name_plural = verbose_name
```

#### 2.第三方库安装

```python
pipenv install django--simple-captcha
pipenv install python-memcached 
```

#### 3. 注册APP

admin.py 注册

```python
from django.contrib import admin
from .models import Before_User

admin.site.register(Before_User)
```

#### 4. 生成数据库

```python
python manage.py makemigrations
python manage.py migrate
```

#### 4.django 后台创建一个注册用户(略)

### setting.py

```python
INSTALLED_APPS = [
    '''略'''
    'Apply_users', # 前端注册用户
    'captcha',

]
# 发送邮箱验证码
EMAIL_HOST = "smtp.163.com"     # 服务器
EMAIL_PORT = 25                 # 一般情况下都为25
EMAIL_HOST_USER = "xxxxx@163.com"     # 账号
EMAIL_HOST_PASSWORD = "xxxxx"          # 密码 (注意：这里的密码指的是授权码)
EMAIL_USE_TLS = False       # 一般都为False
EMAIL_FROM = "xxxxx@163.com"      # 邮箱来自


# 定义memcache服务器连接
import memcache
CACHES = {
    'default':{
        'BACKEND':'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '10.10.181.18:11211',
        ],
        'KEY_FUNCTION': lambda key,prefix_key,version:"django:%s"%key   #自定义存储格式
    }
}
```

### url.py 路径信息

```python
from django.urls import path, re_path
from . import views

urlpatterns = [
    path('index/', views.index, name='bf_index'),
    path('login/', views.LoginView.as_view(), name='bf_login'),
    path('register/', views.RegisterView.as_view(), name='bf_register'),
    path('logout', views.logout, name='bf_logout'),
    path('email_code/', views.email_code, name='bf_email_code'),             # 发送邮箱验证码
    re_path('forgetpwd/$', views.ForgetPwdView.as_view(), name='bf_forgetpwd'),      # 忘记密码，验证邮件
]
```

### forms.py 验证

```python
from django import forms
# from django.core import validators
from .models import Before_User, gender
from captcha.fields import CaptchaField
from django.contrib.auth.hashers import make_password, check_password



# 自定义 base 错误类
class BaseForm(forms.Form):
    def get_errors(self):
        errors = self.errors.get_json_data()
        new_errors = {}
        for key,message_dicts in errors.items():
            messages = []
            for message_dict in message_dicts:
                message = message_dict['message']
                messages.append(message)
                new_errors[key] = messages
        return new_errors


# 登录form验证
class LoginViewForm(BaseForm):
    '''
    登录Form
    attrs：可自定义html属性
    '''
    email = forms.EmailField(label='邮箱', widget=forms.TextInput(attrs={'class': 'form-control'}))
    password = forms.CharField(label='密码', max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    captcha = CaptchaField(label='验证码', error_messages={'invalid': '验证码错误'})

    def clean(self):
        cleaned_data = super().clean()
        email = cleaned_data.get('email')
        password = cleaned_data.get('password')
        user = Before_User.objects.filter(email=email).first()
        if not user.is_active:
            raise forms.ValidationError(message='你的账户已被禁止登陆,请联系管理员')
        if user and check_password(password, user.password):
            cleaned_data.update({'id': user.id, 'name': user.name})
            return cleaned_data
        raise forms.ValidationError(message='邮箱或者密码错误！')



# 注册form验证
class RegisterForm(BaseForm):
    '''注册Form'''
    name = forms.CharField(max_length=128, label='用户名', widget=forms.TextInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(label='邮箱', widget=forms.EmailInput(attrs={'class': 'form-control'}))
    sex = forms.ChoiceField(label='性别', choices=gender)
    password = forms.CharField(label='密码', max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    password2 = forms.CharField(label='确认密码', max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    captcha = CaptchaField(label='验证码')
    code = forms.CharField(label='邮箱验证码', max_length=50, widget=forms.TextInput(attrs={'class': 'form-control'}))

    def clean_email(self):
        email = self.cleaned_data.get('email')
        exists = Before_User.objects.filter(email=email).exists()
        if exists:
            raise forms.ValidationError(message='邮箱已被注册')
        return email

    def clean_password(self):
        password = self.cleaned_data.get('password')
        if len(password) < 6:
            raise forms.ValidationError("密码长度小于6位")
        elif len(password) > 20:
            raise forms.ValidationError('密码长度大于20位')
        return password

    def clean_password2(self):
        password = self.cleaned_data.get('password')
        password2 = self.cleaned_data.get('password2')
        if password != password2:
            raise forms.ValidationError(message='两次密码输入不一致')
        return password2


    def clean_code(self):
        from django.core.cache import cache
        code = self.cleaned_data.get('code')
        email = self.cleaned_data.get('email')
        cache_code = cache.get(email)
        if cache_code != code:
            raise forms.ValidationError(message='邮箱验证码错误')
        return code



# 修改密码 form 验证

class ForgetPwdForm(BaseForm):
    email = forms.EmailField(label='邮箱', widget=forms.EmailInput(attrs={'class': 'form-control'}))
    code = forms.CharField(label='验证码', widget=forms.TextInput(attrs={'class': 'form-control'}))

    def clean_email(self):
        email = self.cleaned_data.get('email')
        exists = Before_User.objects.filter(email=email).exists()
        if exists:
            return email
        raise forms.ValidationError('无此邮箱用户，请检查你的邮箱是否有误')

    def clean_code(self):
        from django.core.cache import cache
        code = self.cleaned_data.get('code')
        db_code = cache.get(self.cleaned_data.get('email'))
        if code != db_code:
            raise forms.ValidationError('邮箱验证码错误')
        return code
```

### send\_mail.py

```python
from random import Random  # 用于生成随机码
from django.core.mail import send_mail  # 发送邮件模块
from django.conf import settings    # setting.py添加的的配置信息
from django.core.cache import cache
import smtplib
import logging
from django.contrib.auth.hashers import make_password
from .models import Before_User

logger = logging.getLogger(__name__)


# 生成随机字符串
def random_str(randomlength=4):
    """
    随机字符串
    :param randomlength: 字符串长度
    :return: String 类型字符串
    """
    str = ""
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    length = len(chars) - 1
    random = Random()
    for i in range(randomlength):
        str += chars[random.randint(0, length)]
    return str


# 发送电子邮件
def send_code_email(email):
    """
    发送电子邮件
    """
    code = random_str(4)
    cache.set(email, code, 120)
    email_title = "Django"
    email_body = "您的邮箱验证码为：{0}, 该验证码有效时间为两分钟，请及时进行验证。".format(code)
    try:
        send_mail(email_title, email_body, settings.EMAIL_FROM, [email])
        return {'code': 200, 'message': '邮箱发送成功，请查收'}
    except smtplib.SMTPRecipientsRefused:
        return {'code': 404, 'message': '未找到此邮箱'}
    except Exception as e:
        logger.error("系统邮件配置有误，请检查......")
        return {'code': 500, 'message': '服务内部错误，请稍等,着急请联系管理员......'}



# 忘记密码发送修改过的密码给用户的邮件
def send_accout_email(email):
    email_title = "Django密码修改:"
    new_pwd = random_str(16)
    email_body = "邮箱:  {0}\n 密码：  {1}\n 这是系统随机设置密码，请及时进行修改。".format(email, new_pwd)
    try:
        send_mail(email_title, email_body, settings.EMAIL_FROM, [email])
        mf_user = Before_User.objects.get(email=email)
        mf_user.password = make_password(new_pwd)
        mf_user.save()
        return {'code': 200, 'message': '密码修改信息已发送到你邮箱，请查收'}
    except smtplib.SMTPRecipientsRefused:
        return {'code': 404, 'message': '未找到此邮箱'}
    except Exception as e:
        logger.error("系统邮件配置有误，请检查......")
        return {'code': 500, 'message': '服务内部错误，请稍等,或者请联系管理员......'}
```

### views.py

```python
from django.shortcuts import render, redirect,reverse
from django.views.generic import View
from django.http import JsonResponse
from .forms import LoginViewForm, RegisterForm, ForgetPwdForm
from .models import Before_User
from django.contrib.auth.hashers import make_password
from .send_email import send_code_email, send_accout_email


def index(request):
    return render(request, 'bf/bf_index.html')


# 注册使用
class RegisterView(View):
    def get(self, request):
        if request.session.get('is_login', None):
            return redirect(reverse("bf_index"))
        register_form = RegisterForm()
        return render(request, 'bf/bf_register.html',locals())

    def post(self, request):
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            name = register_form.cleaned_data.get('name')
            email = register_form.cleaned_data.get('email')
            sex = register_form.cleaned_data.get('sex')
            password = register_form.cleaned_data.get('password')
            new_user = Before_User.objects.create(name=name, email=email, sex=sex, password=make_password(password), is_active=True)
            new_user.save()
            return redirect(reverse('bf_login'))

        message = [item[0] for item in register_form.errors.values()]
        message = ','.join(message)
        return render(request, 'bf/bf_register.html', locals())


# 登录使用
class LoginView(View):
    def get(self, request):
        if request.session.get('is_login', None):
            return redirect(reverse("bf_index"))
        login_form = LoginViewForm()
        return render(request, 'bf/bf_login.html', locals())

    def post(self, request):
        login_form = LoginViewForm(request.POST)
        if login_form.is_valid():
            user_id = login_form.cleaned_data.get('id')
            user_name = login_form.cleaned_data.get('name')
            request.session['is_login'] = True
            request.session['user_id'] = user_id
            request.session['user_name'] = user_name
            return redirect(reverse('bf_index'))

        message = [item[0] for item in login_form.errors.values()]
        message = ','.join(message)
        return render(request, 'bf/bf_login.html', locals())



# 忘记密码视图并发送修改后的密码给用户
class ForgetPwdView(View):
    def get(self, request):
        FORGETPWD_FORM = ForgetPwdForm()
        return render(request, 'bf/bf_forgetpwd.html', locals())

    def post(self, request):
        FORGETPWD_FORM = ForgetPwdForm(request.POST)
        if FORGETPWD_FORM.is_valid():
            email = FORGETPWD_FORM.cleaned_data.get('email')
            send_accout_email(email=email)
            return redirect(reverse('bf_login'), locals())

        message = [item[0] for item in FORGETPWD_FORM.errors.values()]
        message = ','.join(message)
        return render(request, 'bf/bf_forgetpwd.html', locals())



# 登出使用
def logout(request):
    if not request.session.get('is_login', None):
        return redirect(reverse('bf_index'))
    request.session.flush()
    # 或者使用下面的方法
    # del request.session['is_login']
    # del request.session['user_id']
    # del request.session['user_name']
    return render(request, 'bf/bf_index.html')



# 发送邮箱验证码 url 视图函数
def email_code(request, methods=['POST']):
    email = request.POST.get('email')
    data = send_code_email(email)
    return JsonResponse(data, safe=False)
```

### 摸版 templates

#### bf\_base.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %} {% endblock%}
    </title>
</head>
<body>

{% block content %} {% endblock %}



<script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
        $(function () {
            // Add refresh button after field (this can be done in the template as well)
            // $('img.captcha').after(
            //     $('<a class="btn btn-sm text-white btn-info pull-right captcha-refresh">看不清</a>')
            // );
            // 点击图形验证码切换效果
            $('.captcha').click(function () {
                var $form = $(this).parents('form');
                var url = location.protocol + "//" + window.location.hostname + ":"
                    + location.port + "/captcha/refresh/";

                // Make the AJAX-call
                $.getJSON(url, {}, function (json) {
                    $form.find('input[name="captcha_0"]').val(json.key);
                    $form.find('img.captcha').attr('src', json.image_url);
                });
                return false;
            });
        });
</script>


<script>
    // 发送邮件，并倒计时
    $(function() {
        $('.code').click(function(event){
            event.preventDefault();
            var self = $(this);
            var email = $("input[name='email']").val();
            var csrfmiddlewaretoken = $("[name='csrfmiddlewaretoken']").val();
            $.post({
                'url': '{% url "bf_email_code" %}',
                'data': {
                    'email': email,
                    'csrfmiddlewaretoken': csrfmiddlewaretoken
                },
                success: function (data) {
                    if (data['code'] == 200) {
                        alert('邮箱验证码发送成功！');
                        self.attr("disabled", true);
                        var timeCount = 60;
                        var timer = setInterval(function () {
                            timeCount--;
                            self.attr(timeCount);
                            if (timeCount <= 0) {
                                self.removeAttr('disabled');
                                clearInterval(timer);
                                self.text('发送验证码');
                            }
                        }, 1000);
                    } else {
                        alert(data['message']);
                    }
                }
            })

        })
    })
</script>

</body>
</html>
```

#### bf\_login.html

```html
<div data-gb-custom-block data-tag="extends" data-0='bf/bf_base.html'></div>





<div data-gb-custom-block data-tag="block">


    登陆页面

</div>





<div data-gb-custom-block data-tag="block"></div>
<form action="<div data-gb-custom-block data-tag="url" data-0='bf_login'></div>" method="post">
    <div data-gb-custom-block data-tag="csrf_token"></div>

    <div data-gb-custom-block data-tag="if">
        {{ message }}
    </div>

<p>
    {{ login_form.email.label_tag }}
    {{ login_form.email }}
</p>


<p>
    {{ login_form.password.label_tag }}
    {{ login_form.password }}
</p>



<p>
      {{ login_form.ca{% extends 'bf/bf_base.html'%}


{% block title %}
    登陆页面
{% endblock %}


{% block content %}
<form action="{% url 'bf_login' %}" method="post">
    {% csrf_token %}

    {% if message %}
        {{ message }}
    {% endif %}

<p>
    {{ login_form.email.label_tag }}
    {{ login_form.email }}
</p>


<p>
    {{ login_form.password.label_tag }}
    {{ login_form.password }}
</p>



<p>
      {{ login_form.captcha.errors }}
      {{ login_form.captcha.label_tag }}
      {{ login_form.captcha }}
</p>

    <input type="submit" value="登录">
</form>

<a href="{% url 'bf_forgetpwd' %}">忘记密码？</a>
<a href="{% url 'bf_register' %}">注册</a>


{% endblock %}{% extends 'bf/bf_base.html'%}


{% block title %}
    登陆页面
{% endblock %}


{% block content %}
<form action="{% url 'bf_login' %}" method="post">
    {% csrf_token %}

    {% if message %}
        {{ message }}
    {% endif %}

<p>
    {{ login_form.email.label_tag }}
    {{ login_form.email }}
</p>


<p>
    {{ login_form.password.label_tag }}
    {{ login_form.password }}
</p>



<p>
      {{ login_form.captcha.errors }}
      {{ login_form.captcha.label_tag }}
      {{ login_form.captcha }}
</p>

    <input type="submit" value="登录">
</form>

<a href="{% url 'bf_forgetpwd' %}">忘记密码？</a>
<a href="{% url 'bf_register' %}">注册</a>


{% endblock %}
```

#### bf\_register.html

```html
{% extends 'bf/bf_base.html'%}


{% block title %}
    注册页面
{% endblock %}

{% block content %}

<form action="{% url 'bf_register' %}" method="post">

    {% csrf_token %}

    <!-- 显示全部错误信息 -->
    {% if message %}
        {{ message }}
    {% endif %}

    <p>
        {{ register_form.name.label_tag }}
        {{ register_form.name }}
    </p>

    <p>
        {{ register_form.email.label_tag }}
        {{ register_form.email }}
    </p>

    <p>
        {{ register_form.sex.label_tag }}
        {{ register_form.sex }}
    </p>

    <p>
        {{ register_form.password.label_tag }}
        {{ register_form.password }}
    </p>
    <p>
        {{ register_form.password2.label_tag }}
        {{ register_form.password2 }}
    </p>

    <p>
        {{ register_form.captcha.errors }}
        {{ register_form.captcha.label_tag }}
        {{ register_form.captcha }}
    </p>
    <p>
        {{  register_form.code.label_tag }}
        {{  register_form.code }}
        <!-- 只显示指定 form 验证错误信息-->
        {% if register_form.errors.code %}
            <span>{{ register_form.errors.email_captcha }}</span>
        {% endif %}
        <input type="submit" value="点击发送" class="code">
    </p>
    <input type="reset" value="重置">
    <input type="submit" value="注册">
</form>

{% endblock %}

```

#### bf\_forget.html

```html
{% extends 'bf/bf_base.html'%}


{% block title %}
    忘记密码
{% endblock %}

{% block content %}
<form action="{% url 'bf_forgetpwd' %}" method="post">

    {% csrf_token %}

    {% if message %}
        {{ message }}
    {% endif %}

<p>
    {{  FORGETPWD_FORM.email.label_tag }}
    {{  FORGETPWD_FORM.email }}
</p>
<p>
    {{ FORGETPWD_FORM.code.label_tag }}
    {{  FORGETPWD_FORM.code }}
    <input type="submit" value="点击发送" class="code">
</p>

    <input type="submit" value="提交">
</form>
{% endblock %}

```

#### bf\_index.html

```html
{% extends 'bf/bf_base.html'%}


{% block title %}
    首页
{% endblock %}




{% block content %}
    {% if request.session.is_login %}
        welcome {{ request.session.user_name }}!
    {% else %}
        未登录
    {% endif %}
{% endblock %}
```

### 登录+验证码

![](/files/QuHPbY7C32thUZufIycG)

### 注册+验证码

![](/files/Y8BKojlB8Q80YMZjPQ5P)

### 忘记密码验证

![](/files/FSxU0e93xseAOFHNwlsP)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://close.gitbook.io/yun-wei-bi-ji/python/django/11django-you-xiang-yan-zheng-ma-deng-lu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
