小白学Python-使用Django实现商城登陆功能

用户名登陆

登陆的核心思想,认证和状态保持,经过用户的认证,肯定该登陆用户是美多商场的注册用户。经过状态保持缓存用户的惟一标识信息,用于后续是否登陆的判断。html

1. 用户名登陆逻辑分析

image-20210601224743047

2. 用户名登陆接口设计

1.请求方式
选项 方案
请求方法 POST
请求地址 /login/
2.请求参数:表单
参数名 类型 是否必传 说明
username string 用户名
password string 密码
remembered string 是否记住用户
3.响应结果:HTML
字段 说明
登陆失败 响应错误提示
登陆成功 重定向到首页

3. 用户名登陆接口定义

class LoginView(View):
    """用户名登陆"""

    def get(self, request):
        """
        提供登陆界面
        :param request: 请求对象
        :return: 登陆界面
        """
        pass

    def post(self, request):
        """
        实现登陆逻辑
        :param request: 请求对象
        :return: 登陆结果
        """
        pass

4. 用户名登陆后端逻辑

class LoginView(View):
    """用户名登陆"""

    def get(self, request):
        """
        提供登陆界面
        :param request: 请求对象
        :return: 登陆界面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        实现登陆逻辑
        :param request: 请求对象
        :return: 登陆结果
        """
        # 接受参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')

        # 校验参数
        # 判断参数是否齐全
        if not all([username, password]):
            return http.HttpResponseForbidden('缺乏必传参数')

        # 判断用户名是不是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入正确的用户名或手机号')

        # 判断密码是不是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('密码最少8位,最长20位')

        # 认证登陆用户
        user = authenticate(username=username, password=password)
        if user is None:
            return render(request, 'login.html', {'account_errmsg': '用户名或密码错误'})

        # 实现状态保持
        login(request, user)
        # 设置状态保持的周期
        if remembered != 'on':
            # 没有记住用户:浏览器会话结束就过时
            request.session.set_expiry(0)
        else:
            # 记住用户:None表示两周后过时
            request.session.set_expiry(None)

        # 响应登陆结果
        return redirect(reverse('contents:index'))

多帐号登陆

Django自带的用户认证系统只会使用用户名去认证一个用户。因此咱们为了实现多帐号登陆,用户名、手机号或者第三方登录,就须要自定义认证后端,采用其余的惟一信息去认证一个用户python

自定义用户认证后端步骤ajax

  • 在users应用中新建utils.py文件
  • 新建类,继承自ModelBackend
  • 重写认证authenticate()方法
  • 分别使用用户名和手机号查询用户
  • 返回查询到的用户实例

1. 自定义用户认证后端

users.utils.py
from django.contrib.auth.backends import ModelBackend
import re
from .models import User


def get_user_by_account(account):
    """
    根据account查询用户
    :param account: 用户名或者手机号
    :return: user
    """
    try:
        if re.match('^1[3-9]\d{9}$', account):
            # 手机号登陆
            user = User.objects.get(mobile=account)
        else:
            # 用户名登陆
            user = User.objects.get(username=account)
    except User.DoesNotExist:
        return None
    else:
        return user


