django笔记(3)

1.cookie

  • HTTP被设计为”⽆态“,这⼀次请求和下⼀次请求 之间没有任何状态保持,我们⽆法根据请求的任何⽅⾯(IP地址,⽤户代理等)来识别来⾃同⼀⼈的连续请求。实现状态保持的⽅式:在客户端或服务器端存储与会话有关的数据(客户端与服务器端的⼀次通信,就是⼀次会话)

  • 方法

set_cookie(self, key, value='', max_age=None, expires=None, path='/',domain=None, secure=False, httponly=False, samesite=None):
  • key:cookie的名称
  • value:cookie的值
  • max_age:cookie的有效时间,设置为None,关闭浏览器时关闭
  • expires:过期时间
  • path:cookie的作用路径
  • domain:cookie作用的域名
  • secure:需要https传递cookie
  • httponly:仅http传输,不允许js获取

set_signed_cookie(self, key, value, salt='', **kwargs)

  • 对cookie的value加密,其他的一样
HttpResponse.delete_cookie(key, path='/', domain=None)
  • 删除cookie

1.1.案例

  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="{% url 'app:login' %}" method="post">
    {% csrf_token %}
    用户名:<input type="text" name="username"> <br>
    密码: <input type="password" name="password"> <br>
    <input type="submit">
</form>
</body>
</html>
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
{% if username %}
{{ username }} &nbsp;<a href="{% url 'app:logout' %}">退出登录</a>
{% else %}
    你还有登录,<a href="{% url 'app:login' %}">点击登录</a>
{% endif %}
</body>
</html>
  • urls.py
from django.urls import path
from app import views

app_name = 'app'

urlpatterns = [
    path('login/', views.login, name="login"),
    path('logout/', views.logout, name="logout"),
    path('', views.home, name="home")
]
  • views.py
from datetime import datetime, timedelta
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from app.models import Account


def login(request: HttpRequest):
    if request.method == "POST":
        username = request.POST.get('username', "")
        password = request.POST.get('password', "")
        user = Account.objects.get(username=username, password=password)
        if user:
            response = render(request, 'app/index.html', locals())
            response.set_cookie('username', username, expires=datetime.now() + timedelta(days=3))
            return response
    return render(request, 'app/login.html')


def logout(request: HttpRequest):
    res = render(request, 'app/index.html')
    res.delete_cookie('username')
    return res


def home(request: HttpRequest):
    username = request.COOKIES.get('username')
    return render(request, 'app/index.html', locals())

2.session

  • cookie看似解决了HTTP(短连接、⽆状态)的会话保持问题,但把全部⽤户数据保存在客户端,存在安全隐患,于是session出现了。我们可以 把关于⽤户的数据保存在服务端,在客户端cookie⾥加⼀个sessionID(随机字符串)。

  • 其⼯作流程:

  • (1)、当⽤户来访问服务端时,服务端会⽣成⼀个随机字符串;

  • (2)、当⽤户登录成功后 把 {sessionID :随机字符串} 组织成键值对加到cookie⾥发送给⽤户;

  • (3)、服务器以发送给客户端 cookie中的随机字符串做键,⽤户信息做值,保存⽤户信息;

  • (4)、再访问服务时客户端会带上sessionid,服务器根据sessionid来确认⽤户是否访问过⽹站

2.1.案例

  • login.html、index.html、urls.py和前面cookie一样
  • views.py
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render

# Create your views here.
from app.models import Account


def login(request: HttpRequest):
    if request.method == "POST":
        username = request.POST.get('username', "")
        password = request.POST.get('password', "")
        user = Account.objects.get(username=username, password=password)
        if user:
            response = render(request, 'app/index.html', locals())
            # response.set_cookie('username', username, expires=datetime.now() + timedelta(days=3))
            request.session['username'] = username
            return response
    return render(request, 'app/login.html')


def logout(request: HttpRequest):
    res = render(request, 'app/index.html')
    # res.delete_cookie('username')
    del request.session['username']
    return res


def home(request: HttpRequest):
    # username = request.COOKIES.get('username')
    username = request.session.get('username', "")
    return render(request, 'app/index.html', locals())

