[django] 会话保持及Form表单

前言

你可以从这篇文章中学习到:

  • 会话(session)状态保持, 及在django框架中的应用
  • django中的form表单的使用及应用

并且我会在本文中演示使用form表单编写 学生详情页
<ps: 学生详情页属于学生管理系统的一个模块, 该系统之后会整理到另一篇文章中>

会话保持及Form表单

session保存状态

上面我们使用过cookie来实现将数据保存在客户端中, 但是浏览器存储cookie的方式不太安全

那有没有更好些的来存储登入状态的方式呢? 这里有引入了session会话保持

  1. 首先http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态
  2. 客户端与服务器端的一次通信,就是一次会话实现 状态保持 的方式:在客户端或服务器端存储与会话有关的数据
  3. 保持方式包括cookie、session,会话一般指session对象
  4. 使用cookie方式,所有数据都会存储在客户端, 很不安全,因此注意不要存储敏感信息
  5. 使用session方式,所有数据存储在服务器端,session技术其实是基于cookie技术的, 在使用时, 它的服务端会给每一个客户端生成一个独一无二的session_id(散列值, 几乎不可能重复), 然后又会通过cookie传输到客户端, 在客户端的cookie中, 存储的就是session_id
  6. 服务端可以通过分析用户从客户端传输过来的session_id来分辨用户信息
  7. 状态保持的目的是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据
  • 注意:不同的请求者之间不会共享这个数据,与请求者一一对应

启用session

在settings.py文件中, 以下session设置默认是都有的启用的:

INSTALLED_APPS = [
	...
    'django.contrib.sessions',
	...
]	# sessuions是作为一个django内置app存在的
MIDDLEWARE = [
   ...    'django.contrib.sessions.middleware.SessionMiddleware',
   ...
]	# session的中间件

在生成迁移之后 , 会在数据库中生成一个django_session的表格.

启用会话后,每个HttpRequest对象将具有一个session属性,它是一个类字典对象

  • get(key, default=None) 根据键获取会话的值
  • clear() 清除所有会话
  • flush() 删除当前的会话数据并删除会话的Cookie
  • del request.session['member_id']删除某个session值

登陆注册案例

用户登陆的整体框架不变

  • 判断请求方式(GET,POST), 实行不同的操作
    • GET, 则展示登陆页面
    • POST, 则对用户名密码进行判断, 不同的是这里加入session技术
