Django应用-1-用户账号及鉴权体系

1、项目背景

用户账号及鉴权体系几乎是任何一个web应用都需要的底层架构,目前正在学习Django,作为练手,本文从业务逻辑到技术实现,完整走一遍用户鉴权体系搭建的流程。

2、业务逻辑层

2.1、用户账号及鉴权体系的场景梳理

在用户视角来看,有如下场景:

  • 注册
  • 登录
  • 绑定手机、邮箱
  • 账号信息维护
  • 找回密码
    • 通过手机找回
    • 通过邮箱找回
  • 权限级别的升降
  • 登出

在运营人员来看,有如下场景:

  • 根据不同用户级别,设定不同权限
    • 功能权限
    • 数据权限

2.2、用户账号需要的信息

  • username
  • 昵称,nickname
  • password
  • email,可以通过email登录、找回密码。
  • phone,可以通过手机登录、找回密码。
  • 可以关联微信开放平台的openid,可以通过微信扫码登录。
  • verify_code,存储验证码信息
  • email_is_verified,邮箱是否被绑定了
  • phone_is_verified,手机号是否被绑定了
  • avatar,头像,在后期的很多场景中都会用到。
  • grade,用户等级,不同的等级会有不同的权限

2.3、注册的时候需要哪些信息

站在用户的角度:

  • 希望尽量简单,需要的信息越少越好。
  • 希望有一个主键,能够在丢失号码的时候找回。

站在运营人员的角度:

  • 希望拿到的信息越多越好。
  • 又不希望因为要太多信息而给用户吓跑。
  • 必须需要验证码,防止用户刷。

结论:

  • 注册的时候,只要username,password,验证码。
  • 可以在后台绑定手机号、邮箱。
  • 绑定手机和邮箱时,需要验证信息真实性。

3、技术实现层

3.1、基础准备

首先,建议一个User的App,在terminal输入如下指令:

python3 manage.py startapp user

运行完成后,会发现生成了如下文件:

user/
    __init__.py
    modeks.py
    tests.py
    view.py

接下来,在settings.py里面,配置installed_app,将user.apps.UserConfig配置进来:

INSTALLED_APPS = [
…
'user.apps.UserConfig',
…
]

第三步,将userApp加入到站点的urls.py中的urlpatterns内:

urlpatterns = [
		...
    path('user/', include('user.urls')),
		...
]

第四步,在user项目目录下新建urls.py,设定如下内容:

app_name = 'user'
urlpatterns = [
    # 初始页面
    path('', views.index, name='index'),
    # 注册
    path('registrition', views.registrition , name = 'registrition'),
    # 登录
    path('login/',auth_views.LoginView.as_view(template_name = 'login.html', redirect_field_name='/') , name='login'),
    # 登出
    path('logout/',auth_views.LogoutView.as_view(next_page='/'),name = 'logout'),
    # 找回密码
    path('find_password',views.find_password,name='find_password'),
    # 修改密码
    path('change_password',views.change_password,name='change_password'),
    # 绑定邮箱
    path('bind_email_v2',views.bind_email_v2,name='bind_email_v2'),
    path('bind_email_v2/<str:e>',views.bind_email_v2,name='bind_email_v2'),
    path('clear_email',views.clear_email,name='clear_email'),
    # 绑定手机
    path('bind_phone',views.bind_phone,name='bind_phone'),
    # 个人信息
    path('profile',views.profile,name='profile'),
    path('change_profile',views.change_profile,name='change_profile'),
]

第五步,修改user项目下的views.py,设定如下内容:

from django.shortcuts import render
from django.shortcuts import HttpResponse,HttpResponseRedirect
from django.core.mail import send_mail


# Create your views here.
def index(request):
    context = {}
    return render(request, 'index.html', context)


def login(request):
    context = {}
    return render(request, 'login.html', context)


def registrition(request):
    context = {}
    return render(request, 'registrition.html', context)


def send_email(request):
    send_mail(
    )
    return HttpResponse('发送成功!')

3.2、建立自定义的用户Model

Django自带的User Model包含如下字段:

序号字段说明
1ID
2password密码
3last_login最近一次登录时间
4is_superuser是否是超级管理员
5username用户名
6first_name
7last_name
8email邮箱地址
9is_staff是否是员工
10is_active是否是激活状态
11date_joined加入时间