3.cookie和session简单区别

  • session将数据存储与服务器端 cookie存储在客户端

  • cookie 存储在客户端,不安全,sess存储在服务器端,客户端只存sesseionid,安全

  • cookie在客户端存储值有⼤⼩的限制,⼤约⼏kb。session没有限制

4.分页

  • Paginator分页器

  • 实例化:Paginator(<query_set查询集>,每⻚显示数据的条数)

  • 方法

    • page(num) 返回page对象 如果给定的⻚码不存在 则抛出异常
  • 属性

    • count 分⻚对象的个数

    • num_pages 总⻚数

    • page_range ⻚码的列表

  • page对象

类别名称说明
属性object_list当前⻚码上的所有数据
属性number当前⻚码值
属性paginator返回Paginator的对象
⽅法has_next是否有下⼀⻚
⽅法has_previous是否有上⼀⻚
⽅法has_other_pages是否有上⼀⻚ 或者下⼀⻚
⽅法next_page_number返回下⼀⻚的⻚码
⽅法previous_page_number返回上⼀⻚的⻚码
⽅法len返回当前⻚数据的个数

4.1.实例

  • url.py
path('page/<int:page>', views.page_test, name='page')
  • views.py
def page_test(request: HttpRequest, page=1):
    accounts = Account.objects.all()
    paginator = Paginator(accounts, 10)
    pageobj = paginator.page(page)
    return render(request, 'list.html', locals())
  • list.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table border="1" cellspacing="0" width="80%">
    <tr>
        <td>用户名</td>
        <td>密码</td>
    </tr>
    {% for account in pageobj.object_list %}
        <tr>
            <td>{{ account.username }}</td>
            <td>{{ account.password }}</td>
        </tr>
    {% endfor %}
