从零开始做个人网站(3)

写在前面:

由于作者仅仅是自学,且是独自做这个项目,代码中出现不少漏洞、错误、累赘或常识问题是难免的,作者也在不断努力学习中,请各位看官多提意见,轻喷~


基本信息

1. 项目环境

Python 3.10.7

Django 4.2

编辑器:VSCode

操作系统:Windows 10

2. 项目背景

正好近一段时间在自学 Django,突发奇想不妨试着做个个人网站练练手吧~

3. 项目构思

暂定为:登录系统 + 个人主页 + 个人博客 + 个人作品


项目代码

今天的任务是做完登录系统的视图 ~

tips: 本次所有代码除特殊说明,仍在 /LZLBlog/login/views.py 中编写

1. 登出视图

大体思路如下:

  1. 如果没有登录,跳转至登录页面(都没登录自然没有登出一说)
  2. 如果登录了,则清除之前设置的所有状态

登出视图函数代码如下:

def logout(request):
    if not request.session.get('is_login', ''):
        return redirect('/login/login/')
    
    try:
        response = redirect('/login/login/')
        response.delete_cookie(key=hashcode('LZLBlog'))
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return response
    except:
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return redirect('/login/login/')

代码注释:

1. key = hashcode('LZLBlog') 获取到的 Cookie 是 "记住我" 的标记

2. request.session.flush()  用于清空所有 session 会话

3. 如果有 redirect_href,那么跳转至其指定地址,若没有则跳转至登录页面

2. 重置密码视图函数

大体思路如下:

  1. 在第一个页面获取前端用户输入的邮箱,并发送验证链接至用户邮箱(带有标识符)
  2. 用户点击链接进入第二个页面,检查标识符是否正确且是否未过期
  3. 若标识符不正确,重新渲染页面并提示错误,若标识符过期,删除标识符,再重新渲染页面并提示错误
  4. 若检查通过,获取前端用户输入的密码与再次确认的密码,进行验证
  5. 验证未通过,重新渲染页面并提示错误
  6. 验证通过,更改用户密码并删除标识符,清除用户登录状态,跳转至登录页面

重置密码视图函数如下:

def reset(request):
    message = ''
    if request.method == 'POST':
        email = conditional_escape(request.POST.get('email', ''))
        message = '请检查填写的内容格式是否正确!'
        if email:
            if email_verification(email):
                message = email_verification(email)
                return render(request, 'login/reset.html', {'message':message})
            try:
                user = models.User.objects.get(email=email)
            except:
                message = '请检查填写的邮箱是否正确!'
                return render(request, 'login/reset.html', {'message':message})
            
            code = make_reset_password_confirm_string(user)
            send_reset_password_email(email, code)

            message = '邮件已发送,请查收邮件并通过邮件中的链接修改密码!'
            return render(request, 'login/reset.html', {'message':message})

        return render(request, 'login/reset.html', {'message':message})
    return render(request, 'login/reset.html', {'message':message})

def resetpassword(request):
    code = conditional_escape(request.GET.get('code', ''))
    message = ''
    if request.method == 'POST':
        message = '请检查填写的内容是否符合格式!'
        try:
            confirm = models.ResetString.objects.get(code=code)
        except:
            message = '无效的修改密码链接!'
            return render(request, 'login/resetpassword.html', {'message':message, 'code':''})

        c_time = confirm.c_time
        user = confirm.user
        password = conditional_escape(request.POST.get('password', ''))
        password_repeat = conditional_escape(request.POST.get('password_repeat', ''))
        now = datetime.datetime.now()
        now = now.replace(tzinfo=pytz.timezone('UTC'))
        if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):
            message = '邮件已过期,请重新重置密码!'
            confirm.delete()
            return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
        
        if password and password_repeat:
            if password != password_repeat:
                message = '两次输入的密码不同!'
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
            if password_verification(password):
                message = password_verification(password)
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})

            user.password = hashcode(password_repeat)
            user.save()
            confirm.delete()

            response = redirect('/login/login/')
            try:
                response.delete_cookie(key=hashcode('LZLBlog'))
                request.session.flush()
            except:
                request.session.flush()
            return response

        return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 
    return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 

相信逻辑应该比较清楚,这里的代码注释不写了,可以参考前面注册视图理解