但是我们希望能够有手机号、微信openID等更多信息,所以需要扩展Django自带的UserModel,通常来说,有两种办法来实现这种扩展:a. 给原来的用户model表增加一个扩展表,扩展表与原表是1对1的关系;b. 重写一个自定义的UserModel。如果我们开始了一个新项目,哪怕默认的User Model能够满足初始需求,也强烈推荐你使用方案2,一方面是方便扩展,另一方面是进行用户信息操作时,不用跨很多个表去查。我们先简单了解一下方案1,再重点看方案2。

3.2.1、使用一对一模型扩展用户表信息

如果想要在给user存储更多的信息,可以使用一个:OneToOneField,这个一对一的model通常被叫作:profile model,比如我想给用户扩展两个信息:phone和openID,那么需要修改models.py:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phone = models.CharField(max_length=16,blank=True)
    openid = models.CharField(max_length=50,blank=True)
    verify_code = models.CharField(max_length=8,blank=True)
    invalid_time =models.DateTimeField(blank=True)
		phone_is_verified = models.BooleanField(blank=True,null=True)
    email_is_verified = models.BooleanField(blank=True,null=True)
    avatar = models.ImageField(verbose_name='avatar', upload_to=user_directory_path, blank=True, null=True)
序号字段类型说明
1phoneCharField(max_length=16)用户的手机号码
2openIDCharField(max_length=50)用户的openid

如果想要在管理后台增加上面说的自定义字段,可以在你的应用名/admin.py里面,增加一个InlineModelAdmin(内联模型管理后台),然后将它添加到UserAdmin类内。

admin.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from stock_analysis.models import Profile

# Register your models here.
class ProfileInline(admin.StackedInline):
    model = Profile
    can_delete = False
    verbose_name_plural = 'profile'

class UserAdmin(BaseUserAdmin):
    inlines = (ProfileInline,)

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

3.2.2、用自定义的用户类

Django允许重写默认的user model,如果想在应用里使用对应的用户类,你可以在settings里关联一个自定义的model:

# 账号体系配置
AUTH_USER_MODEL = 'user.myUser'

上面的常量里,myapp是指一个django应用(需要在INSTALLED_APP里面进行配置),后面的MyUser是指你希望在使用的自定义User Model。

最简单的创建一个自定义用户类的方式是从AbstractUser继承。AbstractUser继承自AbstractBaseUser,它提供了user model的核心实现,在使用它的时候,需要声明几个关键实现信息:

  • 【必填】USERNAME_FIELD
  • 【选填】EMAIL_FIELD
  • 【选填】REQUIRED_FIELDS

models.py:

from django.db import models
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
    phone = models.CharField(max_length=16,blank=True, null=True)
    openid = models.CharField(max_length=50,blank=True, null=True)
    verify_code = models.CharField(max_length=8,blank=True, null=True)
    invalid_time = models.DateTimeField(blank=True, null=True)
		phone_is_verified = models.BooleanField(blank=True,null=True)
    email_is_verified = models.BooleanField(blank=True,null=True)
    avatar = models.ImageField(verbose_name='avatar', upload_to=user_directory_path, blank=True, null=True)

admin.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import MyUser

# Register your models here.
admin.site.register(MyUser,UserAdmin)

都搞定之后,按顺序执行,安装对应的表:

python3 manage.py makemigrations
python3 manage.py migrate

中间有遇到过一个报错:

踩坑记录:
写完上面的自定义用户类后,先运行python3 manage.py makemigration,再运行python3 manage.py migrate,报错,如下所示:
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency stock_analysis.0001_initial on database 'default'.
处理方式如下:
1. 先makemigrations
2. 打开settings.py,注释掉INSTALLED_APP里面的’django.contrib.admin’,
3. 打开urls.py,注释掉urlpatterns里面的admin,
4. 再migrate就不会报错了。
5. 最后把上面的两个注释恢复回来。

接下来,运行:

python3 manage.py createsuperuser

即可以创建一个应用的超级管理员。

3.3、注册功能

接下来我们完成注册功能,它的url在前面已经配置完成了,接下来配置view层。

在注册页面里面需要有一个表单,Django已经默认创建了一些内建表单,有一些表单能够适用于任何一个AbstractBaseUser的子类:

  • AuthenticationForm
  • SetPasswordForm
  • PasswordChangeForm
  • AdminPasswordChangeForm

