美文网首页
20-Django之CBV、序列化、Form表单

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

作者: likethebluebird | 来源:发表于2017-09-27 12:37 被阅读0次

    一、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指向静态文件地址

    相关文章

      网友评论

          本文标题:20-Django之CBV、序列化、Form表单

          本文链接:https://www.haomeiwen.com/subject/awquextx.html