# 登陆
def login(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST.get('password')
        if username == 'abc' and password == '123':
            request.session['username'] = username  # 在登录中使用request.session设置一个登录的信息.
            request.session.set_expiry(5)   # 设置会话的超时时间, 5s后过期
            return redirect(reverse('firstapp:index2'))
    return render(request, 'firstapp/login.html')
#首页
def index2(request):
    username = request.session.get('username', '游客')    # 在主页面中获取设置的值,然后传给模板.
    return render(request, 'firstapp/new_index.html', context={'username': username})
# 注销
def logout(request):
    request.session.flush()     # 删除当前的会话数据并删除会话的Cookie
    return redirect(reverse('firstapp:index2'))
set_expiry 会话过期时间
- set_expiry(value):设置会话的超时时间
- 如果没有指定,则两个星期后过期
- 如果value是一个整数,会话将在values秒没有活动后过期
- 若果value是一个imedelta对象,会话将在当前时间加上这个指定的日期/时间过期
- 如果value为0,那么用户会话的Cookie将在用户的浏览器关闭时过期
- 如果value为None,那么会话永不过期

setting文件中的配置

在这里插入图片描述

可以不配置,那么都是默认的选项


django的form表单

我们实现了登录状态的保持, 接下来, 如果需要注册登录呢?

简单表单

使用
  1. 创建一个forms.py的文件, 放在指定的app当中, 然后在里面写表单.

    # 表单是通过类实现的,继承自forms.Form,然后在里面定义要验证的字段
    class RegisterForm(forms.Form):
        # 这里的类属性(e.g.CharField)和models的类属性非常相似, 但要注意的是他们是不同模块下的(forms)
        # 如果不指定类属性input的类型, 它默认就是text
        username = forms.CharField(label='用户名', max_length=20)  # 最大长度20
        password = forms.CharField(label='输入密码', max_length=8, min_length=6,
                                   # widget可以设置input的属性, PasswordInput设置为密码类型
                                   widget=forms.PasswordInput(attrs={'placeholder': '请输入长度为6-8位的密码'}),
                                   # error_messages可以设置当不满足某个属性的条件时, 抛出错误警报
                                   error_messages={'min_length': '密码长度小于6', 'max_length': '密码长度大于8'})
        password_repeat = forms.CharField(label='确认密码', widget=forms.PasswordInput())
        email = forms.EmailField()  # 邮箱类型
    
  2. 写好form还不算完, 还需要去视图函数调用, 先用GET看一下页面布局

    def register(request):
        if request.method == 'GET':
            # 实例化forms对象
            form = forms.RegisterForm()
            return render(request, 'firstapp/register.html', context={
                'form': form,
            })
    

    别忘了urlconf

    path('register/', views.register, name='register'),
    
  3. 这样一来前端就可以直接拿form去显示表单了

    <body>
        {{ form }}
    </body>
    
  4. 显示的是没有任何修饰的表单在这里插入图片描述, 但这已经是很大的进步了, 我们通过后端代码实现了前端的页面展示, 几乎没有编写前端代码!

  5. 修饰1, {{ form.as_p }}, 自动在每一个input标签外, 套上一个p标签, 加上之后再看

    在这里插入图片描述

  6. 修饰2, 虽然是用的表单方法, 但是它并没有给我们的前端代码加上form, 所以这一块就要自己设置了

    <form action="" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="提交">
    </form>
    
  7. 既然已经完善了form表单, 现在就可以从前端接收数据了(POST)

    def register(request):
        form = forms.RegisterForm()     # 实例化对象
    
        if request.method == 'POST':
            form = forms.RegisterForm(request.POST)     # 此时的对象接收了提交的数据
            # 检验字段的有效性
            if form.is_valid():
                # 通过cleaned_data可以拿到forms.py里的所有数据
                username = form.cleaned_data.get('username')
                password = form.cleaned_data.get('password')
                password_repeat = form.cleaned_data.get('password_repeat')
                email = form.cleaned_data.get('email')
                if password == password_repeat:
                    return HttpResponse('注册成功')
    
        return render(request, 'firstapp/register.html', context={
            'form': form,
        }
    

    运行, 当输入错误的密码或email时就会出现错误信息,

在这里插入图片描述

但是, 两次密码不一致时并不会出错.
  1. 我们来视图函数中加一个检验方法, 以解决上面那个问题

    class RegisterForm(forms.Form):
    ...
    	def clean(self):
            cleaned_data = super().clean() # 继承父类方法
    
            # 增加提示信息功能
            password = cleaned_data.get('password')
            password_repeat = cleaned_data.get('password_repeat')
    		# 如果两次密码不一致, 就出现错误信息
            if password_repeat != password:
                # 使用add_error(<字段名>, <提示信息>)
                self.add_error('password_repeat', '两次密码不一致')
    

    这样再运行出来, 就按照预期报错了

在这里插入图片描述

模型表单(models)

  1. 通过form表单去展示models时, 就可以直接使用模型(models)中的数据, 甚至不用在forms.py中声明字段

    from django import forms
    from student.models import Student, StudentDetail
    
    
    # 使用这种方式就要继承ModelForm
    class StudentForm(forms.ModelForm):
        class Meta:
            # 在元信息中拿到它的模型
            model = Student
            # 分析并获取想要展示的字段
            # fields = {'name', 'age', 'sex'}     # 指定字段展示
            # fields = {'__all__'}      # 选择所有的字段
            exclude = {'is_delete'}     # 排除不想展示的字段
    
  2. 学生详情也是同样的

    class StudentDetailForm(forms.ModelForm):
        class Meta:
            model = StudentDetail
            exclude = {'student'}   # 排除掉1对1关系字段
    
  3. 视图函数

    from student.forms import StudentForm, StudentDetailForm
    
    
    def student_edit_form(request, pk):
        student = Student.objects.get(pk=pk)    # 同样的拿到学生信息
        section = '编辑%s的信息' % student.name
        grade = Grade.objects.get(pk=student.grade_id)
        detail = StudentDetail.objects.get(student=student)
        # 学生和学生详情的form
        if request.method == 'GET':
            # 给参数instance赋值一个实例化对象, 这样form里面就有student的数据了
            form = StudentForm(instance=student)
            # 如果已有对应学生的详情信息, 就直接拿
            detail_form = StudentDetailForm(instance=detail)
            return render(request, 'student/detail_form.html', context={
                'section': section,
                'student': student,
                'form': form,
                'detail_form': detail_form,
            })
    
  4. 模板文件, 直接复制之前的detail文件来改, 我们只需要继承, 填坑和保存等按钮的代码, 其余的全部删除

    {% extends 'student/base.html' %}
    {% block title %}学生信息详情管理{% endblock %}
    {% block section %}{{ section }}{% endblock %}
    {% block content %}
        <form class="form-horizontal" method="post">
        {% csrf_token %}
            
    	{{ form.as_p }}
    	{{ detail_form.as_p }}
    
          <div class="form-group">...保存等按钮...</div>
        </form>
    {% endblock %}
    
  5. 运行一下看看
    在这里插入图片描述

    可以看到基本的表单已经实现了, 但是input的label名都还是英文

  6. models模型文件中设置verbose_name, 可以更改显示在页面上的label

    grade = models.ForeignKey('Grade', on_delete=models.SET_NULL, null=True, verbose_name='班级')
    

    verbose_name有两种设置方式, 除了上面的一种之外还有一种比较方便的, 针对非外键/非连接字段的方式

    name = models.CharField('姓名', max_length=20)
    
  7. 加样式使用for循环

    {% for field in form %}
        <div class="form-group">
            <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
            <div class="col-sm-3">
                {{ field }}
            </div>
        </div>
        {% endfor %}
        {% for field in detail_form %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
                <div class="col-sm-3">
                    {{ field }}
                </div>
            </div>
    {% endfor %}
    

    field.id_for_label代表的是label的地址, 加上后可以通过点label到输入框

    field.label代表每个字段的label

    运行结果也比之前好看一些了, 但是与原始的编辑页面还是有一定差距:
    在这里插入图片描述

  8. 从前面可以知道, 通过field.as_widget(attrs={"class": "form-control"})能够给前端标签加属性, 但是这里不可以直接写到这里, 我们可以通过自定义标签的形式来完成这个操作

    自定义过滤器

    @register.filter()
    def add_class(field, class_str):
        return field.as_widget(attrs={"class": class_str})
    

    自定义标签

    @register.simple_tag()
    def add_class(field, class_str):
        return field.as_widget(attrs={"class": class_str})
    
  9. 在原来{{ field }} 的地方改成自定义过滤器{{ field|add_class:"form-control" }}或是自定义标签{% add_class field "form-control" %}即可

    运行结果:
    在这里插入图片描述

  10. 整体上的界面已经完成, 但是还有一点就是, 性别单选框, 到models里改一下代码:

    class Student(models.Model):
        ...
        SEX_CHOICES = ((1, '男'), (0, '女'))
        sex = models.SmallIntegerField('性别', default=1, choices=SEX_CHOICES)
        # 这里的choices 可以使其在界面上显示为下拉框
    

    运行结果:
    在这里插入图片描述

  11. 这样显示是可以的, 那如果想要显示成单选框呢?

    我们可以在forms里这么写:

    class StudentForm(forms.ModelForm):
        class Meta:
            ...
            widgets = {'sex': forms.RadioSelect}	# 设置性别为单项选择
    

    但尽管设置了单选框, 我们之前的css样式一样也会影响到它, 因此我们要单独写单选框的样式

    .radio {
        list-style: none;
        padding-inline-start: 0;
    }
    .radio>li{
        float: left;
        margin-left: 20px;
    }
    

    同时模型中也要加判断

    {% extends 'student/base.html' %}
    {% load student_customer_filter %}
    {% load static %}
    {% block link %}
        <link rel="stylesheet" href="{% static 'student/css/detail.css' %}">
    {% endblock %}
    ...
            <div class="col-sm-3">
                {% if field.label == '性别' %}
                    {{ field|add_class:"radio" }}
                {% else %}
                	{{ field|add_class:"form-control" }}
                {% endif %}
            </div>
    ...
    

    运行结果:
    在这里插入图片描述

    这样整个页面的设计的就到此为止了

使用django表单的目的, 主要是用于快速的展示页面的主要功能, 追求美观的话, 还是建议安安稳稳的敲前端代码

信息验证

  • 可以通过models中的error_message来实现:

    class Student(models.Model):
        ...
        phone = models.CharField('电话', max_length=20, unique=True, error_messages={'unique': "电话号码重复!"})
    
  • 也可以通过forms中满足条件触发错误:

        def clean_num(self):
            data = self.cleaned_data.get('num')
            # 如果身份证号最后一位之外有非数字,则报错
            if not data[:-1].isdigit():
                # 以种方式都可以
                # self.add_error('num', '身份证号码不正确!')
                raise forms.ValidationError('=身份证号码不正确!=')
            return data
    
  • 错误触发写好之后一定要记得在前端模型上应用, 在循环下加上这些代码即可

        <div class="form-group {% if field.errors %}has-error{% endif %}">
            {% for error in field.errors %}
                <label for="{{ field.id_for_label }}" class="control-label">{{ error }}</label>
            {% endfor %}
    
  • 运行结果:
    在这里插入图片描述

到此大部分的基本内容已经写完了, 希望上面的内容对你有所帮助

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值