</table>
<div>
{#    paginator.page_range 页码列表   #}
    {% for page in paginator.page_range %}
        <a href="{% url 'app:page' page %}">{{ page }}</a>
    {% endfor %}
</div>
</body>
</html>

5.form表单

  • 概要
通常情况下,我们需要⾃⼰⼿动在HTML⻚⾯中,编写form标签和其内的其它元
素。但这费时费⼒,⽽且有可能写得不太恰当,数据验证也⽐较麻烦。有鉴于此,
Django在内部集成了⼀个表单模块,专⻔帮助我们快速处理表单相关的内容。
Django的表单模块给我们提供了下⾯三个主要功能:
  • 准备和重构数据⽤于⻚⾯渲染
  • 为数据创建HTML表单元素
  • 接收和处理⽤户从表单发送过来的数据

5.1.使用

  • 有2种实用方式,一种是继承django.forms.Form,和模型类相似,第二种是集成django.froms.ModelForm,指定模型类生成form表单
  • ModelForm使用
class Shop(models.Model):
    title = models.CharField('标题', max_length=30)
    content = models.CharField('内容', max_length=20)

    class Meta:
        db_table = 'T_SHOP'
class ShopForm(ModelForm):
    class Meta:
        model = Shop
        fields = "__all__"
  • Form使用
class RegisterForm(forms.Form):
    username = forms.CharField(min_length=3,required=True,error_messages={
        'required':'用户名必须输入',
        'min_length':'用户名至少3个字符'
    })
    password = forms.CharField(min_length=3,required=True,error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    confirm = forms.CharField(min_length=3,required=True,error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    regtime = forms.DateTimeField(required=False,error_messages={
        'invalid':'日期格式错误',
    })
    sex = forms.BooleanField(required=False)

5.2.Form字段和字段属性

docs.djangoproject.com/zh-hans/3.2…

5.3.重写验证

单个字段重写验证

  • 函数名就必须为clean_字段名

  • 必须有返回值

  • 只能拿⾃⼰当前字段值

  • 错误必须是raise ValidationError('xxx')

def clean_password(self):
    if self.cleaned_data.get('password').isdigit() or self.cleaned_data.get('password').isalpha():
        raise ValidationError('密码必须包含数字和字⺟')
    else:
        return self.cleaned_data['password']

所有字段重写验证

def clean(self):
    if self.cleaned_data.get('password') != self.cleaned_data.get('password2'):
        raise ValidationError('密码不⼀致')
    else:
        return self.cleaned_data

5.4.事例

  • models.py
class User(AbstractUser):
    uid = models.AutoField(primary_key=True)
    username = models.CharField(unique=True, max_length=30)
    password = models.CharField(max_length=128)
    regtime = models.DateTimeField()
    sex = models.IntegerField(blank=True, null=True)

    class Meta:
        db_table = 'user'
  • forms.py
class RegisterForm(forms.Form):
    username = forms.CharField(min_length=3, required=True, error_messages={
        'required': '用户名必须输入',
        'min_length': '用户名至少3个字符'
    })
    password = forms.CharField(min_length=3, required=True, error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    confirm = forms.CharField(min_length=3, required=True, error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    regtime = forms.DateTimeField(required=False, error_messages={
        'invalid': '日期格式错误',
    })
    sex = forms.BooleanField(required=False)

    # 单个字段验证: clean_xxxx
    def clean_password(self):
        password = self.cleaned_data.get('password')
        print(password)
        if password and password.isdigit():
            raise ValidationError("密码不能是纯数字")
        return password

    # 全局验证
    def clean(self):
        password = self.cleaned_data.get('password', None)
        confirm = self.cleaned_data.get('confirm', '')
        print(password, confirm)
        if password != confirm:
            raise ValidationError({'confirm': "两次密码输入不一致"})
        return self.cleaned_data
  • urls.py
app_name = 'user'

urlpatterns = [
    path('userregister/', views.user_register, name='user_register'),
]
  • views.py
def user_register(request):
    if request.method == "POST":
        print(request.POST)
        form = RegisterForm(request.POST)
        if form.is_valid():
            data = form.cleaned_data
            data.pop("confirm")
            # 把用户写入数据库
            # 密码会做签名,不能手动签名加密password
            user = User.objects.create_user(**data)
            if user:
                return HttpResponse("注册成功")
            else:
                return render(request, "register.html", {'form': form})
        else:
            return render(request, "register.html", {'form': form})
    return render(request, "register.html")
  • register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<form action="{% url 'user:user_register' %}" method="post">
    {% csrf_token %}
    用户名:<input type="text" name="username">
    {% for error in form.username.errors %}
        <span>{{ error }}</span>
    {% endfor %}
    <br>
    密码:<input type="password" name="password">
    {{ form.password.errors }}
    <br>
    确认密码:<input type="password" name="confirm">
     {{ form.confirm.errors }}
    <br>
    注册时间:<input type="text" name="regtime">
     {{ form.regtime.errors }}
    <br>
    性别:<input type="radio" name="sex" value="0"><input type="radio" name="sex" value="1"> 男
     {{ form.sex.errors }}
    <br>
    <input type="submit" value="注册">
</form>
</body>
</html>
  • settings.py
AUTH_USER_MODEL = 'app.User' # appname.modelname

6.用户认证

6.1.概要

  • auth模块是Django提供的标准权限管理系统,可以提供⽤户身份认证, ⽤户组和权限管理。在INSTALLED_APPS中添加'django.contrib.auth'使⽤该APP
  • 主要操作
    • create_user 创建⽤户
    • authenticate验证登录
    • login记录用户登录状态
    • logout退出登录
    • is_authenticated判断是否登录
    • @login_required保护路由

6.2.django user对象

说明说明备注
username少于等于30个字符。 ⽤户名可以包含字⺟、数字、_、@、+、.和- 字符必选
password密码的哈希及元数据。(Django 不保存原始密码)。原始密码可以⽆限⻓⽽且可以包含任意字符。必须
is_active账户是否激活,默认True非必选
first_name少于30个字符非必选
last_name少于30个字符非必选
email邮箱地址非必选
groups与Group之间多对多非必选
is_staff是否可以登录admin站点非必选
is_superuser是否是管理员非必选
last_login用户最后一次登录非必选
date_joined用户创建时间非必选
  • is_active为false可以禁止用户登录

6.3.扩展User模型

  • django自带的user有时候不满足业务需求,django.contrib.auth.models.User也是继承⾃AbstractUser抽象基类,⽽且仅仅就是继承了AbstractUser ,没有对AbstractUser做任何的拓展

  • 在自己的app/models.py中定义用户类

class User(AbstractUser):

    phone = models.CharField(max_length=12,null=True,verbose_name="⼿机号")

class Meta(AbstractUser.Meta):

    db_table='user'
  • 在setttings.py定义AUTH_USER_MODEL,'app_name.user_model_name'
AUTH_USER_MODEL='app.user'
  • 迁移
python manage.py makemigrations
python manage.py migrate

6.4.事例

  • urls.py
from django.urls import path

from . import views

app_name = 'user'

urlpatterns = [
    path('userregister/', views.user_register, name='user_register'),
    path('login/', views.user_login, name='login'),
    path('user_info/', views.user_info, name='login'),
    path('logout/', views.user_logout, name='logout'),
]
  • forms.py
from django import forms
from django.core.exceptions import ValidationError


class RegisterForm(forms.Form):
    username = forms.CharField(min_length=3, required=True, error_messages={
        'required': '用户名必须输入',
        'min_length': '用户名至少3个字符'
    })
    password = forms.CharField(min_length=3, required=True, error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    confirm = forms.CharField(min_length=3, required=True, error_messages={
        'required': '密码名必须输入',
        'min_length': '密码至少3个字符'
    })
    regtime = forms.DateTimeField(required=False, error_messages={
        'invalid': '日期格式错误',
    })
    sex = forms.BooleanField(required=False)

    # 单个字段验证: clean_xxxx
    def clean_password(self):
        password = self.cleaned_data.get('password')
        print(password)
        if password and password.isdigit():
            raise ValidationError("密码不能是纯数字")
        return password

    # 全局验证
    def clean(self):
        password = self.cleaned_data.get('password', None)
        confirm = self.cleaned_data.get('confirm', '')
        print(password, confirm)
        if password != confirm:
            raise ValidationError({'confirm': "两次密码输入不一致"})
        return self.cleaned_data
  • views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpRequest
from django.shortcuts import render
# Create your views here.
from app.forms import RegisterForm
from app.models import User


def user_register(request):
    if request.method == "POST":
        print(request.POST)
        form = RegisterForm(request.POST)
        if form.is_valid():
            data = form.cleaned_data
            data.pop("confirm")
            # 把用户写入数据库
            # 密码会做签名,不能手动签名加密password
            user = User.objects.create_user(**data)
            if user:
                return HttpResponse("注册成功")
            else:
                return render(request, "register.html", {'form': form})
        else:
            return render(request, "register.html", {'form': form})
    return render(request, "register.html")


def user_login(request):
    if request.method == "POST":
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        user = authenticate(request, username=username, password=password)
        if user:
            login(request, user)
            return HttpResponse('登录成功')
        return render(request, 'login.html', {'msg': "用户名和密码错误"})
    return render(request, "login.html")


def user_logout(request: HttpRequest):
    logout(request)
    return HttpResponse('退出登录')


@login_required(login_url='/login')
def user_info(request):
    return HttpResponse('用户中心')
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>{{ msg }}</h2>
<form action="{% url 'user:login' %}" method="post">
    {% csrf_token %}
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="password"> <br>
    <input type="submit" value="登录">
</form>
</body>
</html>
  • register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<form action="{% url 'user:user_register' %}" method="post">
    {% csrf_token %}
    用户名:<input type="text" name="username">
    {% for error in form.username.errors %}
        <span>{{ error }}</span>
    {% endfor %}
    <br>
    密码:<input type="password" name="password">
    {{ form.password.errors }}
    <br>
    确认密码:<input type="password" name="confirm">
     {{ form.confirm.errors }}
    <br>
    注册时间:<input type="text" name="regtime">
     {{ form.regtime.errors }}
    <br>
    性别:<input type="radio" name="sex" value="0"><input type="radio" name="sex" value="1"> 男
     {{ form.sex.errors }}
    <br>
    <input type="submit" value="注册">
</form>
</body>
</html>