重置密码视图中出现的一些小函数:

1. 邮箱格式验证与密码验证函数

email_verification() 与 password_verification()

之前都提到过,与注册视图用到的函数是一致的,此处不再做解释

2. 创建重置密码标识符函数

作用:在邮箱验证通过后创建重置密码标识符,为下面的发送邮件做准备

def make_reset_password_confirm_string(user):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    code = hashcode(user.name, now)
    repeat_reset_list = models.ResetString.objects.filter(user=user)
    if repeat_reset_list:
        for repeat_reset in repeat_reset_list:
            repeat_reset.delete()

    models.ResetString.objects.create(code=code, user=user)

代码注释:

1. 使用用户名加密,用户创建的时间(精确到秒)作为盐,确保验证码的值是独一无二的

2. 首先检查是否有重复标识符,如果有,那么先删掉所有重复标识符再创建当前标识符

3. 发送邮件函数

作用:发送带有重置密码链接的邮件到用户刚刚输入的邮箱地址

def send_reset_password_email(email, code):
    from django.core.mail import EmailMultiAlternatives
    subject = "来自 www.LZLBlog.com 的修改密码邮件"
    text_content = '''感谢来到 www.LZLBlog.com, 欢迎阅读作者的博客并发表评论与改进意见,
                    如果您看到这条信息,说明您的邮箱服务器不支持 HTML 链接功能,请检查或升级系统以解决问题!'''
    html_content = '''
                    <p>感谢来到<a href="http://{}/index/index/" target=_blank> www.LZLBlog.com </a>,
                    欢迎阅读作者的博客并发表评论与改进意见!</p>
                    <h2>点击此链接以重置密码:<a href="http://{}/login/resetpassword/?code={}">重置密码链接</a></h2>
                    <p>此链接有效期为 {} 天!</p>
                    '''.format('127.0.0.1:8000', '127.0.0.1:8000', code, settings.CONFIRM_DAYS)

    msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_HOST_USER, [email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

仍然假装自己有域名

代码注释:

1.  同注册视图中的发送邮件函数,这里需要有个开通 smtp 服务的邮箱,settings.py 中也要配置

2. 这里发送的是重置密码的 链接,而不是一个验证码

3. 用户主页视图函数

大体思路如下:

  1. 首先通过传递的 name 参数尝试获取用户,若不成功,直接返回
  2. 比对当前 登录的 用户是否是用户主页的用户
  3. 如果不是,打上限制标记,直接返回
  4. 如果是,获取前端用户输入的信息,更改相关用户信息(如签名、个人介绍等)

用户主页视图函数如下:

def index(request, name):
    try:
        user = models.User.objects.get(name=name)
        username = user.name
    except:
        user = None
        username = None
    if not(user):
        limits = 1
        render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    elif not(request.session.get('is_login', '')) or request.session.get('user_name','') != user.name:
        limits = 1
        return render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    else:
        limits = 0

    if request.method == "POST":
        big_detail = request.POST.get("big_detail", "")
        if big_detail:
            user.big_detail = big_detail
            user.save()
        
        small_description = conditional_escape(request.POST.get('small_description', ''))
        if small_description:
            user.small_description = small_description
            user.save()

        try:
            avatar = request.FILES['avatar']
        except:
            avatar = None
        if avatar:
            user.avatar = avatar
            user.save()

    return render(request, 'login/index.html', {'user':user, "limits":limits, 'username':username, 'login':request.session.get('is_login', '')})

@csrf_exempt
def upload_image(request):
    import os

    if request.method == "POST":
        file_obj = request.FILES['file']
        file_name_suffix = file_obj.name.split(".")[-1]
        if file_name_suffix not in ["jpg", "png", "gif", "jpeg"]:
            return JsonResponse({"message": "错误的文件格式"})

        upload_time = timezone.now()
        path = os.path.join(
            settings.MEDIA_ROOT,
            'tinymce',
            str(upload_time.year),
            str(upload_time.month),
            str(upload_time.day)
        )

        if not os.path.exists(path):
            os.makedirs(path)

        file_path = os.path.join(path, file_obj.name)

        file_url = f'{settings.MEDIA_URL}tinymce/{upload_time.year}/{upload_time.month}/{upload_time.day}/{file_obj.name}'
        if os.path.exists(file_path):
            return JsonResponse({'message': '文件已存在', 'location': file_url})

        with open(file_path, 'wb+') as f:
            for chunk in file_obj.chunks():
                f.write(chunk)

        return JsonResponse({'message': '上传图片成功', 'location': file_url})
    return JsonResponse({'detail': '错误的请求'})

代码注释:

1. upload_image() 函数是一个配置 tinymce 富文本编辑器上传图片的函数,与个人主页无关

2. 由于我在前端使用了 tinymce 富文本编辑器,因此需要配置这个函数

3. 这里仍然需要配置 media,还是等到后面再说

4. @csrf_exempt 装饰器用于取消 Django 在当前视图函数上的 CSRF 防御机制,不写会有 CSRF Forbidden 的问题

4. 404 处理函数

作用:处理 404(此视图只会在关闭开发服务器,即 settings.py 中的 DEBUG = False 时才会被使用)

代码如下:

@requires_csrf_token
def page_not_found(request, exception):
    return render(request, 'login/404.html')

这里需要在 根 URLconf 即 LZLBlog/LZLBlog/urls.py 中配置:

"""LZLBlog URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/dev/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from login import views              # 新添加的

urlpatterns = [
    path('admin/', admin.site.urls),
]

handler404 = views.page_not_found    # 新添加的

5. 全部代码

def logout(request):
    if not request.session.get('is_login', ''):
        return redirect('/login/login/')
    
    try:
        response = redirect('/login/login/')
        response.delete_cookie(key=hashcode('LZLBlog'))
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return response
    except:
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return redirect('/login/login/')
        
def make_reset_password_confirm_string(user):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    code = hashcode(user.name, now)
    repeat_reset_list = models.ResetString.objects.filter(user=user)
    if repeat_reset_list:
        for repeat_reset in repeat_reset_list:
            repeat_reset.delete()

    models.ResetString.objects.create(code=code, user=user)
    return code

def send_reset_password_email(email, code):
    from django.core.mail import EmailMultiAlternatives
    subject = "来自 www.LZLBlog.com 的修改密码邮件"
    text_content = '''感谢来到 www.LZLBlog.com, 欢迎阅读作者的博客并发表评论与改进意见,
                    如果您看到这条信息,说明您的邮箱服务器不支持 HTML 链接功能,请检查或升级系统以解决问题!'''
    html_content = '''
                    <p>感谢来到<a href="http://{}/index/index/" target=_blank> www.LZLBlog.com </a>,
                    欢迎阅读作者的博客并发表评论与改进意见!</p>
                    <h2>点击此链接以重置密码:<a href="http://{}/login/resetpassword/?code={}">重置密码链接</a></h2>
                    <p>此链接有效期为 {} 天!</p>
                    '''.format('127.0.0.1:8000', '127.0.0.1:8000', code, settings.CONFIRM_DAYS)

    msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_HOST_USER, [email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

def reset(request):
    message = ''
    if request.method == 'POST':
        email = conditional_escape(request.POST.get('email', ''))
        message = '请检查填写的内容格式是否正确!'
        if email:
            if email_verification(email):
                message = email_verification(email)
                return render(request, 'login/reset.html', {'message':message})
            try:
                user = models.User.objects.get(email=email)
            except:
                message = '请检查填写的邮箱是否正确!'
                return render(request, 'login/reset.html', {'message':message})
            
            code = make_reset_password_confirm_string(user)
            send_reset_password_email(email, code)

            message = '邮件已发送,请查收邮件并通过邮件中的链接修改密码!'
            return render(request, 'login/reset.html', {'message':message})

        return render(request, 'login/reset.html', {'message':message})
    return render(request, 'login/reset.html', {'message':message})

def resetpassword(request):
    code = conditional_escape(request.GET.get('code', ''))
    message = ''
    if request.method == 'POST':
        message = '请检查填写的内容是否符合格式!'
        try:
            confirm = models.ResetString.objects.get(code=code)
        except:
            message = '无效的修改密码链接!'
            return render(request, 'login/resetpassword.html', {'message':message, 'code':''})

        c_time = confirm.c_time
        user = confirm.user
        password = conditional_escape(request.POST.get('password', ''))
        password_repeat = conditional_escape(request.POST.get('password_repeat', ''))
        now = datetime.datetime.now()
        now = now.replace(tzinfo=pytz.timezone('UTC'))
        if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):
            message = '邮件已过期,请重新重置密码!'
            confirm.delete()
            return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
        
        if password and password_repeat:
            if password != password_repeat:
                message = '两次输入的密码不同!'
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
            if password_verification(password):
                message = password_verification(password)
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})

            user.password = hashcode(password_repeat)
            user.save()
            confirm.delete()

            response = redirect('/login/login/')
            try:
                response.delete_cookie(key=hashcode('LZLBlog'))
                request.session.flush()
            except:
                request.session.flush()
            return response

        return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 
    return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 

def index(request, name):
    try:
        user = models.User.objects.get(name=name)
        username = user.name
    except:
        user = None
        username = None
    if not(user):
        limits = 1
        render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    elif not(request.session.get('is_login', '')) or request.session.get('user_name','') != user.name:
        limits = 1
        return render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    else:
        limits = 0

    if request.method == "POST":
        big_detail = request.POST.get("big_detail", "")
        if big_detail:
            user.big_detail = big_detail
            user.save()
        
        small_description = conditional_escape(request.POST.get('small_description', ''))
        if small_description:
            user.small_description = small_description
            user.save()

        try:
            avatar = request.FILES['avatar']
        except:
            avatar = None
        if avatar:
            user.avatar = avatar
            user.save()

    return render(request, 'login/index.html', {'user':user, "limits":limits, 'username':username, 'login':request.session.get('is_login', '')})

@csrf_exempt
def upload_image(request):
    import os

    if request.method == "POST":
        file_obj = request.FILES['file']
        file_name_suffix = file_obj.name.split(".")[-1]
        if file_name_suffix not in ["jpg", "png", "gif", "jpeg"]:
            return JsonResponse({"message": "错误的文件格式"})

        upload_time = timezone.now()
        path = os.path.join(
            settings.MEDIA_ROOT,
            'tinymce',
            str(upload_time.year),
            str(upload_time.month),
            str(upload_time.day)
        )

        if not os.path.exists(path):
            os.makedirs(path)

        file_path = os.path.join(path, file_obj.name)

        file_url = f'{settings.MEDIA_URL}tinymce/{upload_time.year}/{upload_time.month}/{upload_time.day}/{file_obj.name}'
        if os.path.exists(file_path):
            return JsonResponse({'message': '文件已存在', 'location': file_url})

        with open(file_path, 'wb+') as f:
            for chunk in file_obj.chunks():
                f.write(chunk)

        return JsonResponse({'message': '上传图片成功', 'location': file_url})
    return JsonResponse({'detail': '错误的请求'})

@requires_csrf_token
def page_not_found(request, exception):
    return render(request, 'login/404.html')

至此,登录视图函数的内容就完结啦~   

下篇做前端模板 (HTML5 + CSS3 + JavaScript)~

最后在篇尾,附上登录视图函数的全部代码

from django.conf import settings
from django.shortcuts import render
from django.shortcuts import redirect
from django.utils.html import conditional_escape
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
from django.http import JsonResponse
from django.utils import timezone
from . import models
import hashlib, datetime, random, pytz

# Create your views here.

def hashcode(s, salt="LZLBlog"):
    h = hashlib.sha256()
    s += salt
    h.update(s.encode())
    return h.hexdigest()

def login(request):
    if request.session.get("is_login", ""):
        return redirect("/index/index/")
    
    visitnum = int(conditional_escape(request.session.get('visit_num', 0)))
    if visitnum <= 3:
        request.session['visit_num'] = visitnum+1
    else:
        turn_visit_num = conditional_escape(request.POST.get('turn_visit_num',0))
        if turn_visit_num:
            request.session['visit_num'] = 1

    try:
        remember_signature = request.get_signed_cookie(key=hashcode('LZLBlog'), salt=hashcode('LZLBlog'), max_age=7*24*3600)
        if remember_signature == hashcode(hashcode('LZLBlog')):
            href = conditional_escape(request.COOKIES.get('redirect_href',None))
            if href:
                return redirect(href)
            else:
                username = conditional_escape(request.get_signed_cookie(key=hashcode('username'), salt=hashcode(hashcode('username')), max_age=7*24*3600))
                return redirect('/index/index/')
    except:
        pass

    if request.method == "POST":     
        username = conditional_escape(request.POST.get("username",''))
        password = conditional_escape(request.POST.get("password",''))
        message = '请检查填写的内容格式是否正确!'
        if username.strip() and password:
            try:
                user = models.User.objects.get(name=username)
            except:
                try:
                    user = models.User.objects.get(email=username)
                except:
                    message = '用户名或密码不正确!'
                    return render(request, 'login/login.html', {'message':message})
            if user.password == hashcode(password):
                if not(user.has_confirmed):
                    message = '用户未经过邮件确认!'
                    return render(request, 'login/login.html', {'message':message})
                request.session['is_login'] = 1
                request.session['user_id'] = user.id
                request.session['user_name'] = user.name

                response = redirect('/index/index/')
                remember = request.POST.get('remember', '')
                href = conditional_escape(request.COOKIES.get('redirect_href','')) 
                if remember:
                    response.set_signed_cookie(key=hashcode('LZLBlog'), value=hashcode(hashcode('LZLBlog')), salt=hashcode('LZLBlog'), max_age=7*24*3600)
                    response.set_signed_cookie(key=hashcode('username'), value=hashcode(user.name), salt=hashcode(hashcode('username')), max_age=7*24*3600)
                if href:
                    return redirect(href)
                else:
                    return response
            else:
                message = '用户名或密码不正确!'
                return render(request, 'login/login.html', {'message':message})            
    else:
        message = ''
    
    return render(request, 'login/login.html', {'message':message})

def password_verification(password):
    message = ''
    if len(password) < 8:
        message = '密码位数必须至少有 8 位!'
        return message
    for chars in password:
        if '\u4e00' <= chars and chars <= '\u9fff':
            message = '密码中不可以包含中文字符!'
            return message
    en = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
    capital_en = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
    number = ['1','2','3','4','5','6','7','8','9']
    flag = 0
    for letter in en:
        if letter in password:
            flag = flag + 1
            break
    for letter in capital_en:
        if letter in password:
            flag = flag + 1
            break
    for num in number:
        if num in password:
            flag = flag+1
            break
    if flag < 2:
        message = '密码必须至少包含小写字母、大写字母和数字中的两种!'
        return message
    return message

def email_verification(email):
    message = ''
    emaillist = list(email)
    if emaillist.count('@') != 1:
        message = '请检查邮箱格式是否正确!'
        return message
    if emaillist.index('@') == 0 or emaillist.index('@') == len(emaillist)-1:
        message = '请检查邮箱格式是否正确!'
        return message
    return message

def make_confirm_string(user):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    code = hashcode(user.name, now)
    tag = hashcode(user.name, "tag")
    get_str = ''
    for x in range(6):
        num = random.randint(0,len(code)-1)
        get_str = get_str + code[num]
    models.ConfirmString.objects.create(code=get_str, user=user, tag=tag)
    return (get_str,tag)

def send_email(email, code):
    from django.core.mail import EmailMultiAlternatives
    subject = "来自 www.LZLBlog.com 的注册确认邮件"
    text_content = '''感谢注册 www.LZLBlog.com, 欢迎阅读作者的博客并发表评论与改进意见,
                    如果您看到这条信息,说明您的邮箱服务器不支持 HTML 链接功能,请检查或升级系统以解决问题!'''
    html_content = '''
                    <p>感谢注册<a href="http://{}/index/index/" target=_blank> www.LZLBlog.com </a>,
                    欢迎阅读作者的博客并发表评论与改进意见!</p>
                    <p>您的验证码是:</p><h1> {} </h1>
                    <p>此验证码有效期为 {} 天!</p>
                    '''.format('127.0.0.1:8000', code, settings.CONFIRM_DAYS)

    msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_HOST_USER, [email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

def register(request):
    if request.method == 'POST':
        username = conditional_escape(request.POST.get('username',''))
        password = conditional_escape(request.POST.get('password',''))
        password_repeat = conditional_escape(request.POST.get('password_again',''))
        email = conditional_escape(request.POST.get('email',''))
        message = "请检查填写的内容格式是否正确!"
        if username.strip() and password and password_repeat and email:
            if password != password_repeat:
                message = "两次输入的密码不同!"
                return render(request, 'login/register.html', {'message':message})
            else:
                user_list = models.User.objects.filter(name=username)
                if user_list:
                    message = "该用户名已经存在!"
                    return render(request, 'login/register.html', {'message':message})
                email_list = models.User.objects.filter(email=email)
                if email_list:
                    message = "该邮箱已经被注册了!"
                    return render(request, 'login/register.html', {'message':message})
                if password_verification(password):
                    message = password_verification(password)
                    return render(request, 'login/register.html', {'message':message})
                if email_verification(email):
                    message = email_verification(email)
                    return render(request, 'login/register.html', {'message':message})                   
                
                new_user = models.User()
                new_user.name = username
                new_user.password = hashcode(password_repeat)
                new_user.email = email
                new_user.save()

                code,tag = make_confirm_string(new_user)
                send_email(email, code)
                request.session['tag'] = tag

                return redirect('/login/confirm/')
        else:
            return render(request, 'login/register.html', {'message':message})
    return render(request, 'login/register.html')

def user_confirm(request):
    message = ''
    if request.method == "POST":
        tag = conditional_escape(request.session.get('tag', ''))
        email_password = conditional_escape(request.POST.get('email_password',''))
        try:
            confirm = models.ConfirmString.objects.get(tag=tag)
        except:
            return render(request, 'login/confirm.html', {'message':message})
        
        c_time = confirm.c_time
        now = datetime.datetime.now()
        now = now.replace(tzinfo=pytz.timezone('UTC'))
        if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):
            confirm.user.delete()
            del request.session['tag']
            message = '您的邮件已经过期!请重新注册!'
            return render(request, 'login/confirm.html', {'message':message})
        if email_password == confirm.code:
            confirm.user.has_confirmed = True
            confirm.user.save()
            confirm.delete()
            del request.session['tag']
            return redirect('/login/login/')
        else:
            message = '邮箱验证码不正确!'
            return render(request, 'login/confirm.html', {'message':message})
    return render(request, 'login/confirm.html', {'message':message})

def logout(request):
    if not request.session.get('is_login', ''):
        return redirect('/login/login/')
    
    try:
        response = redirect('/login/login/')
        response.delete_cookie(key=hashcode('LZLBlog'))
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return response
    except:
        request.session.flush()
        redirect_href = conditional_escape(request.COOKIES.get('redirect_href',''))
        if redirect_href:
            return redirect(redirect_href)
        else:
            return redirect('/login/login/')
        
def make_reset_password_confirm_string(user):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    code = hashcode(user.name, now)
    repeat_reset_list = models.ResetString.objects.filter(user=user)
    if repeat_reset_list:
        for repeat_reset in repeat_reset_list:
            repeat_reset.delete()

    models.ResetString.objects.create(code=code, user=user)
    return code

def send_reset_password_email(email, code):
    from django.core.mail import EmailMultiAlternatives
    subject = "来自 www.LZLBlog.com 的修改密码邮件"
    text_content = '''感谢来到 www.LZLBlog.com, 欢迎阅读作者的博客并发表评论与改进意见,
                    如果您看到这条信息,说明您的邮箱服务器不支持 HTML 链接功能,请检查或升级系统以解决问题!'''
    html_content = '''
                    <p>感谢来到<a href="http://{}/index/index/" target=_blank> www.LZLBlog.com </a>,
                    欢迎阅读作者的博客并发表评论与改进意见!</p>
                    <h2>点击此链接以重置密码:<a href="http://{}/login/resetpassword/?code={}">重置密码链接</a></h2>
                    <p>此链接有效期为 {} 天!</p>
                    '''.format('127.0.0.1:8000', '127.0.0.1:8000', code, settings.CONFIRM_DAYS)

    msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_HOST_USER, [email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

def reset(request):
    message = ''
    if request.method == 'POST':
        email = conditional_escape(request.POST.get('email', ''))
        message = '请检查填写的内容格式是否正确!'
        if email:
            if email_verification(email):
                message = email_verification(email)
                return render(request, 'login/reset.html', {'message':message})
            try:
                user = models.User.objects.get(email=email)
            except:
                message = '请检查填写的邮箱是否正确!'
                return render(request, 'login/reset.html', {'message':message})
            
            code = make_reset_password_confirm_string(user)
            send_reset_password_email(email, code)

            message = '邮件已发送,请查收邮件并通过邮件中的链接修改密码!'
            return render(request, 'login/reset.html', {'message':message})

        return render(request, 'login/reset.html', {'message':message})
    return render(request, 'login/reset.html', {'message':message})

def resetpassword(request):
    code = conditional_escape(request.GET.get('code', ''))
    message = ''
    if request.method == 'POST':
        message = '请检查填写的内容是否符合格式!'
        try:
            confirm = models.ResetString.objects.get(code=code)
        except:
            message = '无效的修改密码链接!'
            return render(request, 'login/resetpassword.html', {'message':message, 'code':''})

        c_time = confirm.c_time
        user = confirm.user
        password = conditional_escape(request.POST.get('password', ''))
        password_repeat = conditional_escape(request.POST.get('password_repeat', ''))
        now = datetime.datetime.now()
        now = now.replace(tzinfo=pytz.timezone('UTC'))
        if now > c_time + datetime.timedelta(settings.CONFIRM_DAYS):
            message = '邮件已过期,请重新重置密码!'
            confirm.delete()
            return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
        
        if password and password_repeat:
            if password != password_repeat:
                message = '两次输入的密码不同!'
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})
            if password_verification(password):
                message = password_verification(password)
                return render(request, 'login/resetpassword.html', {'message':message, 'code':code})

            user.password = hashcode(password_repeat)
            user.save()
            confirm.delete()

            response = redirect('/login/login/')
            try:
                response.delete_cookie(key=hashcode('LZLBlog'))
                request.session.flush()
            except:
                request.session.flush()
            return response

        return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 
    return render(request, 'login/resetpassword.html', {'message':message, 'code':code}) 

def index(request, name):
    try:
        user = models.User.objects.get(name=name)
        username = user.name
    except:
        user = None
        username = None
    if not(user):
        limits = 1
        render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    elif not(request.session.get('is_login', '')) or request.session.get('user_name','') != user.name:
        limits = 1
        return render(request, 'login/index.html', {'user':user, 'limits':limits, 'username':username, 'login':request.session.get('is_login', '')})
    else:
        limits = 0

    if request.method == "POST":
        big_detail = request.POST.get("big_detail", "")
        if big_detail:
            user.big_detail = big_detail
            user.save()
        
        small_description = conditional_escape(request.POST.get('small_description', ''))
        if small_description:
            user.small_description = small_description
            user.save()

        try:
            avatar = request.FILES['avatar']
        except:
            avatar = None
        if avatar:
            user.avatar = avatar
            user.save()

    return render(request, 'login/index.html', {'user':user, "limits":limits, 'username':username, 'login':request.session.get('is_login', '')})

@csrf_exempt
def upload_image(request):
    import os

    if request.method == "POST":
        file_obj = request.FILES['file']
        file_name_suffix = file_obj.name.split(".")[-1]
        if file_name_suffix not in ["jpg", "png", "gif", "jpeg"]:
            return JsonResponse({"message": "错误的文件格式"})

        upload_time = timezone.now()
        path = os.path.join(
            settings.MEDIA_ROOT,
            'tinymce',
            str(upload_time.year),
            str(upload_time.month),
            str(upload_time.day)
        )

        if not os.path.exists(path):
            os.makedirs(path)

        file_path = os.path.join(path, file_obj.name)

        file_url = f'{settings.MEDIA_URL}tinymce/{upload_time.year}/{upload_time.month}/{upload_time.day}/{file_obj.name}'
        if os.path.exists(file_path):
            return JsonResponse({'message': '文件已存在', 'location': file_url})

        with open(file_path, 'wb+') as f:
            for chunk in file_obj.chunks():
                f.write(chunk)

        return JsonResponse({'message': '上传图片成功', 'location': file_url})
    return JsonResponse({'detail': '错误的请求'})

@requires_csrf_token
def page_not_found(request, exception):
    return render(request, 'login/404.html')

下篇再见~