一、FBV & CBV
CBV定义
Djanggo中的请求处理方式
FBV: Function Based View
/index/ func(request)
CBV: Class Based View
/index/ class(object)--get方法/post方法
View方法
- View方法支持的http请求:
['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] - form表单提交请求:
'get', 'post' - ajax提交请求:
['get'获取, 'post'发送, 'put'增加, 'patch'增加, 'delete'删除, 'head', 'options', 'trace'] - 请求数据:用url去路由系统中找到对应的类/函数
- 函数:直接执行函数
- 类:类名()执行构造方法初始化-->执行类中的dispatch()方法-->dispatch()中拿到请求的method进行反射
dispatch方法
class A:
def f1(self):
self.f2()
class B(A):
def f2(self):
print("-->a.f2")
#关键点:找到self到底是谁,就从哪个self这个类开始找,没有就往上找
obj = B()
obj.f1()
#报错:A类中没有f2()方法
obj = A()
obj.f1()
========================
class View:
def dispatch(self):
func = getattr(self,'get')
return func(...)
def get()
print(1)
class LoginView(View):
def dispatch(self,request, *args, **kwargs):
# 可以在执行dispatch方法前后统一执行一些代码,起到装饰器的作用
print("before")
response = super(LoginView, self).dispatch(request, *args, **kwargs)
print("after")
return response
def get():
print(2)
#打印结果为2
obj = LoginView()
obj.dispatch()
对于super(B, self).dispatch()可以这样理解:super(B, self)首先找到B的父类(就是类A),然后把类B的对象self转换为类A的对象,然后“被转换”的类A对象,先调用类B中的方法,如果没有,就向上调用类A中的方法。
应用场景:登录验证
- 继承
- 单继承
- 多继承
- 装饰器:@method_decorator(auth, name='post')
#单继承方式
class BaseView(View):
def dispatch(self, request, *args, **kwargs):
if request.session.get('username'):
response = super(BaseView,self).dispatch(request, *args, **kwargs)
return response
else:
return redirect('/login.html')
class IndexView(BaseView):
def get(self, request, *args, **kwargs):
return HttpResponse('Welcome! '+request.session.get('username'))
#多继承方式
class BaseView(object):
def dispatch(self, request, *args, **kwargs):
if request.session.get('username'):
response = super(BaseView,self).dispatch(request, *args, **kwargs)
return response
else:
return redirect('/login.html')
class IndexView(BaseView, View):
def get(self, request, *args, **kwargs):
return HttpResponse('Welcome! '+request.session.get('username'))
#装饰器
from django.utils.decorators import method_decorator
def auth(func):
def inner(request, *args, **kwargs):
if request.session.get('username'):
obj = func(request, *args, **kwargs)
return obj
else:
return redirect('/login.html')
return inner
@method_decorator(auth,name='get')
class IndexView(View):
# @method_decorator(auth)
def get(self, request, *args, **kwargs):
return HttpResponse('Welcome! ' + request.session.get('username'))
CBV中csrf的一个小bug
@method_decorator(csrf_exempt/protect)放在类或者对应方法上面时,不起作用,目前只能放在dispatch方法上面(对该类下的所有方法起作用)
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.utils.decorators import method_decorator
class LoginView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
response = super(LoginView, self).dispatch(request, *args, **kwargs)
return response
def get(self, request, *args, **kwargs):
return render(request, 'login.html')
def post(self, request, *args, **kwargs):
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'amy' and pwd == '123':
request.session['username']=user
return redirect('/index.html')
return render(request, 'login.html', {'msg':'输入错误,请重新输入!'})
二、序列化
后端向前端传送数据
传统方式
def users(request):
user_list = models.UserInfo.objects.all()
return render(request, 'users.html', {'user_list':user_list})
方式1:序列化
serializers.serialize(),不推荐使用(数据过多)
def users(request):
return render(request, 'users.html')
from django.core import serializers
def get_users(request):
user_list = models.UserInfo.objects.all()
data = serializers.serialize('json',user_list) #将queryset格式转化为json
return HttpResponse(data)
方式2:json.dumps()
def get_users(request):
user_list = models.UserInfo.objects.values()
user_list = list(user_list)
data = json.dumps(user_list)
return HttpResponse(data)
问题:对json.dumps做定制化
json.dumps()的局限性:只能处理python的数据类型,而对于date/datetime等类型就会报错
解决办法:
前提:取数据时用values()或者values_list(),保证取到的数据是json认识的类型
from django.test import TestCase
import json
from datetime import date
from datetime import datetime
class JsonCustomEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(o, date):
return o.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, o)
user_list = [
{'id':1,'user':'amy','ctime':datetime.now()},
{'id':1,'user':'bulk','ctime':datetime.now()},
]
data = json.dumps(user_list,cls=JsonCustomEncoder)
前端展示
<body>
<ul id="user_list_id">
</ul>
<script src="/static/jquery-3.2.1.js"></script>
<script>
function initData() {
$.ajax({
url:'/get_users.html',
type:'GET', // get大小写都可以
dataType:'JSON', // 等价于arg = JSON.parse(arg)
sucess:function (arg) {
if (arg.status){
/*
arg.data = [
{id: xxx,username: xxx}
]
*/
$.each(arg.data, function (index,row) {
// row = {id: xxx,username: xxx}
var tag = '<li>' +row.username + '</li>';
$('#user_list_id').append(tag);
})
}else {
alert(arg.msg);
}
}
})
}
</body>
三、Form表单验证
功能:用户请求验证+生成html标签
示例:用户管理
添加用户页面
- 显示HTML标签
- 提交:数据验证
- 成功之后保存
- 错误显示错误信息
步骤:
-
创建Form类(本质就是正则表达式的集合)
class MyForm(Form) username = fields.CharField() email = fields.EmailField() ut_id = fields.ChoicesField() role_id = fields.MultipleChoiceField() ip = fields.GenericIPAddressField(protocol='ipv4') #用户表 class UserForm(Form): #Form第一次加载的时候,会深拷贝所有字段放到fields中,方便以后调用 username = fields.CharField( required=True, error_messages={'required':'用户名不能为空!'}, widget = widgets.TextInput(attrs={'class':'form-control'}) ) password = fields.CharField( required=True, error_messages={'required':'密码不能为空!'}, widget= widgets.TextInput(attrs={'class':'form-control'}) ) ut_id = fields.ChoiceField( choices = [], widget= widgets.Select(attrs={'class':'form-control'}) ) role_id = fields.MultipleChoiceField( choices= [], widget = widgets.SelectMultiple(attrs={'class':'form-control'}) ) def __init__(self, *args, **kwargs): super(UserForm,self).__init__(*args, **kwargs) #self.fields中已经有所有拷贝的字段,每次实例化时重新赋值 self.fields['ut_id'].choices = models.UserType.objects.values_list('id','title') self.fields['role_id'].choices = models.UserRole.objects.values_list('id', 'caption')
-
只是生成HTML标签:添加页面
form = MyForm() {{form.字段名}}
-
带默认值的HTML标签:编辑页面
form = MyForm(initial={'Form字段名':obj.数据库字段名) {{form.字段名}}
-
提交数据
form = MyForm(data=request.POST) if form.is_valid(): print(form.cleaned_data) else: print(form.errors)
-
数据验证
- 错误提示信息在UserForm类中设置
- cleaned_data格式:{'ut_id': '1', 'username': 'd', 'email': 'd@123.com', 'ip': '1.1.1.1'},来源于前端对应标签中的name属性-->进一步来源于UserForm类
- 可以通过models.UserInfo.objects.create(**form.cleaned_data)将POST数据保存到数据库,前提是:创建Form表单类的字段与数据库中的对应字段保持一致
-
问题:下拉框数据无法实时更新-->重写Form表单的init方法(见步骤1)
- new方法中,循环出所有的Form表单字段。
- init方法中,通过self.fields = copy.deepcopy(self.base_fields)将Form表单深拷贝放在fields中
静态字段只执行一次,__init(self)中的数据在每次实例化对象的时候执行一次。
NewForm()先执行new(),再执行init()
一个bug
ut_id = fields.ChoiceField(choices=models.UserType.objects.values_list('id','title'))
取值为空
进一步:
user_type = models.UserType.objects.values_list('id','title')
print(user_type) #
四、Form多对多操作
添加页面
注意:POST操作中,要先把m2m字段pop出来,再通过m2m字段关联到第三张表进行添加
class AddUserView(AuthView, View):
def get(self, request, *args, **kwargs):
form = UserForm()
return render(request, 'adduser.html',{'form':form})
def post(self, request, *args, **kwargs):
form = UserForm(data=request.POST)
#将用户提交的数据和UserForm中定义的规则进行匹配
if form.is_valid():
# {'role_id': ['1', '4'], 'password': '123', 'ut_id': '1', 'username': 'frank'}
role_id_list = form.cleaned_data.pop('role_id') #['1', '4']
obj = models.UserInfo.objects.create(**form.cleaned_data)
obj.role.add(*role_id_list)
return redirect('/users.html')
else:
print(form.errors)
return render(request, 'adduser.html', {'form':form})
编辑页面,显示默认值
注意:有的用户没有角色,需要通过三元表达式判断后再赋值
class EditUserView(AuthView, View):
def get(self, request, pk):
# 获取当前编辑对象
obj = models.UserInfo.objects.filter(id=pk).first()
# <QuerySet [(1,), (4,)]>。没有设置角色的用户<QuerySet []>
role_list = obj.role.values_list('id')
#为真时的结果 if 判定条件 else 为假时的结果
# v = list(zip(*role_list))[0] if role_list else []
#判定条件 and 为真时的结果 or 为假时的结果
v = role_list and list(zip(*role_list))[0] or []
#获取Form表单的默认值
form = UserForm(initial={'username':obj.username, 'password':obj.password, 'ut_id':obj.ut_id, 'role_id':v})
return render(request, 'edituser.html',{'form':form})
def post(self, request, pk):
form = UserForm(data=request.POST)
if form.is_valid():
#用户表更新
# {'role_id': ['1', '4'], 'password': '123', 'ut_id': '1', 'username': 'frank'}
role_id = form.cleaned_data.pop('role_id')
query = models.UserInfo.objects.filter(id=pk)
query.update(**form.cleaned_data)
obj = query.first()
obj.role.set(role_id)
return redirect('/users.html')
else:
print(form.errors)
return render(request, 'edituser.html', {'form':form})
widgets插件,自定义组件样式
四、Form组件和Ajax组合示例
应用场景
-
页面模态对话框:添加/删除/编辑 -->适用:个数少、内容少
- 前端用ajax提交数据(无刷新)+后端用Django的Form组件进行验证
- Form组件验证功能必用,生成HTML可不用
- Ajax页面不刷新,可以手写input框
-
跳转到新URL的方式:添加/删除/编辑 --> 适用:数据个数多、博客
- Form标签提交(页面刷新)+后端用Django的Form组件进行验证
- Form验证功能必用,生成HTML可不用
- 用Form表单提交数据时,input框不能手写,否则不能保留已填写的值
-
个人使用习惯
- 页面上的删除用模态对话框:【是否确认删除?】
- 添加/修改:用新URL方式
-
注意:
- 页面只在第一次请求时渲染一次。
- Ajax不能识别redirect,只能接收字符串。只有Form提交时redirect可用
- 注册成功,通过JS的location.href进行跳转
- 注册失败,标签后提示错误信息:each循环出错误信息-->创建错误信息标签放在对应标签后面-->保证每点一次,错误信息清空
一个Bug
- 报错:Uncaught ReferenceError: $ is not defined
- 报错语句:$('#register_form .error').remove();
- 解决:
- 静态文件设置写错为STATICFILE_DIRS
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),
) - 要先引用JQuery,再引用javascript
- 静态文件设置写错为STATICFILE_DIRS
- 补充
- STATICFILE_DIRS:自己写代码时用,
- STATIC_ROOT:线上部署时用,nginx做静态文件处理,这时用STATIC_ROOT指向静态文件地址