class UsernameMobileAuthBackend(ModelBackend):
    """自定义用户认证后端"""

    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        重写认证方法,实现多帐号登陆
        :param request: 请求对象
        :param username: 用户名
        :param password: 密码
        :param kwargs: 其余参数
        :return: user
        """
        # 根据传入的username获取user对象。username能够是手机号也能够是帐号
        user = get_user_by_account(username)
        # 校验user是否存在并校验密码是否正确
        if user and user.check_password(password):
            return user

2. 配置自定义用户认证后端

1.Django自带认证后端源码

image-20210601224911621

2.配置自定义用户认证后端
# 指定自定义的用户认证后端
AUTHENTICATION_BACKENDS = ['users.utils.UsernameMobileAuthBackend']

3. 测试自定义用户认证后端

image-20210601224932855

首页用户名展现

image-20210601225115949

1. 首页用户名展现方案

方案一django

  • 模板中 request 变量直接渲染用户名
  • 缺点:不方便作首页静态化
{% if user.is_authenticated %}
    <div class="login_btn fl">
        欢迎您:<em>{{ user.username }}</em>
        <span>|</span>
        <a href="#">退出</a>
    </div>
    {% else %}
    <div class="login_btn fl">
        <a href="login.html">登陆</a>
        <span>|</span>
        <a href="register.html">注册</a>
    </div>
{% endif %}

方案二后端

  • 发送ajax请求获取用户信息
  • 缺点:须要发送网络请求
<div class="login_btn fl">
    {# ajax渲染 #}
</div>

方案三浏览器

  • Vue读取cookie渲染用户信息
<div v-if="username" class="login_btn fl">
    欢迎您:<em>[[ username ]]</em>
    <span>|</span>
    <a href="#">退出</a>
</div>
<div v-else class="login_btn fl">
    <a href="login.html">登陆</a>
    <span>|</span>
    <a href="register.html">注册</a>
</div>

结论:缓存

  • 对比此三个方案,咱们在本项目中选择 方案三

实现步骤:cookie

  • 注册或登陆后,用户名写入到cookie
  • Vue渲染主页用户名

2. 用户名写入到cookie

# 响应注册结果
response = redirect(reverse('contents:index'))

# 注册时用户名写入到cookie,有效期15天
response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

return response
# 响应登陆结果
response = redirect(reverse('contents:index'))

# 登陆时用户名写入到cookie,有效期15天
response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

return response

3. Vue渲染首页用户名

1.index.html
<div v-if="username" class="login_btn fl">
    欢迎您:<em>[[ username ]]</em>
    <span>|</span>
    <a href="#">退出</a>
</div>
<div v-else class="login_btn fl">
    <a href="login.html">登陆</a>
    <span>|</span>
    <a href="register.html">注册</a>
</div>
2.index.js
mounted(){
    // 获取cookie中的用户名
    this.username = getCookie('username');
},

退出登陆

退出登陆的核心思想就是清理登陆时缓存的状态保持信息。因为首页中用户名是从cookie中读取的。因此退出登陆时,须要将cookie中用户名清除。网络

1. logout()方法介绍

  1. 退出登陆:session

    • 回顾登陆:将经过认证的用户的惟一标识信息,写入到当前session会话中
    • 退出登陆:正好和登陆相反(清理session会话信息)
  2. logout()方法:

    • Django用户认证系统提供了logout()方法
    • 封装了清理session的操做,帮助咱们快速实现登出一个用户
  3. logout()位置:

    • django.contrib.auth.__init__.py文件中
logout(request)

2. logout()方法使用

class LogoutView(View):
    """退出登陆"""

    def get(self, request):
        """实现退出登陆逻辑"""
        # 清理session
        logout(request)
        # 退出登陆,重定向到登陆页
        response = redirect(reverse('contents:index'))
        # 退出登陆时清除cookie中的username
        response.delete_cookie('username')

        return response

判断用户是否登陆

1. 展现用户中心界面

class UserInfoView(View):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        return render(request, 'user_center_info.html')

需求:

  • 当用户登陆后,才能访问用户中心。
  • 若是用户未登陆,就不容许访问用户中心,将用户引导到登陆界面。

实现方案:

  • 须要判断用户是否登陆。
  • 根据是否登陆的结果,决定用户是否能够访问用户中心。

2. is_authenticate 判断用户是否登陆

介绍:

  • Django用户认证系统提供了方法request.user.is_authenticated()来判断用户是否登陆。
  • 若是经过登陆验证则返回True。反之,返回False
  • 缺点:登陆验证逻辑不少地方都须要,因此该代码须要重复编码好屡次。
class UserInfoView(View):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        if request.user.is_authenticated():
            return render(request, 'user_center_info.html')
        else:
            return redirect(reverse('users:login'))

3. login_required装饰器 判断用户是否登陆

  • Django用户认证系统提供了装饰器

    login_required

来判断用户是否登陆。

  • 内部封装了is_authenticate
  • 位置:django.contrib.auth.decorators
  • 若是经过登陆验证则进入到视图内部,执行视图逻辑。
  • 若是未经过登陆验证则被重定向到

    LOGIN_URL

配置项指定的地址。

  • 以下配置:表示当用户未经过登陆验证时,将用户重定向到登陆页面。

    LOGIN_URL = '/login/'

1.装饰as_view()方法返回值

提示:

  • login_required装饰器能够直接装饰函数视图,可是本项目使用的是类视图。
  • as_view()方法的返回值就是将类视图转成的函数视图。

结论:

  • 要想使用login_required装饰器装饰类视图,能够间接的装饰as_view()方法的返回值,以达到预期效果。
url(r'^info/$', login_required(views.UserInfoView.as_view()), name='info'),
class UserInfoView(View):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        return render(request, 'user_center_info.html')

2.定义View子类封装login_required装饰器

  • 提示:LoginRequired(object)依赖于视图类View,复用性不好。
url(r'^info/$', views.UserInfoView.as_view(), name='info'),
class LoginRequired(View):
  """验证用户是否登录"""

  @classmethod
  def as_view(cls, **initkwargs):
      # 自定义as_view()方法中,调用父类的as_view()方法
      view = super().as_view()
      return login_required(view)


class UserInfoView(LoginRequired):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        return render(request, 'user_center_info.html')

3.定义obejct子类封装login_required装饰器

  • 提示:LoginRequired(object)不依赖于任何视图类,复用性更强。
url(r'^info/$', views.UserInfoView.as_view(), name='info'),
class LoginRequired(object):
  """验证用户是否登录"""

  @classmethod
  def as_view(cls, **initkwargs):
      # 自定义as_view()方法中,调用父类的as_view()方法
      view = super().as_view()
      return login_required(view)


class UserInfoView(LoginRequired, View):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        return render(request, 'user_center_info.html')

4.定义验证用户是否登陆扩展类

  • 提示:定义扩展类方便项目中导入和使用(meiduo_mall.utils.views.py)
class LoginRequiredMixin(object):
  """验证用户是否登陆扩展类"""

  @classmethod
  def as_view(cls, **initkwargs):
      # 自定义的as_view()方法中,调用父类的as_view()方法
      view = super().as_view()
      return login_required(view)
 class UserInfoView(LoginRequiredMixin, View):
    """用户中心"""

    def get(self, request):
        """提供我的信息界面"""
        return render(request, 'user_center_info.html')

4. 登陆时next参数的使用

1.next参数的效果
http://127.0.0.1:8000/login/?next=/info/

2.next参数的做用

  • 由Django用户认证系统提供,搭配login_required装饰器使用。
  • 记录了用户未登陆时访问的地址信息,能够帮助咱们实如今用户登陆成功后直接进入未登陆时访问的地址。
# 响应登陆结果
next = request.GET.get('next')
if next:
    response = redirect(next)
else:
    response = redirect(reverse('contents:index'))
相关文章
相关标签/搜索