下面这个表单是要在自定义的用户Model满足一定要求的时候才能被使用:

  • PasswordResetForm:假设user model有一个email地址的字段,并且这个字段可以通过get_email_field_name()返回,用来验证这个用户;并且一个is_active字段用来防止给非活跃的用户复活。

还有两个表单是需要重写的:

  • UserCreationForm
  • UserChangeForm

在这一部分,我们需要重写用户注册表单。先将原表单的信息拆解,了解它的逻辑:

创建forms.py,里面新建MyUserCreationForm,内容如下:

# 创建用户的表单
class MyUserCreationForm(UserCreationForm):
    # 错误信息字典
    error_messages = {
        "password_mismatch": _("两次输入的密码不一致."),
        "same_username" : _("用户名已经被注册"),
    }
    # 定义表单展示名
    captcha = CaptchaField(label='验证码')

    # 进行用户名的验证
    def clean_username(self):
        username = self.cleaned_data['username']
        same_username = myUser.objects.filter(username = username)
        if same_username:
            raise ValidationError(
                self.error_messages["same_username"],
                code="same_username"
            )
        return username

在view层进行引用:

def registrition(request):
    # 展示页
    if request.user.is_authenticated:
        return HttpResponseRedirect(reverse("stock_analysis:index"))
    if request.method == 'GET':
        register_form = CustomUserCreationForm()
        context = {
            'form' : register_form,
            'u' : request.user.is_authenticated
        }
    # 提交后的验证
    if request.method == 'POST':
        register_form = CustomUserCreationForm(request.POST)
        if register_form.is_valid():
            register_form.save()
            return HttpResponse("注册成功!")
        context = {
            'form' : register_form,
            'email' : register_form.fields
        }
    return render(request, 'registrition.html', context)

最后,修改template,就可以啦:

<form method="post" action="{% url 'user:registrition' %}">
        {% csrf_token %}
        <table>
            <tr>
                <td>{{ form.username.label_tag }}</td>
                <td>{{ form.username }}</td>
            </tr>
            <tr>
                <td>{{ form.password1.label_tag }}</td>
                <td>{{ form.password1 }}</td>
            </tr>
            <tr>
                <td>{{ form.password2.label_tag }}</td>
                <td>{{ form.password2 }}</td>
            </tr>
            {{ form.captcha.errors }}
            <tr>
                <td>{{ form.captcha.label_tag }}</td>
                <td>{{ form.captcha }}</td>
            </tr>
        </table>

        <input type="submit" value="注册">
        <input type="hidden" name="next" value="{{ next }}">
    </form>

3.4、图片验证码逻辑

为了防止有人刷,那么增加验证码的验证功能:

使用django-simple-captcha组件

Step1.

在settings.py里面,将captcha放到installed-app里面。

Step2.

captcha需要在数据库内建自己的表,所以:python3 manage.py migrate

Step3.

在根目录的urls.py里面增加配置:path('captcha/',include('captcha.urls'))

Step4.

在自定义的表单内增加:

# 定义表单展示名
captcha = CaptchaField(label='验证码')

Step5、

在前端页面加入验证码的显示:

	{{ form.captcha.errors }}
            <tr>
                <td>{{ form.captcha.label_tag }}</td>
                <td>{{ form.captcha }}</td>
            </tr>

Step6、

实现验证码的点击自动刷新,在前端页面增加:

<script>
        $('.captcha').click(function(){
            $.getJSON('/captcha/refresh/',function(result){
                $('.captcha').attr('src',result['image_url']);
                $('#id_captcha_0').val(result['key']);
            })
        })
    </script>

3.5、管理后台显示自定义Model的字段

等到以上都配置完后,登录后台,发现显示的字段不全,所以需要自己写一下UserAdmin:

from django.contrib import admin
from .models import User

class UserAdmin(admin.ModelAdmin):
    fieldsets = [
        ('基本信息', {'fields':['username','first_name','last_name']}),
        ('联系信息',{'fields':['email','phone','openid']}),
        ('辅助信息',{'fields':['last_login','is_superuser','is_staff','is_active','date_joined','verify_code','invalid_time']})
    ]
    list_display = ('username', 'email', 'phone','last_login','date_joined')

# Register your models here.
admin.site.register(User,UserAdmin)

