前言
你可以从这篇文章中学习到:
- 会话(session)状态保持, 及在django框架中的应用
- django中的form表单的使用及应用
并且我会在本文中演示使用form表单编写 学生详情页
<ps: 学生详情页属于学生管理系统的一个模块, 该系统之后会整理到另一篇文章中>
会话保持及Form表单
session保存状态
上面我们使用过cookie来实现将数据保存在客户端中, 但是浏览器存储cookie的方式不太安全
那有没有更好些的来存储登入状态的方式呢? 这里有引入了session会话保持
- 首先http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态
- 客户端与服务器端的一次通信,就是一次会话实现
状态保持
的方式:在客户端或服务器端存储与会话有关的数据 - 保持方式包括cookie、session,会话一般指session对象
- 使用cookie方式,所有数据都会存储在客户端, 很不安全,因此注意不要存储敏感信息
- 使用session方式,所有数据存储在服务器端,session技术其实是基于cookie技术的, 在使用时, 它的服务端会给每一个客户端生成一个独一无二的session_id(散列值, 几乎不可能重复), 然后又会通过cookie传输到客户端, 在客户端的cookie中, 存储的就是session_id
- 服务端可以通过分析用户从客户端传输过来的session_id来分辨用户信息
- 状态保持的目的是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据
- 注意:不同的请求者之间不会共享这个数据,与请求者一一对应
启用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()
删除当前的会话数据并删除会话的Cookiedel 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表单
我们实现了登录状态的保持, 接下来, 如果需要注册登录呢?
简单表单
使用
-
创建一个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() # 邮箱类型
-
写好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'),
-
这样一来前端就可以直接拿form去显示表单了
<body> {{ form }} </body>
-
显示的是没有任何修饰的表单, 但这已经是很大的进步了, 我们通过后端代码实现了前端的页面展示, 几乎没有编写前端代码!
-
修饰1,
{{ form.as_p }}
, 自动在每一个input标签外, 套上一个p标签, 加上之后再看 -
修饰2, 虽然是用的表单方法, 但是它并没有给我们的前端代码加上form, 所以这一块就要自己设置了
<form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="提交"> </form>
-
既然已经完善了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时就会出现错误信息,
但是, 两次密码不一致时并不会出错.
-
我们来视图函数中加一个检验方法, 以解决上面那个问题
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)
-
通过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'} # 排除不想展示的字段
-
学生详情也是同样的
class StudentDetailForm(forms.ModelForm): class Meta: model = StudentDetail exclude = {'student'} # 排除掉1对1关系字段
-
视图函数
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, })
-
模板文件, 直接复制之前的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 %}
-
运行一下看看
可以看到基本的表单已经实现了, 但是input的label名都还是英文
-
models模型文件中设置
verbose_name
, 可以更改显示在页面上的labelgrade = models.ForeignKey('Grade', on_delete=models.SET_NULL, null=True, verbose_name='班级')
verbose_name有两种设置方式, 除了上面的一种之外还有一种比较方便的, 针对非外键/非连接字段的方式
name = models.CharField('姓名', max_length=20)
-
加样式使用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运行结果也比之前好看一些了, 但是与原始的编辑页面还是有一定差距:
-
从前面可以知道, 通过
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})
-
在原来
{{ field }}
的地方改成自定义过滤器{{ field|add_class:"form-control" }}
或是自定义标签{% add_class field "form-control" %}
即可运行结果:
-
整体上的界面已经完成, 但是还有一点就是, 性别单选框, 到models里改一下代码:
class Student(models.Model): ... SEX_CHOICES = ((1, '男'), (0, '女')) sex = models.SmallIntegerField('性别', default=1, choices=SEX_CHOICES) # 这里的choices 可以使其在界面上显示为下拉框
运行结果:
-
这样显示是可以的, 那如果想要显示成单选框呢?
我们可以在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 %}
-
运行结果:
到此大部分的基本内容已经写完了, 希望上面的内容对你有所帮助