• 搭建自己的博客(二十九):增加绑定邮箱的功能,完善用户信息


    1、邮箱服务器使用了腾讯服务器

    具体操作见:python自动发邮件

    2、变化的部分

    3、上代码:

    {# 引用模板 #}
    {% extends 'base.html' %}
    {% load staticfiles %}
    {% load comment_tags %}
    {% load likes_tags %}
    
    {% block header_extends %}
        <link rel="stylesheet" href="{% static 'blog/blog.css' %}">
        <link rel="stylesheet" href="{% static 'fontawesome-free-5.5.0-web/css/all.min.css' %}">
    
        {#  处理公式  #}
        <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML'
                async></script>
        <script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
        <script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
    
    {% endblock %}
    
    {# 标题 #}
    {% block title %}
        {{ blog.title }}
    {% endblock %}
    
    {# 内容#}
    {% block content %}
        <div class="container">
            <div class="row">
                <div class="col-10 offset-1">
                    <ul class="blog-info-description">
                        <h3>{{ blog.title }}</h3>
                        <li>作者:{{ blog.author }}</li>
                        {# 时间过滤器让时间按照自己需要的格式过滤 #}
                        <li>发布日期:{{ blog.created_time|date:"Y-m-d H:i:s" }}</li>
                        <li>分类:
                            <a href="{% url 'blogs_with_type' blog.blog_type.pk %}">
                                {{ blog.blog_type }}
                            </a>
                        </li>
                        <li>阅读({{ blog.get_read_num }})</li>
                        <li>评论({% get_comment_count blog %})</li>
                    </ul>
                    <div class="blog-content">{{ blog.content|safe }}</div>
    
                    <div class="like"
                         onclick="likeChange(this,'{% get_content_type blog %}',{{ blog.pk }})">
                        <i class="far fa-thumbs-up {% get_like_status blog %}"></i>
                        <span class="liked-num">{% get_like_count blog %}</span>
                        <span>喜欢</span>
                    </div>
    
                    <p>上一篇:
                        {% if previous_blog %}
                            <a href="{% url 'blog_detail' previous_blog.pk %}">{{ previous_blog.title }}</a>
                        {% else %}
                            <span>没有了</span>
                        {% endif %}
                    </p>
                    <p>下一篇:
                        {% if next_blog %}
                            <a href="{% url 'blog_detail' next_blog.pk %}">{{ next_blog.title }}</a>
                        {% else %}
                            <span>没有了</span>
                        {% endif %}
                    </p>
                </div>
            </div>
            <div class="row">
                <div class="col-10 offset-1">
                    <div class="comment-area">
                        <h3 class="comment-area-title">提交评论</h3>
                        {% if user.is_authenticated %}
                            <form id="comment-form" action="{% url 'update_comment' %}" method="post"
                                  style="overflow: hidden">
                                {% csrf_token %}
                                <label for="form-control">{{ user.get_nickname_or_username }},欢迎评论~</label>
                                <div id="reply-content-container" style="display: none;">
                                    <p id="reply_title">回复:</p>
                                    <div id="reply-content">
    
                                    </div>
                                </div>
                                {% get_comment_form blog as comment_form %}
                                {% for field in comment_form %}
                                    {{ field }}
                                {% endfor %}
                                <span id="comment-error" class="text-danger float-left"></span>
                                <input type="submit" value="评论" class="btn btn-primary float-right">
                            </form>
                        {% else %}
                            您尚未登录,登录之后方可评论
                            {# 提交登录的时候带上从哪里访问的路径 #}
                            <a class="btn btn-primary" href="{% url 'login' %}?from={{ request.get_full_path }}">登录</a>
                            <span> or </span>
                            <a class="btn-danger btn" href="{% url 'register' %}?from={{ request.get_full_path }}">注册</a>
                        {% endif %}
                    </div>
                    <div class="-comment-area">
                        <h3 class="comment-area-title">评论列表</h3>
                        <div id="comment-list">
                            {% get_comment_list blog as comments %}
                            {% for comment in comments %}
                                <div id="root-{{ comment.pk }}" class="comment">
                                    <span>{{ comment.user.get_nickname_or_username }}</span>
                                    <span>{{ comment.comment_time|date:"Y-m-d H:i:s" }}</span>
                                    <div id="comment-{{ comment.pk }}">{{ comment.text|safe }}</div>
                                    {# 点赞 #}
                                    <div class="like"
                                         onclick="likeChange(this,'{% get_content_type comment %}',{{ comment.pk }})">
                                        <i class="far fa-thumbs-up {% get_like_status comment %}"></i>
                                        <span class="liked-num">{% get_like_count comment %}</span>
                                    </div>
    
                                    <a href="javascript:reply({{ comment.pk }})">回复</a>
    
                                    {% for reply in comment.root_comment.all %}
                                        <div class="reply">
                                            <span>{{ reply.user.get_nickname_or_username }}</span>
                                            <span>{{ reply.comment_time|date:"Y-m-d H:i:s" }}</span>
                                            <span>回复:</span><span>{{ reply.reply_to.get_nickname_or_username }}</span>
                                            <div id="comment-{{ reply.pk }}">{{ reply.text|safe }}</div>
                                            {# 点赞 #}
                                            <div class="like"
                                                 onclick="likeChange(this,'{% get_content_type reply %}',{{ reply.pk }})">
                                                <i class="far fa-thumbs-up {% get_like_status reply %}"></i>
                                                <span class="liked-num">{% get_like_count reply %}</span>
                                            </div>
    
                                            <a href="javascript:reply({{ reply.pk }})">回复</a>
                                        </div>
                                    {% endfor %}
                                </div>
                            {% empty %}
                                <span id="no-comment">暂无评论</span>
                            {% endfor %}
                        </div>
                    </div>
                </div>
            </div>
        </div>
    {% endblock %}
    
    {% block js %}
        <script>
            // 处理点赞
            function likeChange(obj, content_type, object_id) {
                let is_like = obj.getElementsByClassName('active').length === 0;
    
    
                $.ajax({
                    url: "{% url 'like_change' %}",
                    type: 'GET',
                    data: {
                        content_type: content_type,
                        object_id: object_id,
                        is_like: is_like,
                    },
                    cache: false,
                    success: function (data) {
                        console.log(data);
                        if (data['status'] === 'SUCCESS') {
                            // 更新点赞状态
                            let element = $(obj.getElementsByClassName('fa-thumbs-up'));
                            if (is_like) {
                                element.addClass('active');
                            } else {
                                element.removeClass('active');
                            }
                            // 更新点赞数量
                            let like_num = $(obj.getElementsByClassName('liked-num'));
                            like_num.text(data['liked_num']);
    
                        } else {
    
                            if (data['code'] === 400) {
                                $('#login_model').modal('show');
                            } else {
                                alert(data['msg']);
                            }
                        }
                    },
                    error: function (xhr) {
                        console.log(xhr);
                    }
                });
            }
    
            // 处理回复
            function reply(reply_comment_id) {
                $('#reply_comment_id').val(reply_comment_id);
                let html = $('#comment-' + reply_comment_id).html();
    
                $('#reply-content').html(html);
                $('#reply-content-container').show();  // 显示内容
    
    
                // 滚动富文本编辑器
                $('html').animate({scrollTop: $('#comment-form').offset().top - 60}, 300, function () {
                    // 动画执行完毕后执行的方法
                    // 让富文本编辑器获得焦点
                    CKEDITOR.instances['id_text'].focus();
                });
            }
    
            function numFormat(num) {
                return ('00' + num).substr(-2);
            }
    
            function timeFormat(timestamp) {
                let datetime = new Date(timestamp * 1000);
                let year = datetime.getFullYear();
                let month = numFormat(datetime.getMonth() + 1);
                let day = numFormat(datetime.getDate());
                let hour = numFormat(datetime.getHours());
                let minute = numFormat(datetime.getMinutes());
                let second = numFormat(datetime.getSeconds());
                return `${year}-${month}-${day} ${hour}:${minute}:${second}`
            }
    
            // 提交评论
            $('#comment-form').submit(function () {
                // 获取错误框
                let comment_error = $('#comment-error');
                comment_error.text('');
    
                // 更新数据到textarea
                CKEDITOR.instances['id_text'].updateElement();
                let comment_text = CKEDITOR.instances['id_text'].document.getBody().getText().trim();
                // 判断是否为空
                if (!(CKEDITOR.instances['id_text'].document.getBody().find('img')['$'].length !== 0 || comment_text !== '')) {
                    // 显示错误信息
                    comment_error.text('评论内容不能为空');
                    return false;
                }
                //异步提交
                $.ajax({
                    url: "{% url 'update_comment' %}",
                    type: 'POST',
                    data: $(this).serialize(),// 序列化表单值
                    cache: false, // 关闭缓存
                    success: function (data) {
                        let reply_comment = $('#reply_comment_id');
                        if (data['status'] === 'SUCCESS') {
                            console.log(data);
                            //  插入数据
                            //  es6写法
                            let like_html = `<div class="like"
                                         onclick="likeChange(this,'${data['content_type']}',${data["pk"]})">
                                        <i class="far fa-thumbs-up"></i>
                                        <span class="liked-num">0</span>
                                    </div>`;
                            if (reply_comment.val() === '0') {
                                // 插入评论
                                let comment_html = `<div id="root-${data["pk"]}" class="comment">
                                    <span>${data["username"]}</span>
                                    <span>${timeFormat(data["comment_time"])}</span>
                                    <div id="comment-${data["pk"]}">${data["text"]}</div>
                                    ${like_html}
                                    <a href="javascript:reply(${data["pk"]})">回复</a>
                                </div>`;
    
                                $('#comment-list').prepend(comment_html);
    
                            } else {
                                // 插入回复
    
                                let reply_html = `<div class="reply">
                                            <span>${data["username"]}</span>
                                            <span>${timeFormat(data["comment_time"])}</span>
                                            <span>回复:</span><span>${data["reply_to"]}</span>
                                            <div id="comment-${data["pk"]}">${data["text"]}</div>
                                            ${like_html}
                                            <a href="javascript:reply(${data["pk"]})">回复</a>
                                        </div>`;
                                $('#root-' + data['root_pk']).append(reply_html);
    
                            }
    
                            //  清空编辑框的内容
                            CKEDITOR.instances['id_text'].setData('');
                            $('#reply-content-container').hide(); // 回复完隐藏掉要回复的内容
                            reply_comment.val('0'); // 将回复标志重置0
                            $('#no-comment').remove(); // 如果有没回复标志,清除掉5
                            comment_error.text('评论成功');
                        } else {
                            // 显示错误信息
                            comment_error.text(data['message'])
                        }
                    },
                    error: function (xhr) {
                        console.log(xhr);
                    }
                });
                return false;
            });
        </script>
    
        <script>
            $(".nav-blog").addClass("active").siblings().removeClass("active");
        </script>
    {% endblock %}
    blog下的blog_detail.html
    from django.http import JsonResponse
    from django.contrib.contenttypes.models import ContentType
    from .models import Comment
    from .forms import CommentForm
    
    
    def update_commit(requests):
        comment_form = CommentForm(requests.POST, user=requests.user)
        if comment_form.is_valid():
            comment = Comment()
            comment.user = comment_form.cleaned_data['user']
            comment.text = comment_form.cleaned_data['text']
            comment.content_object = comment_form.cleaned_data['content_object']
    
            parent = comment_form.cleaned_data['parent']
            if parent is not None:
                comment.root = parent.root if parent.root is not None else parent
                comment.parent = parent
                comment.reply_to = parent.user
            comment.save()
            # 返回数据
            data = {
                'status': 'SUCCESS',
                'username': comment.user.get_nickname_or_username(),
                'comment_time': comment.comment_time.timestamp(),  # 返回时间戳
                'text': comment.text.strip(),
                'reply_to': comment.reply_to.get_nickname_or_username() if parent is not None else '',
                'pk': comment.pk,
                'root_pk': comment.root.pk if comment.root is not None else '',
                'content_type': ContentType.objects.get_for_model(comment).model,
            }
    
        else:
            data = {
                'status': 'ERROR',
                'message': list(comment_form.errors.values())[0][0],
            }
        return JsonResponse(data)
    comment下的views.py
    """
    Django settings for myblog project.
    
    Generated by 'django-admin startproject' using Django 2.1.3.
    
    For more information on this file, see
    https://docs.djangoproject.com/en/2.1/topics/settings/
    
    For the full list of settings and their values, see
    https://docs.djangoproject.com/en/2.1/ref/settings/
    """
    
    import os
    
    # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = 'ea+kzo_5k^6r7micfg@lar1(rfdc08@b4*+w5d11=0mp1p5ngr'
    
    # SECURITY WARNING: don't run with debug turned on in production!2.
    DEBUG = True
    
    ALLOWED_HOSTS = ['*']
    
    # Application definition
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'ckeditor',
        'ckeditor_uploader',
        'blog.apps.BlogConfig',  # 将自己创建的app添加到设置中
        'read_statistics.apps.ReadStatisticsConfig',  # 注册阅读统计app
        'comment.apps.CommentConfig',  # 注册评论
        'likes.apps.LikesConfig',  # 注册点赞
        'user.apps.UserConfig',  # 用户相关
    ]
    
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'blog.middleware.mymiddleware.My404',  # 添加自己的中间件
    ]
    
    ROOT_URLCONF = 'myblog.urls'
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [
                os.path.join(BASE_DIR, 'templates'),
            ],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                    'user.context_processors.login_model_form',  # 自定义模板变量,直接用模板语言调用
                ],
            },
        },
    ]
    
    WSGI_APPLICATION = 'myblog.wsgi.application'
    
    # Database
    # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
    
    DATABASES = {
        # 'default': {
        #     'ENGINE': 'django.db.backends.sqlite3',
        #     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        # }
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'myblogs',  # 要连接的数据库,连接前需要创建好
            'USER': 'root',  # 连接数据库的用户名
            'PASSWORD': 'felixwang',  # 连接数据库的密码
            'HOST': '127.0.0.1',  # 连接主机,默认本级
            'PORT': 3306  # 端口 默认3306
        }
    }
    
    # Password validation
    # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
    
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    
    # Internationalization
    # https://docs.djangoproject.com/en/2.1/topics/i18n/
    
    # LANGUAGE_CODE = 'en-us'
    # 语言
    LANGUAGE_CODE = 'zh-hans'
    
    # TIME_ZONE = 'UTC'
    # 时区
    TIME_ZONE = 'Asia/Shanghai'
    
    USE_I18N = True
    
    USE_L10N = True
    
    # 不考虑时区
    USE_TZ = False
    
    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/2.1/howto/static-files/
    
    STATIC_URL = '/static/'
    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, "static")
    ]
    
    # media
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
    
    # 配置ckeditor
    CKEDITOR_UPLOAD_PATH = 'upload/'
    
    # 自定义参数
    EACH_PAGE_BLOGS_NUMBER = 7
    
    # 设置数据库缓存
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
            'LOCATION': 'my_read_num_cache_table',
        }
    }
    
    # ckeditor 代码高亮,以及公式
    CKEDITOR_CONFIGS = {
        'default': {
            'skin': 'moono',
            'tabSpaces': 4,
            'mathJaxLib': 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML',
            'toolbar': (
                ['div', 'Source', '-', 'Save', 'NewPage', 'Preview', '-', 'Templates'],
                ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Print',
                 'SpellChecker', 'Scayt'],
                ['Undo', 'Redo', '-', 'Find', 'Replace', '-', 'SelectAll', 'RemoveFormat',
                 '-', 'Maximize', 'ShowBlocks', '-', "CodeSnippet", 'Mathjax', 'Subscript',
                 'Superscript'],
                ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button',
                 'ImageButton', 'HiddenField'],
                ['Bold', 'Italic', 'Underline', 'Strike', '-'],
                ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Blockquote'],
                ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
                ['Link', 'Unlink', 'Anchor'],
                ['Image', 'Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar',
                 'PageBreak'], ['Styles', 'Format', 'Font', 'FontSize'],
                ['TextColor', 'BGColor'],),
            'extraPlugins': ','.join([
                'codesnippet',
                'mathjax',
                'dialog',
                'dialogui',
                'lineutils',
            ]),
        },
        'comment_ckeditor': {
            'toolbar': 'custom',
            'toolbar_custom': [
                ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript'],
                ['TextColor', 'BGColor', 'RemoveFormat'],
                ['NumberedList', 'BulletedList'],
                ['Link', 'Unlink'],
                ['Smiley', 'SpecialChar', 'Blockquote'],
            ],
            'width': 'auto',
            'height': '180',
            'tabSpace': 4,
            'removePlugins': 'elementspath',
            'resize_enabled': False,
        }
    }
    
    # 发送邮箱设置
    MAIL_HOST = 'smtp.qq.com'  # smtp服务地址
    EMAIL_PORT = 465  # 端口号
    EMAIL_HOST_USER = '1403179190@qq.com'  # qq邮箱
    EMAIL_HOST_PASSWORD = '''  # 如果是qq邮箱的话该密码是配置qq邮箱的SMTP功能的授权码
    FROM_WHO = 'FCBlog'  # 前缀
    seetings.py
    # -*- coding: utf-8 -*-
    # @Time    : 18-11-27 上午11:07
    # @Author  : Felix Wang
    
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
    from io import BytesIO
    import random
    
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.image import MIMEImage
    from email.utils import parseaddr, formataddr
    from email.header import Header
    import smtplib
    from hashlib import md5
    
    
    class CheckCode:
        def __init__(self, font_file, font_size=36, width=240, height=60, char_length=4, start_color_num=0,
                     end_color_num=255, is_simple=True, is_oncache=False):
            self.is_oncache = is_oncache
            self.is_simple = is_simple
            self.font_file = font_file
            self.font_size = font_size
            self.width = width
            self.height = height
            self.char_length = char_length
            self.start_num = start_color_num
            self.end_num = end_color_num
            # 定义使用Image类实例化一个长为120px,宽为30px,基于RGB的(255,255,255)颜色的图片
            self.image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
            # 创建Font对象:
            self.font = ImageFont.truetype(self.font_file, self.font_size)
            # 创建Draw对象:
            self.draw = ImageDraw.Draw(self.image)
            # 双下划綫的变量在类中不能直接访问起到保护的作用
            self.__code_text = []
    
        def get_random_code(self, time=1):
            """
            :param is_simple: 是否包含中文繁体字,默认不包含,True表示不包含
            :param time: 返回多少个
            :return: 返回一个随机字符列表
            """
            is_simple = self.is_simple
            codes = []
            for i in range(time):
                num = chr(random.randint(0x30, 0x39))  # 随机生成数字
                lowercase = chr(random.randint(0x61, 0x74))  # 随机生成小写字母
                capcase = chr(random.randint(0x41, 0x5a))  # 随机生成大写字母
    
                # Unicode编码的汉字,带繁体字 ,共2万多字
                zh = chr(random.randint(0x4e00, 0x9fbf))
    
                # gbk编码的简单汉字,无繁体字
                head = random.randint(0xb0, 0xf7)
                body = random.randint(0xa1, 0xf9)  # 在head区号为55的那一块最后5个汉字是乱码,为了方便缩减下范围
                val = f'{head:x}{body:x}'
                ch = bytes.fromhex(val).decode('gb2312')
    
                if is_simple:
                    # code = random.choice([ch, num, lowercase, capcase])
                    code = random.choice([ch, num, lowercase, capcase])
                else:
                    code = random.choice([zh, num, lowercase, capcase])
                codes.append(code)
            return codes
    
        # 随机颜色:
        def rndColor(self, start, end, randomflag=True):
            """
            :param start:
            :param end:
            :param randomflag: 是否返回随机参数,
            :return:
            """
            return (random.randint(start, end), random.randint(start, end),
                    random.randint(start, end))
    
        def rotate(self):
            self.image.rotate(random.randint(0, 90), expand=0)
    
        # 随机点
        def randPoint(self):
            return (random.randint(0, self.width), random.randint(0, self.height))
    
        # 随机线
        def randLine(self, num):
            draw = ImageDraw.Draw(self.image)
            for i in range(0, num):
                draw.line([self.randPoint(), self.randPoint()], self.rndColor(0, 255))
            del draw
    
        # 获取验证码
        def get_codes(self):
            return self.__code_text
    
        def draw_pic(self):
            # 填充每个像素:
            # 单一背景
            color = self.rndColor(170, 255)  # 可以把背景调浅色
            for x in range(self.width):
                for y in range(self.height):
                    self.draw.point((x, y), fill=color)
    
            # 输出文字:
            codes = self.get_random_code(time=self.char_length)
            self.__code_text = []
            for ii in range(self.char_length):
                code = self.get_random_code()[0]
                self.__code_text.append(code)
                self.draw.text([random.randint(int((self.width / 2 - self.font_size / 2) / self.char_length * 2 * ii),
                                               int((self.width / 2 - self.font_size / 2) / self.char_length * 2 * (
                                                       ii + 1))), random.randint(0, self.height / 4)],
                               code, font=self.font, fill=self.rndColor(0, 120))  # 把字体调深色
            # self.rotate()
    
            # 画出随机线
            self.randLine(10)
    
            # 模糊:
            # self.image = self.image.filter(ImageFilter.BLUR)
            if self.is_oncache:  # 保存在缓存
                f = BytesIO()
                self.image.save(f, 'jpeg')
                return f.getvalue()
            else:
                # 保存
                self.image.save('{}.jpg'.format(''.join(self.get_codes())), 'jpeg')
    
    
    # 自动发邮件
    class AutoSendEmail:
        def __init__(self, sender, password, title, from_who, recever, smtp_server="smtp.qq.com", port=465):
            """
            :param sender: 邮件发送者
            :param password: 密码
            :param title: 邮件发送主题
            :param from_who: 邮件来自谁
            :param recever: 邮件接收者,可以是多个
            :param smtp_server: 邮件服务器,默认qq邮箱服务器
            :param port: 服务器端口qq邮箱默认端口为465
            """
            self.smtp_server = smtp_server  # 使用qq转发需要用到,可以在QQ邮箱设置中查看并开通此转发功能
            self.smtp_port = port  # smtp默认的端口是465
            # 接受者可以是多个,放在列表中
            self.recever = recever
            self.sender = sender
            self.password = password  # 该密码是配置qq邮箱的SMTP功能的授权码
            self.msg = MIMEMultipart()
            self.msg['Subject'] = title  # 邮件标题
            self.msg['From'] = self._format_addr(u'{} <{}>'.format(from_who, self.sender))
    
        # 添加文字信息
        def addTextMsg(self, text):
            text_plain = MIMEText(text, 'plain', 'utf-8')
            self.msg.attach(text_plain)
    
        # 添加图片
        def addImageMsg(self, imgPath):
            extend = imgPath.split('.')[-1]
            with open(imgPath, 'rb')as f:
                sendimagefile = f.read()
                filename = md5(sendimagefile).hexdigest() + '.' + extend
            image = MIMEImage(sendimagefile)
            image.add_header('Content-ID', '<image1>')
            image["Content-Disposition"] = u'attachment; filename={}'.format(filename)
            self.msg.attach(image)
    
        # 添加附件
        def addFile(self, filePath):
            extend = filePath.split('.')[-1]
            with open(filePath, 'rb')as f:
                sendfile = f.read()
                filename = md5(sendfile).hexdigest() + '.' + extend
            # 构造附件
            text_att = MIMEText(sendfile, 'base64', 'utf-8')
            text_att["Content-Type"] = 'application/octet-stream'
            text_att["Content-Disposition"] = u'attachment; filename="{}"'.format(filename)
            self.msg.attach(text_att)
    
        # 添加html格式
        def addHtml(self, html):
            # 构造html
            # 发送正文中的图片:由于包含未被许可的信息,网易邮箱定义为垃圾邮件,报554 DT:SPM :<p><img src="cid:image1"></p>
            text_html = MIMEText(html, 'html', 'utf-8')
            self.msg.attach(text_html)
    
        # 格式化邮件地址
        def _format_addr(self, s):
            name, address = parseaddr(s)
            return formataddr((Header(name, 'utf-8').encode(), address))
    
        # 发送邮件
        def sendEmail(self):
            server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)  # 链接服务器
            server.set_debuglevel(1)  # 打印出和SMTP服务器交互的信息
            server.login(self.sender, self.password)  # 登录
            server.sendmail(self.sender, self.recever, self.msg.as_string())  # 发送邮件
            server.quit()  # 退出
            print('邮件发送成功')
    
    
    if __name__ == '__main__':
        smtp_server = "smtp.qq.com"  # smtp服务地址
        port = 465  # 端口号
        recever = ['1403179190@qq.com']  # 接收人列表可以是多个
        sender = "1403179190@qq.com"  # 发送人邮箱
        password = ""  # 如果是qq邮箱的话该密码是配置qq邮箱的SMTP功能的授权码
        title = '验证码'
        from_who = 'felixCRM'  # 发送人姓名
    
        # 实例化对象
        autoEmail = AutoSendEmail(sender=sender, recever=recever, password=password, title=title, from_who=from_who,
                                  smtp_server=smtp_server, port=port)
    
        html = """
            <html>  
              <head></head>  
              <body>  
                <p>Hi!<br>  
                   欢迎注册felixCRM系统!
                   <br>  
                   Here is the <a href="http://www.baidu.com">link</a> you wanted.<br> 
                </p> 
                <img src="http://img.zcool.cn/community/01f09e577b85450000012e7e182cf0.jpg@1280w_1l_2o_100sh.jpg"></img>
              </body>  
            </html>  
            """
        # 以html的形式发送文字,推荐这个,因为可以添加图片等
        autoEmail.addHtml(html)
        # 发送邮件
        try:
            autoEmail.sendEmail()
        except Exception as e:
            print(e)
            print('邮件发送失败')
    
    if __name__ == '__main__':
    
        # 这里的字体采用能识别中文的字体
        # font_file = '‪C:WindowsFontssimhei.ttf' # windows使用这个
        font_file = '/home/felix/.local/share/fonts/SIMHEI.TTF'
        checkcode = CheckCode(font_file=font_file, is_simple=True, char_length=random.randint(3, 5))
    
        # 生成多张验证码
        for i in range(1):
            checkcode.draw_pic()
            # 生成的验证码的内容
            codes = checkcode.get_codes()
            print(i, codes)
    utils.py
    {% load staticfiles %}
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <!-- 根据屏幕自动响应布局 -->
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>
            {#  用来放标题  #}
            {% block title %}
    
            {% endblock %}
        </title>
        {# 加载css代码 #}
        <link rel="stylesheet" href="{% static 'bootstrap4.1/bootstrap.min.css' %}">
        <link rel="stylesheet" href="{% static 'css/base.css' %}">
    
        {# js代码放在后面可以增加性能 #}
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
        <script src="{% static 'bootstrap4.1/popper.min.js' %}"></script>
        <script src="{% static 'bootstrap4.1/bootstrap.min.js' %}"></script>
    
        {% block header_extends %}
            {#    用来做头部扩展,如加载某些静态文件     #}
        {% endblock %}
    </head>
    <body>
    
    {# 导航栏 #}
    <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
        <a class="navbar-brand" href="{% url 'home' %}">Felix Blog</a>
    
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
    
    
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item nav-home"><a href="{% url 'home' %}" class="nav-link">首页</a></li>
                <li class="nav-item nav-blog"><a href="{% url 'blog_list' %}" class="nav-link">博客</a></li>
            </ul>
    
            <ul class="navbar-nav">
                {% if not user.is_authenticated %}
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'login' %}?from={{ request.get_full_path }}">登录</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{% url 'register' %}?from={{ request.get_full_path }}">注册</a>
                    </li>
                {% else %}
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
                           data-toggle="dropdown"
                           aria-haspopup="true" aria-expanded="false">
                            {{ user.username }}
                            {% if user.has_nickname %}
                                ({{ user.get_nickname }})
                            {% endif %}
                        </a>
                        <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                            <a class="dropdown-item" href="{% url 'user_info' %}">个人资料</a>
                            {% if user.is_staff or user.is_superuser %}
                                <a class="dropdown-item" href="{% url 'admin:index' %}">后台管理</a>
                            {% endif %}
                            <div class="dropdown-divider"></div>
                            <a class="dropdown-item" href="{% url 'logout' %}?from={{ request.get_full_path }}">登出</a>
                        </div>
                    </li>
                {% endif %}
            </ul>
        </div>
    </nav>
    
    {# 用来放内容 #}
    {% block content %}
    
    {% endblock %}
    
    <!-- Modal 登录模态框 -->
    <div class="modal fade" id="login_model" tabindex="-1" role="dialog"
         aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <form id="login_model_form" action="" method="post">
                    <div class="modal-header">
                        <h5 class="modal-title">登录</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                    <div class="modal-body">
                        {% csrf_token %}
                        {% for field in login_model_form %}
                            <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                            {{ field }}
                        {% endfor %}
                        <span id="login_model_tip" class="text-danger"></span>
                    </div>
                    <div class="modal-footer">
                        <button type="submit" class="btn btn-primary">登录</button>
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <script>
        $('#login_model_form').submit(function (event) {
            $('#login_model_tip').text('');
            event.preventDefault(); // 阻止原事件提交
            $.ajax({
                url: '{% url 'login_for_model' %}',
                type: 'POST',
                data: $(this).serialize(),
                cache: false,
                success: function (data) {
                    if (data['status'] === 'SUCCESS') {
                        window.location.reload();
                    } else {
                        $('#login_model_tip').text('用户名或密码不正确')
                    }
                }
            });
        })
    </script>
    
    {# 导入资源建议放在js代码前 #}
    {# 用来放js代码 #}
    {% block js %}
    
    {% endblock %}
    
    </body>
    </html>
    templates下的base.html
    {% extends 'base.html' %}
    
    {% block title %}
        {{ page_title }}
    {% endblock %}
    
    {% block content %}
        <div class="container">
            <div class="col-xl-6 offset-xl-3">
                <div class="card">
                    <h5 class="card-header">{{ form_title }}</h5>
                    <div class="card-body">
                        <form action="" method="post">
                            {% csrf_token %}
                            {% for field in form %}
                                {% if not field.is_hidden %}
                                    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                                {% endif %}
                                {{ field }}
                                <p class="text-danger">{{ field.errors.as_text }}</p>
                            {% endfor %}
                            <span id="error-tip" class="text-danger">{{ form.non_field_errors }}</span>
                            <div class="clearfix"></div>
                            <div class="float-left">
                                {% block other-buttons %}{% endblock %}
                            </div>
                            <div class="float-right">
                                <input type="submit" value="{{ submit_text }}" class="btn btn-primary">
                                <button class="btn" onclick="window.location.href='{{ return_back_url }}'">返回</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    {% endblock %}
    
    {% block js %}
        {# 将首页这个按钮设置激活状态 #}
        <script>
            $(".nav-home").addClass("active").siblings().removeClass("active");
        </script>
    {% endblock %}
    templates下的form.html
    {% extends 'form.html' %}
    
    {% block other-buttons %}
        <div id="send_code" class="btn btn-primary">发送验证码</div>
    {% endblock %}
    
    {% block js %}
        <script>
            $("#send_code").click(function () {
                if ($(this).hasClass('disabled')) {
                    return false;
                }
    
                let email = $('#id_email').val();
                if (email === '') {
                    $('#error-tip').text('邮箱不能为空');
                    return false
                }
                let re_email = /^([a-zA-Z0-9]+[_|\_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,3}$/;
                if (!re_email.test(email)) {
                    alert('邮箱格式不正确');
                    return false
                }
    
                // 发送验证码
                $.ajax({
                    url: "{% url 'send_verification_code' %}",
                    type: 'GET',
                    data: {
                        'email': email,
                    },
                    cache: false,
                    success: function (data) {
                        if (data['status'] === 'ERRORS') {
                            alert(data['msg']);
                        } else {
                            alert(data['msg']);
                        }
    
                    }
                });
    
                // # 把按钮变灰
                $(this).addClass('disabled');
                $(this).attr("disabled", true);
                let time = 60;
                let interval = setInterval(() => {
                    time -= 1;
                    $(this).text(`再次发送(${time}s)`);
                    if (time <= 0) {
                        // 时间等于0,进行复原
                        clearInterval(interval);
                        $(this).removeClass('disabled');
                        $(this).attr('disabled', false);
                        $(this).text('再次发送');
                        return false;
                    }
                }, 1000);
    
            });
        </script>
    {% endblock %}
    user下的bind_email.html
    {% extends 'base.html' %}
    
    {% block title %}
        个人资料
    {% endblock %}
    
    {% block content %}
        <div class="container">
            <div class="col-xl-8 offset-xl-2">
                <h2>{{ user.username }}</h2>
                {% if user.is_authenticated %}
                    <ul>
                        <li>昵称:{{ user.get_nickname }} <a
                                href="{% url 'change_nickname' %}?from={{ request.get_full_path }}">修改昵称</a></li>
                        <li>
                            邮箱:
                            {% if user.email %}
                                {{ user.email }}
                            {% else %}
                                未绑定 <a href="{% url 'bind_email' %}?from={{ request.get_full_path }}">绑定邮箱</a>
                            {% endif %}
                        </li>
                        <li>上一次登录时间:{{ user.last_login|date:"Y-m-d H:i:s" }}</li>
                        <li><a href="">修改密码</a></li>
                    </ul>
                {% else %}
                    {# 未登录跳转到首页 #}
                    <script>
                        window.location.href = '{% url 'home' %}'
                    </script>
                {% endif %}
            </div>
        </div>
        </div>
    {% endblock %}
    
    {% block js %}
        {# 将首页这个按钮设置激活状态 #}
        <script>
            $(".nav-home").addClass("active").siblings().removeClass("active");
        </script>
    {% endblock %}
    user_info.html
    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
    from django.contrib.auth.models import User
    
    from .models import Profile
    
    
    # Define an inline admin descriptor for Employee model
    # which acts a bit like a singleton
    class ProfileInline(admin.StackedInline):
        model = Profile
        can_delete = False
    
    
    # Define a new User admin
    class UserAdmin(BaseUserAdmin):
        inlines = (ProfileInline,)
        list_display = ('username', 'nickname', 'email', 'is_staff', 'is_active', 'is_superuser')
    
        def nickname(self, obj):
            return obj.profile.nickname
    
        nickname.short_description = '昵称'  # 后台字段
    
    
    # Re-register UserAdmin
    admin.site.unregister(User)
    admin.site.register(User, UserAdmin)
    
    
    @admin.register(Profile)
    class ProfileAdmin(admin.ModelAdmin):
        list_display = ('user', 'nickname')
    user下的admin.py
    # -*- coding: utf-8 -*-
    # @Time    : 18-11-20 下午8:10
    # @Author  : Felix Wang
    
    import re
    from django import forms
    from django.contrib import auth
    from django.contrib.auth.models import User
    
    
    class LoginForm(forms.Form):
        username = forms.CharField(label='用户名', required=True,
                                   widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入用户名'}))
        # widget指定input标签类型
        password = forms.CharField(label='密码',
                                   widget=forms.PasswordInput(attrs={'class': 'form-control', 'placeholder': '请输入密码'}))
    
        def clean(self):  # 验证数据
            username = self.cleaned_data['username']
            password = self.cleaned_data['password']
            user = auth.authenticate(username=username, password=password)
            if user is None:
                raise forms.ValidationError('用户名或密码错误')
            self.cleaned_data['user'] = user  # 将验证过的user放入clean_data
            return self.cleaned_data
    
    
    class RegisterForm(forms.Form):
        # 用户名字段
        username = forms.CharField(label='用户名',
                                   max_length=30,
                                   min_length=3,
                                   required=True,
                                   widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入用户名'}))
        # 邮箱字段
        email = forms.EmailField(label='邮箱',
                                 min_length=3,
                                 required=True,
                                 widget=forms.EmailInput(attrs={'class': 'form-control', 'placeholder': '请输入邮箱'}))
        # 密码字段
        password = forms.CharField(label='密码',
                                   min_length=6,
                                   required=True,
                                   widget=forms.PasswordInput(
                                       attrs={'class': 'form-control', 'placeholder': '请输入密码'}))
        # 再次输入密码
        password_again = forms.CharField(label='确认密码',
                                         min_length=6,
                                         required=True,
                                         widget=forms.PasswordInput(
                                             attrs={'class': 'form-control', 'placeholder': '请再输入一次密码'}))
    
        def clean_username(self):
            username = self.cleaned_data['username']
            if User.objects.filter(username=username).exists():
                raise forms.ValidationError('用户名已存在')
            return username
    
        def clean_email(self):
            email = self.cleaned_data['email']
            if User.objects.filter(email=email).exists():
                raise forms.ValidationError('邮箱已存在')
            if not re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}.[com,cn,net]{1,3}$', email):
                raise forms.ValidationError('邮箱格式错误')
            return email
    
        def clean_password_again(self):
            password = self.cleaned_data['password']
            password_again = self.cleaned_data['password_again']
            if password != password_again:
                raise forms.ValidationError('两次输入的密码不一致')
            return password_again
    
    
    class ChangeNicknameForm(forms.Form):
        nickname_new = forms.CharField(
            label='新的昵称',
            max_length=20,
            widget=forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '请输入新的昵称',
                }
            )
        )
    
        def __init__(self, *args, **kwargs):
            if 'user' in kwargs:
                self.user = kwargs.pop('user')
            super().__init__(*args, **kwargs)
    
        # 表单验证
        def clean(self):
            # 判断用户是否登录
            if self.user.is_authenticated:
                self.cleaned_data['user'] = self.user
            else:
                raise forms.ValidationError('用户尚未登录')
            return self.cleaned_data
    
        def clean_nickname_new(self):
            nickname_new = self.cleaned_data.get('nickname_new', '').strip()
            if nickname_new == '':
                raise forms.ValidationError('新的昵称不能为空')
            return nickname_new
    
    
    class BindEmailForm(forms.Form):
        email = forms.EmailField(
            label='邮箱',
            widget=forms.EmailInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '请输入邮箱',
                }
            )
        )
    
        verification_code = forms.CharField(
            label='验证码',
            max_length=20,
            required=False,
            widget=forms.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '请输入验证码',
                }
            )
        )
    
        def __init__(self, *args, **kwargs):
            if 'requests' in kwargs:
                self.requests = kwargs.pop('requests')
            super().__init__(*args, **kwargs)
    
        # 表单验证
        def clean(self):
            # 判断用户是否登录
            if self.requests.user.is_authenticated:
                self.cleaned_data['user'] = self.requests.user
            else:
                raise forms.ValidationError('用户尚未登录')
    
            # 判断用户是否已经绑定邮箱
            if self.requests.user.email != '':
                raise forms.ValidationError('你已经绑定邮箱')
    
            # 判断验证码
            code = self.requests.session.get('bind_email_code', '').upper()
            print('code', code)
            verification_code = self.cleaned_data.get('verification_code', '').upper()
            print('verification_code', verification_code)
            if code != verification_code or code == '':
                raise forms.ValidationError('验证码不正确')
            return self.cleaned_data
    
        def clean_email(self):
            email = self.cleaned_data['email']
            if User.objects.filter(email=email).exists():
                raise forms.ValidationError('该邮箱已经被绑定')
            if not re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}.[com,cn,net]{1,3}$', email):
                raise forms.ValidationError('邮箱格式错误')
            return email
    
        def clean_verification_code(self):
            verification_code = self.cleaned_data.get('verification_code', '').strip().upper()
            if verification_code == '':
                raise forms.ValidationError('验证码不能为空')
            return verification_code
    forms.py
    from django.db import models
    from django.contrib.auth.models import User
    
    
    class Profile(models.Model):
        user = models.OneToOneField(User, on_delete=models.CASCADE)
        nickname = models.CharField(max_length=20, verbose_name='昵称', default='')
    
        def __str__(self):
            return '<Profile: {} for {}>'.format(self.nickname, self.user.username)
    
    
    def get_nickname(self):
        if Profile.objects.filter(user=self).exists():
            profile = Profile.objects.get(user=self)
            return profile.nickname
        else:
            return ''
    
    
    def get_nickname_or_username(self):
        if Profile.objects.filter(user=self).exists():
            profile = Profile.objects.get(user=self)
            return profile.nickname
        else:
            return self.username
    
    
    def has_nickname(self):
        return Profile.objects.filter(user=self).exists()
    
    
    User.get_nickname = get_nickname  # 动态绑定方法
    User.has_nickname = has_nickname  # 动态绑定方法
    User.get_nickname_or_username = get_nickname_or_username  # 动态绑定方法
    models.py
    # -*- coding: utf-8 -*-
    # @Time    : 18-11-4 下午5:22
    # @Author  : Felix Wang
    
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('login/', views.login, name='login'),  # 登录
        path('logout/', views.logout, name='logout'),  # 登录
        path('login_for_model/', views.login_for_model, name='login_for_model'),  # 登录
        path('register/', views.register, name='register'),  # 注册
        path('user_info/', views.user_info, name='user_info'),  # 用户信息
        path('change_nickname/', views.change_nickname, name='change_nickname'),  # 更改昵称
        path('bind_email/', views.bind_email, name='bind_email'),  # 更改昵称
        path('send_verification_code/', views.send_verification_code, name='send_verification_code'),  # 更改昵称
    ]
    urls.py
    # -*- coding: utf-8 -*-
    # @Time    : 18-11-7 下午4:12
    # @Author  : Felix Wang
    import random
    import re
    import time
    from django.shortcuts import render, redirect
    from django.http import JsonResponse
    from django.contrib import auth
    from django.contrib.auth.models import User
    from django.urls import reverse
    from django.conf import settings
    from .forms import LoginForm, RegisterForm, ChangeNicknameForm, BindEmailForm
    from .models import Profile
    from myblog.utils import AutoSendEmail
    
    
    def login(requests):
        # 如果是form表单提交验证登录
        if requests.method == 'POST':
            login_form = LoginForm(requests.POST)
            if login_form.is_valid():  # 验证是否通过
                # 因为在form表单验证过了,所以不用自己再验证
                user = login_form.cleaned_data.get('user')
                auth.login(requests, user)
                return redirect(requests.GET.get('from', reverse('home')))
            else:
                login_form.add_error(None, '用户名或密码不正确')
        else:
            login_form = LoginForm()
        context = {
            'login_form': login_form,
        }
        return render(requests, 'user/login.html', context)
    
    
    def login_for_model(requests):
        login_form = LoginForm(requests.POST)
    
        # 如果是form表单提交验证登录
        if login_form.is_valid():  # 验证是否通过
            # 因为在form表单验证过了,所以不用自己再验证
            user = login_form.cleaned_data.get('user')
            auth.login(requests, user)
    
            data = {
                'status': 'SUCCESS',
            }
        else:
            data = {
                'status': 'ERROR',
            }
        return JsonResponse(data)
    
    
    def register(requests):
        if requests.method == 'POST':
            reg_form = RegisterForm(requests.POST)
            if reg_form.is_valid():
                username = reg_form.cleaned_data['username']
                email = reg_form.cleaned_data['email']
                password = reg_form.cleaned_data['password']
    
                # 创建用户
                user = User.objects.create_user(username=username, email=email, password=password)
                user.save()
    
                # 登录用户
                user = auth.authenticate(username=username, password=password)
                auth.login(requests, user)
                # 登录之后跳转
                return redirect(requests.GET.get('from', reverse('home')))
        else:
            reg_form = RegisterForm()
    
        context = {
            'reg_form': reg_form,
        }
        return render(requests, 'user/register.html', context)
    
    
    def logout(requests):
        auth.logout(requests)
        return redirect(requests.GET.get('from', reverse('home')))
    
    
    def user_info(requests):
        context = {}
        return render(requests, 'user/user_info.html', context)
    
    
    def change_nickname(requests):
        redirect_to = requests.GET.get('from', reverse('home'))
    
        if requests.method == 'POST':
            form = ChangeNicknameForm(requests.POST, user=requests.user)
            if form.is_valid():
                nickname_new = form.cleaned_data['nickname_new']
                profile, created = Profile.objects.get_or_create(user=requests.user)
                profile.nickname = nickname_new
                profile.save()
                return redirect(redirect_to)
        else:
            form = ChangeNicknameForm()
    
        context = {
            'submit_text': '修改',
            'page_title': '修改昵称',
            'form_title': '修改昵称',
            'form': form,
            'return_back_url': redirect_to,
    
        }
        return render(requests, 'form.html', context)
    
    
    def bind_email(requests):
        redirect_to = requests.GET.get('from', reverse('home'))
    
        if requests.method == 'POST':
            form = BindEmailForm(requests.POST, requests=requests)
            if form.is_valid():
                email = form.cleaned_data['email']
                requests.user.email = email
                requests.user.save()
                return redirect(redirect_to)
        else:
            form = BindEmailForm()
    
        context = {
            'submit_text': '绑定邮箱',
            'page_title': '绑定邮箱',
            'form_title': '绑定',
            'form': form,
            'return_back_url': redirect_to,
    
        }
        return render(requests, 'user/bind_email.html', context)
    
    
    def send_verification_code(requests):
        email = requests.GET.get('email', '')
        if re.match(r'^[0-9a-zA-Z_]{0,19}@[0-9a-zA-Z]{1,13}.[com,cn,net]{1,3}$', email):
            # 生成验证码
            all_codes = list(range(0x30, 0x39)) + list(range(0x61, 0x74)) + list(range(0x41, 0x5a))  # 大写,小写和数字
            code = ''.join([chr(random.choice(all_codes)) for i in range(6)])
            now = int(time.time())
            send_code_time = requests.session.get('send_code_time', 0)
            if now - send_code_time < 60:
                data = {
                    'status': 'ERROR',
                }
            else:
                requests.session['bind_email_code'] = code
                requests.session['send_code_time'] = send_code_time
                title = '验证码'
                auto_email = AutoSendEmail(sender=settings.EMAIL_HOST_USER, recever=[email],
                                           password=settings.EMAIL_HOST_PASSWORD, title=title, from_who=settings.FROM_WHO,
                                           smtp_server=settings.MAIL_HOST, port=settings.EMAIL_PORT)
                html = """
                            <html>
                              <head></head>
                              <body>
                                <p>Hi!<br>
                                   非常感谢您绑定邮箱!
                                   <br>
                                   本次的验证码是:{},请不要透露给其他人!
                                   <br>
                                </p>
                                <img style="180px;height:240px" src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1541440161574&di=fd6156e441788866ffbd6c654d75fa23&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201507%2F22%2F20150722222322_Ky8Nj.jpeg" />
                              </body>
                            </html>
                            """.format(code)
    
                # 以html的形式发送文字,推荐这个,因为可以添加图片等
                auto_email.addHtml(html)
                # 发送邮件
                try:
                    auto_email.sendEmail()
                    data = {
                        'status': 'SUCCESS',
                        'msg': '邮件发送成功',
                    }
                except Exception as e:
                    data = {
                        'status': 'ERRORS',
                        'msg': '邮件发送失败',
                    }
        else:
            data = {
                'status': 'ERRORS',
                'msg': '邮箱格式不正确',
            }
    
        return JsonResponse(data)
    user下的views.py
  • 相关阅读:
    IFNULL和isnull用法
    Python 进制转换 二进制 八进制 十进制 十六进制
    xhr是什么文件类型?
    from __future__ import unicode_literals
    sort is deprecated, use sort_values(inplace=True) for INPLACE sorting
    Autodesk View and Data API二次开发学习指南
    设置Mac 中保存对话框默认为扩展窗口
    [大数据学习研究] 错误排查,Hadoop集群部分DataNode不能启动
    IDEA 环境下更改Maven的仓库镜像提高下载速度
    [大数据学习研究] 4. Zookeeper-分布式服务的协同管理神器
  • 原文地址:https://www.cnblogs.com/felixwang2/p/10037426.html
Copyright © 2020-2023  润新知