3.6、登录和登出

目前可以直接用自带的视图:

# 登录
    path('login/',auth_views.LoginView.as_view(template_name = 'login.html', redirect_field_name='/') , name='login'),
    # 登出
    path('logout/',auth_views.LogoutView.as_view(next_page='/'),name = 'logout'),

3.7、绑定邮箱

绑定邮箱有两种方式:

  • 给用户邮箱发送一串激活码,之后用户回到网页回填发送的激活码。
  • 给用户邮箱发送一个URL地址,用户点击此地址后,自动激活。

先采用第一种方式,此流程分为两步:

首先,要求用户填写一个邮箱,填写完成后,将邮箱保存到数据库内,并向此邮箱发送一串激活码;在此步骤中,为防止用户刷,需要增加一个数字验证码的校验功能。

之后,页面内要展示需要用户填写激活码的表单,此时,用户可以选择:更换邮箱、再次发送验证码,或者是输入激活码激活。

3.7.1、发送邮件能力

发送邮件使用django自带的send_email()方法,需要在settings.py里面进行配置:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True  # 是否使用TLS安全传输协议(用于在两个通信应用程序之间提供保密性和数据完整性。)
EMAIL_USE_SSL = False  # 是否使用SSL加密,qq企业邮箱要求使用
EMAIL_HOST = 'smtp.163.com'  # 发送邮件的邮箱 的 SMTP服务器,这里用了163邮箱
EMAIL_PORT = 25  # 发件箱的SMTP服务器端口# 上面配置可以不动,下面配置修改为自己的

# 上面的内容不用动,下面的内容设置为自己的

EMAIL_HOST_USER = 'tin@163.com'  # 发送邮件的邮箱地址
EMAIL_HOST_PASSWORD = 'xxxxxx'  # 发送邮件的邮箱密码(这里使用的是授权码)
EMAIL_TO_USER_LIST = ['xxxx@foxmail.com', 'xxx@qq.com']   # 此字段是可选的,用来配置收件人列表

3.7.2、设计流程中两个步骤用到的表单

新建forms.py,填写如下代码:

# 验证邮箱的表单

class BindEmailFormStep1(forms.Form):
    email = forms.EmailField(label="邮箱")
    captcha = CaptchaField(label='验证码')
    status = forms.widgets.HiddenInput()

class BindEmailFormStep2(forms.Form):
    email = forms.EmailField(label="邮箱" ,disabled="disabled", required=False)
    bind_msg = forms.CharField(label="激活码",required=False)
    status = forms.widgets.HiddenInput()

3.7.4、View层设计

在view层,根据request.method是get还是post的不同,分别对页面进行处理,get部分处理未填写表单时的展示,其中设置了一个status变量,用来判断用户是进行到了第一步还是第二步:

def bind_email_v2(request,e=''):
    u = myUser.objects.get(id=request.user.id)
    # 对表现层进行处理
    if request.method == 'GET':
        # 1、此用户没有email记录
        if u.email == "":
            status = 0
            form = BindEmailFormStep1()
        # 2、此用户已有email记录,且已经经过了验证
        elif u.email_is_verified:
            status = 1
            return HttpResponseRedirect(reverse("user:profile"))
        # 3、此用户已有email记录,没有经过验证
        else:
            status = 2
            form = BindEmailFormStep2(initial={'email':u.email})
        # 将status值赋值给表单内的隐藏变量
        form.hidden_status = form.status.render(value=status,name="status")
        context = {'form': form, 'status':status, 'e':e}
        return render(request, 'bind_email_v2.html', context)

post部分处理回收过来的结果:

# 对提交请求进行处理
    if request.method == 'POST' :
        p = request.POST
        # 对验证码进行验证
        if p['status'] == '0':
            form = BindEmailFormStep1(request.POST)
            # 如果通过了验证,进行后续流程
            if form.is_valid():
                try:
                    # 获取系统内是否已经有此邮箱的记录了
                    ue = myUser.objects.get(email=form.cleaned_data['email'])
                    # 如果系统内没有此邮箱记录,那么生成token,发送给用户的邮箱,并将这个邮箱存到数据库内。
                except ObjectDoesNotExist:
                    # 根据request里面的id查询用户信息
                    u = myUser.objects.get(id=request.user.id)
                    # 发送验证码并将邮箱保存给这个用户
                    return send_email_base(u,form)
                # 如果出现其他错误了,那么报错
                except:
                    return HttpResponse("出错")
                # 用户的邮箱为空,但是数据库里有这个邮箱的记录
                else:
                    # 这个邮箱已经被认证了
                    if ue.email_is_verified :
                        return HttpResponseRedirect(reverse("user:bind_email_v2", kwargs={'e':'此邮箱已被其他用户占用!'}))
                    # 这个邮箱没有被认证
                    else :
                        ue.email = ""
                        ue.save()
                        return send_email_base(u,form)
            # 如果没通过验证,报错
            else:
                t = ""
                for e in form.errors:
                    t = t + e + ':' + form.errors[e][0]
                    t = t+';'
                return HttpResponseRedirect(reverse("user:bind_email_v2", kwargs={'e':t}))

        # 对激活码进行验证
        else:
            form = BindEmailFormStep2(request.POST)
            # 如果通过了验证,进行后续流程
            if form.is_valid():
                if u.verify_code == form.cleaned_data['bind_msg']:
                    u.email_is_verified = True
                    u.save()
                    return HttpResponseRedirect(reverse("user:profile"))
                else:
                    return HttpResponseRedirect(reverse("user:bind_email_v2", kwargs={'e':'激活码不对,请重新填写'}))
            else :
                t = ""
                for e in form.errors:
                    t = t + e + ':' + form.errors[e][0]
                    t = t+';'
                return HttpResponseRedirect(reverse("user:bind_email_v2", kwargs={'e':t}))

3.7.5、Template层

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>绑定邮箱</title>
</head>
<body>
{{user.id}}
:
{{user}}
{% if status == 0 %}
<!-- 此用户没有email记录 -->
<form method="post" action="{% url 'user:bind_email_v2' %}" id="form1">
    {% csrf_token %}
    <table>
        <tr>
            <td>{{ form.email.label_tag }}</td>
            <td>{{ form.email }}</td>
        </tr>
        <tr id="cccc">
            <td>{{ form.captcha.label_tag }}</td>
            <td>{{ form.captcha }}</td>
        </tr>
    </table>
    {{ form.hidden_status }}
    <input type="submit" id="send" value="发送激活码">
    {% if form.errors %}
    <p>{{form.errors}}</p>
    {% endif %}
    <p id="msg1">{{e}}</p>
</form>

{% else %}
<!-- 此用户有email记录 -->
<form method="post" action="{% url 'user:bind_email_v2' %}" id="form2">
    {% csrf_token %}
    <table>
        <tr>
            <td>{{ form.email.label_tag }}</td>
            <td>{{ form.email }}</td>
        </tr>
        <tr id="bind_code" class="h">
            <td>{{ form.bind_msg.label_tag }}</td>
            <td>{{ form.bind_msg }}</td>
        </tr>
    </table>
    <td>{{ form.hidden_status }}</td>
    <input type="submit" value="激活" id="bind_submit" class="h">
    <input type="button" value="修改邮箱" id="return" class="h">
    <input type="button" value="再次发送" id="refresh" class="h">
    <p id="msg2">{{e}}</p>
    <p>{{form.errors}}</p>
    {% if form.errors %}
    <p>{{form.errors}}</p>
    {% endif %}
</form>
<form method="post" action="{% url 'user:clear_email' %}" id="form3">
    {% csrf_token %}
</form>
{% endif %}
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<script>
    $('#return').click(function(){
        $("#msg2").text("")
        $.post({
            url: "{% url 'user:clear_email' %}",
            data:($("#form3").serialize()),
            success:function(data){
                if(data === 'done'){
                    location.href = "{% url 'user:bind_email_v2' %}"
                }
                else{
                    alert(data)
                }
            }
        })
    })
</script>
</body>
</html>

3.8、个人资料页&修改资料

3.8.1、URL层

完成注册且登录后,需要有一个个人资料页,在里面会展示用户的基本信息及头像;还会有个修改资料页,可以修改头像、姓名

新建url规则:

# 个人信息
    path('profile',views.profile,name='profile'),
    path('change_profile',views.change_profile,name='change_profile'),

3.8.2、定义表单,修改Model层

在forms.py内,新建修改资料的表单:

