20-Django之CBV、序列化、Form表单

一、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标签
  • 提交:数据验证
  • 成功之后保存
  • 错误显示错误信息

步骤:

  1. 创建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')
    
  2. 只是生成HTML标签:添加页面

     form = MyForm()
     {{form.字段名}}
    
  3. 带默认值的HTML标签:编辑页面

     form = MyForm(initial={'Form字段名':obj.数据库字段名)
     {{form.字段名}}
    
  4. 提交数据

     form = MyForm(data=request.POST)
     if form.is_valid():
         print(form.cleaned_data)
     else:
         print(form.errors)
    
  5. 数据验证

    1. 错误提示信息在UserForm类中设置
    2. cleaned_data格式:{'ut_id': '1', 'username': 'd', 'email': 'd@123.com', 'ip': '1.1.1.1'},来源于前端对应标签中的name属性-->进一步来源于UserForm类
    3. 可以通过models.UserInfo.objects.create(**form.cleaned_data)将POST数据保存到数据库,前提是:创建Form表单类的字段与数据库中的对应字段保持一致
  6. 问题:下拉框数据无法实时更新-->重写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();
  • 解决:
    1. 静态文件设置写错为STATICFILE_DIRS
      STATIC_URL = '/static/'
      STATICFILES_DIRS = (
      os.path.join(BASE_DIR,'static'),
      )
    2. 要先引用JQuery,再引用javascript
  • 补充
    • STATICFILE_DIRS:自己写代码时用,
    • STATIC_ROOT:线上部署时用,nginx做静态文件处理,这时用STATIC_ROOT指向静态文件地址
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • HTML表单 在HTML中,表单是 ... 之间元素的集合,它们允许访问者输入文本、选择选项、操作对象等等,然后将...
    兰山小亭阅读 3,412评论 2 14
  • 在上一个章节,我们已经创建了一个基础的Blog程序。现在我们将使用一些Dajngo高级功能,去实现一个完整的blo...
    金金刚狼阅读 3,578评论 1 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 你把它放在桌上置于眼前 我将它夹在书中藏于心底 世上的人差异万万千 不过用各自的方式做着同一件事情
    星斗思天明阅读 93评论 0 1
  • / 有位明迷说过,当你见过了无数的帅哥之后,你会发现,原来心底里最帅的人,还是他。咦~我觉得这句话就把黎明的魅力表...
    番薯叶阅读 199评论 0 0