# 修改个人资料
class ChangeProfileForm(forms.ModelForm):
    # username = forms.CharField(label="用户名",required=True,disabled=True)
    class Meta:
        model = myUser
        fields = ['avatar','first_name','last_name']

这个表单关联的是myUser的model内的avatar、first_name、last_name字段,其中,avatar是一个ImageField字段,在model内是这么定义的:

avatar = models.ImageField(verbose_name='avatar', upload_to=user_directory_path, blank=True, null=True)

上述定义内,user_directory_path是一个方法,用来对文件进行命名

def user_directory_path(instance, filename):
    ext = filename.split('.').pop()
    filename = '{0}/{1}-{2}-{3}.{4}'.format(datetime.datetime.now().strftime('%Y%m%d'),datetime.datetime.now().strftime('%I%M%S'),instance.username, instance.id,ext)
    return os.path.join( filename) # 系统路径分隔符差异,增强代码重用性

3.8.3、View层

view层内,定义资料页及修改资料页:

# 个人信息页
def profile(request):
    context = {}
    return render(request, 'profile.html', context)


def change_profile(request):
    u = myUser.objects.get(id=request.user.id)
    my_profile = ChangeProfileForm(initial={
        'avatar': u.avatar,
        'first_name': u.first_name,
        'last_name': u.last_name
    })
    context = {'profile': my_profile}
    if request.method == 'POST':
        my_profile = ChangeProfileForm(request.POST,request.FILES,instance=u)
        if my_profile.is_valid():
            u.save()
            return HttpResponseRedirect(reverse("user:profile"))
    return render(request, 'change_profile.html', context)

3.8.4、Template层

资料页:

{% if user.avatar %}
    <img  width="150" height="150" src="/media/{{user.avatar}}" alt="">
{% else %}
    <img  width="150" height="150" src="{% static 'default/avatar.png' %}" alt="">
{% endif %}
    <table>
        <tr>
            <td>用户ID:</td>
            <td>{{user.id}}</td>
        </tr>
        <tr>
            <td>用户名:</td>
            <td>{{user}}</td>
        </tr>
        <tr>
            <td>姓:</td>
            <td>{{user.last_name}}</td>
        </tr>
        <tr>
            <td>名:</td>
            <td>{{user.first_name}}</td>
        </tr>
        <tr>
            <td>邮箱:</td>
            {% if user.email == "" %}
            <td></td>
            <td><a href="{% url 'user:bind_email_v2' %}">绑定邮箱</a></td>
            {% else %}
                <td>
                    {{user.email}}
                </td>
                <td>
                    {% if user.email_is_verified %}
                        <span>已通过验证</span>
                    {% else %}
                        <a href="{% url 'user:bind_email_v2' %}">现在激活</a>
                    {%endif%}
                </td>
            {% endif %}

        </tr>
        <tr>
            <td>手机号码:</td>
            {% if not user.phone %}
            <td></td>
            <td><a href="{% url 'user:bind_phone' %}">绑定手机</a></td>
            {% else %}
                <td>
                    {{user.phone}}
                </td>
                <td>
                    {% if user.phone_is_verified %}
                    <span>已通过验证</span>
                    {% else %}
                    <a href="{% url 'user:bind_phone' %}">现在激活</a>
                    {%endif%}
                </td>
            {% endif %}
        </tr>
        <tr>
            <td>加入时间:</td>
            <td>{{user.date_joined}}</td>
        </tr>
        <tr>
            <td>最近登录:</td>
            <td>{{user.last_login}}</td>
        </tr>

    </table>

    <p><a href="{% url 'user:change_profile' %}">修改资料</a></p>
    <p><a href="{% url 'stock_analysis:index' %}">站点主页</a></p>
    <p><a href="{% url 'user:logout' %}">登出</a></p>

修改资料页:

<form method="post" enctype="multipart/form-data" action="{% url 'user:change_profile' %}" id="form1">
    {% csrf_token %}
    <table>
        {{profile}}
    </table>
    {% if form.errors %}
        <p>{{form.errors}}</p>
    {% endif %}
    <input type="submit" value="保存资料">
</form>

4、尾声

通过以上流程,我们完整地搭建了一个基于django的用户账号体系,包含注册、修改资料、上传头像、邮箱验证、手机号验证等,里面非常多的知识点,是一个很好的练手项目。

Leave a comment

Your email address will not be published. Required fields are marked *