zoukankan      html  css  js  c++  java
  • django之表多对多建立方式、form组件、钩子函数 08

    多对多三种创建方式

    1.全自动(用ManyToManyField创建第三张表)

    class Book(models.Model):
        title = models.CharField(max_length=32)
        # # 通过ORM自带的ManyToManyField自动创建第三张表
        authors=models.ManyToManyField(to='Author')
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
    

    优点:操作简便,第三张表都是orm自动创建

    内置的操作第三张表的方法:add、remove、set、clear
    

    缺点:自动创建的第三张表无法扩展、修改字段,表的扩展性较差

    2.纯手写

    class Book(models.Model):
        title = models.CharField(max_length=32)
       
    class Author(models.Model):
        name = models.CharField(max_length=32)
    
    # 自己创建第三张表,分别通过外键关联书和作者
    class BookAuthor(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to = 'Author')
        create_time = models.DataField(auto_now_add = True)
    

    优点:第三张表的字段个数和类型都可以自己定义,灵活性更高;

    缺点:不再支持orm跨表查询,就没有了正反向的概念;

    3.半自动

    当ManyToManyField只有一个参数to的情况下, orm会自动帮你创建第三张表;
    如果还有额外的through和through_fields参数,第三张表就要自己创建, orm还会维护与第三张表的关系维,能够继续使用orm的跨表查询;
    through 指定第三张关系表;
    through_fields 指定第三张关系表中的两个外键字段。

    class Book(models.Model):
        title = models.CharField(max_length=32)
        author =   models.ManyToManyField(to='Author',,through='BookAuthor',through_fields=('book','author'))
        
    class Author(models.Model):
        name = models.CharField(max_length=32)
        # book =   models.ManyToManyField(to='Book',,through='BookAuthor',through_fields=('author','book'))  # 在哪一张表创建多对多字段都可以,一般建在查询频率较高的那张表中。
      # through_fields接受一个元组('field1','field2'):其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。   
    
    class BookAuthor(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to = 'Author')
        create_time = models.DataField(auto_now_add = True)
    

    优点:可以任意的添加和修改第三张表中的字段,并且支持orm跨表查询

    缺点:不再支持add、remove、clear、set方法的使用

    多表关系断裂

    断关联表关系
    1)不会影响连表查询操作效率
    2)会提升连表增删改操作效率
    3)易于后期数据库表的重构
    4)缺点在于:数据库本身没有连表检测,容易出现脏数据,需要通过严格的逻辑避免脏数据的参数(必要的时候管理脏数据)

    举例:A依赖B,先插入A记录,该记录对应的B记录没产生,在没有关联的情况下,该操作可以实现,但是数据就是脏数据
    接着再将B数据添加,脏数据就得到处理了。反过来先操作B后操作A,更满足逻辑思维,一样可以执行。通过逻辑将AB表进行
    连表查询,不会有任何异常

    # 外键字段属性
    1)related_name在外键中设置外键反向查询的字段名:正向找字段名,反向找related_name值
    2)on_delete在外键中必须设置,表示级联关系,在Django 1.x下,系统默认提供(值为models.CASCADE),Django 2.x下,必须手动明确
            CASCADE:默认值,级联
                例子:作者没,详情一定没,存在没意义
            DO_NOTHING:外键不会被级联,假设A表依赖B表,B记录删除,A表的外键字段不做任何处理
                例子:作者没,书还是作者写的 | 出版社没,书还是该出版社出版的
            SET_DEFAULT:假设A表依赖B表,B记录删除,A表的外键字段置为default属性设置的值,所以必须配合default属性使用
                例子:部门没,部门员工进入待定部门(注:部门表一定要有待定部门记录)
            SET_NULL:假设A表依赖B表,B记录删除,A表的外键字段置为null,所以必须配合null=True属性使用
                例子:部门没,部门员工进入未分组部门(注:关联部门表外键可以为空)
            
            注:多对多字段不能设置on_delete级联关系,默认为级联,如果要处理级联关系,需要手动明确关系表,处理关系表中的多个外键
            
    3) db_constraint在外键中控制表关联,默认为True表示关联,设置False表示断开关联
    
    from django.db import models
    
    class BaseModel(models.Model):
    
        is_delete = models.BooleanField(default=False)
        create_time = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            # 基表,为抽象表,是专门用来被继承,提供公有字段的,自身不会完成数据库迁移
            abstract = True
    
    class Book(BaseModel):
        name = models.CharField(max_length=64)
        price = models.DecimalField(max_digits=10, decimal_places=2)
        publish = models.ForeignKey(to='Publish', related_name='books', on_delete=models.SET_NULL, null=True, db_constraint=False)
        author = models.ManyToManyField(to='Author',  through='BookAuthor', through_fields=('books', 'authors'))
    
        def __str__(self):
            return self.name
    
    class Author(BaseModel):
        name = models.CharField(max_length=64)
        # book = models.ManyToManyField(to='Book', through='BookAuthor', through_fields=(' authors', 'books'))
    
    
        def __str__(self):
            return self.name
    
    
    class BookAuthor(BaseModel):
        books = models.ForeignKey(to = 'Book', related_name='books', on_delete=models.SET_NULL, null=True, db_constraint=False)
        authors = models.ForeignKey(to = 'Author', related_name='authors', on_delete=models.SET_NULL, null=True, db_constraint=False)
    
    class Publish(BaseModel):
        name = models.CharField(max_length=64)
        address = models.CharField(max_length=64)
    
        def __str__(self):
            return self.name
    
    
    class AuthorDetail(BaseModel):
        mobile = models.CharField(max_length=64)
        author = models.OneToOneField(to = Author, related_name='detail', on_delete=models.CASCADE, db_constraint=False)
    
    

    form组件

    form表单的主要功能:

    生成页面可用的HTML标签;对用户提交的数据进行校验;保留上次的输入内容;
    

    引入

    需求:在浏览器页面,通过form表单中的input框获取用户输入,传到后端进行校验用户的输入是否合法;然后在对应的input框后展示一些提示信息;

    这里利用标签的渲染(其实用ajax和BOM操作也能实现)

    def register(request):
        back_dic = {'username':'','password':''}
        if request.method == 'POST':
            username = request.POST.get('usename')
            password = request.POST.get('password')
            if '小朋友' in username:
            	back_dic['usernmae'] = '敏感信息!不符合!'
            if len(password) < 4:
                back_dic['password'] = '你太短!不行呀!'
        return render(request,'register.html',local())
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
        <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    </head>
    <body>
    	
    </body>
    </html>
    

    以上需要自己手写HTML代码获取用户输入,传到后端,如果数据有误,还需展示错误信息,比较麻烦,下面利用django自带的form组件来实现。

    form组件的使用

    使用form组件首先要定义一个类,继承django的Form类

    # views.py
    
    from django import forms
    
    class MyForm(forms.Form):
        # username字段,最少3位,最多8位
        username = forms.CharField(max_length=8,min_length=3)
        # password字段,最少3位,最多8位
        password = forms.CharField(max_length=8,min_length=3)
        # email字段,必须是邮箱格式
        email = forms.EmailField()
    
    # 在Django Console中校验  自动搭建测试环境
    
    In[2]:from app01 import views
    
    In[3]:form_obj = views.MyForm({'username':'zhang','password':'123','email':'110117@qq.com'})
    
    In[4]:form_obj.is_valid
    Out[4]: <bound method BaseForm.is_valid of <MyForm bound=True, valid=Unknown, fields=(username;password;email)>>
        
    In[5]:form_obj.is_valid()
    Out[5]: True  # 只有当你传入的全部数据都符合校验规则的情况下才为True,否则为False
    # 查看不符合规则的字段和错误理由
    In[6]:form_obj.errors
    Out[6]: {}
    # 符合规则的数据,不符合的会被排除掉
    In[7]:form_obj.cleaned_data
    Out[7]: {'username': 'zhang', 'password': '123', 'email': '110117@qq.com'}
        
    # 如果出入的数据不符合定义字段指定的长度,错误的字段会放在form_obj.errors中;比如:
    In[8]:form_obj.errors
    Out[8]:{'password': ['Ensure this value has at least 3 characters (it has 2).'],'email': ['Enter a valid email address.']
    				 }
    
    # 传数据的时候,多传不报错
    In[9]:form_obj = views.MyForm({'username':'zhang','password':'123','email':'110117@qq.com','gender':'male'})
    In[10]form_obj.is_valid()
    Out[10]: True
    # 少传对应的字段就不行
    In[11]:form_obj = views.MyForm({'username':'zhang','password':'123'})
    In[12]:form_obj.is_valid()
    Out[13]: False
    

    forms组件渲染标签

    forms组件只会帮你渲染获取用户输入的标签 不会帮你渲染提交按钮 需要你自己手动添加

    字段类括号内的label可以修改input框前面的注释(提示信息)

    from django import forms
    class MyForm(forms.Form):
        username = forms.CharField(max_length=8,min_length=3,label='用户名')  # label 修改用户输入框前面的提示信息
        password = forms.CharField(max_length=8,min_length=3,label='密码')
        email = forms.EmailField(label='邮箱')
    
    def index(request): 
        form_obj = MyForm(request.POST)
        return render(request, 'index.html', locals())
    

    forms组件渲染标签方式1

    封装程度态高 不推荐使用 但是可以用在本地测试

    <p>forms组件渲染标签方式1</p>
    {{ form_obj.as_p }}  <!--自动渲染所有input框  -->
    {{ form_obj.as_ul }}
    {{ form_obj.as_table }}
    

    forms组件渲染标签方式2

    不推荐使用 写起来太烦了

    <p>forms组件渲染标签方式2</p>
    <!--获取input框前面的Username     获取的是一个input输入框-->
    {{ form_obj.username.label }}{{ form_obj.username }}
    {{ form_obj.username.label }}{{ form_obj.password }}
    {{ form_obj.username.label }}{{ form_obj.email }}
    

    forms组件渲染标签方式3

    推荐使用

    <p>forms组件渲染标签方式3:推荐使用 </p>
    {% for form in form_obj %}
    <p>{{ form.label }}{{ form }}</p>  <!--form 等价于你方式2中的对象点字段名,是一个input输入框-->
    {% endfor %}
    

    form表单展示信息

    from django import forms
    
    class MyForm(forms.Form):
     username = forms.CharField(max_length=8,min_length=3,label='用户名',
                                   error_messages={
                                       'max_length':'用户名不能超过八位',
                                       'min_length':'用户名不能少于三位',
                                       'required':'用户名不能为空',
                                   }
    
                                   )  # label 修改用户输入框前面的提示信息
        password = forms.CharField(max_length=8,min_length=3,label='密码',
                                   error_messages={
                                       'max_length': '密码不能超过八位',
                                       'min_length':'密码不能少于三位',
                                       'required': '密码不能为空',
                                   }
                                   )
        email = forms.EmailField(label='邮箱',
                                 error_messages={
                                     'required':'邮箱不能为空!',
                                     'invalid':'邮箱格式错误',
                                 })
    
    def index(request):
        form_obj = MyForm()
        print(request.POST)  # <QueryDict: {'username': ['zhang'], 'password': ['123456'], 'email': ['1101172195@qq.com']}>
        if request.method=='POST':
            form_obj = MyForm(request.POST)  # MyForm类需要传入一个字典,而request.POST获取的用户的输入恰恰也是字典
            if form_obj.is_valid():  # 跟上面的变量名要一致,因为两次不冲突,都要拿到html文件中给同一份代码渲染
                print(form_obj.cleaned_data)
                return HttpResponse('输入真确!')
            else:
                print(form_obj.errors)
    
        return render(request, 'index.html', locals())
    

    novalidate 在form标签内写上它,告诉前端不让其对用户输入的数据做校验

    forms.errors 获取后端校验后错误的报错信息,传给前端;

    forms.errors.0 改变报错信息在input输入框后面展示

    forms.label input框前面的提示信息

    forms 获取一个input输入框

    {#novalidate让前端不对输入信息是否合法做校验#}
    <form action="" method="post" novalidate>
        <p>form组件的渲染方式3:</p>  <!--推荐使用-->
        {% for forms in form_obj %}
            <p>
    {#            获取提示信息和input输入框#}
                {{ forms.label }}{{ forms }}
    {#            这里forms.errors也能拿到后端的报错信息(在input框下面),渲染到浏览器,forms.errors.0把报错信息的展示到input框后面#}
                <span>{{ forms.errors.0 }}</span>
            </p>
        {% endfor %}
        <input type="submit">
    </form>
    

    form组件自定义校验

    内置的校验器(RegexValidator)

    字段类括号内可以连续写多个参数

    from django.core.validators import RegexValidator
     
    class MyForm(Form):
        user = fields.CharField(
            validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
        )
    

    钩子函数(HOOK)

    当你觉得上面的所有的校验还不能够满足你的需求 ,你可以考虑使用钩子函数,是一个函数 函数体内你可以写任意的校验代码。

    钩子函数执行条件:在类内部定义的字段括号设定的条件都满足之后才会走到钩子函数;如果前面的条件都不满足,不会走到钩子函数。

    用法:直接在类内部定义django给我们定义好的函数,根据需要自己选择。包括:

    clean、clean_confirm_password、clean_email、clean_password、clean_username

    局部钩子:校验单个字段

    全局钩子:校验多个字段

    # 使用forms组件的第一步 必须先写一个类
    from django import forms
    class MyForm(forms.Form):
        # username字段 最少三位 最多八位
        username = forms.CharField(max_length=8,min_length=3,label='用户名',initial='默认值',
                                   error_messages={
                                       'max_length':'用户名最长八位',
                                       'min_length':'用户名最短三位',
                                       'required':'用户名不能为空'
                                   },required=False,
                                   widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'zhang'})
                                   )
        # password字段 最少三位  最多八位
        password = forms.CharField(max_length=8,min_length=3,label='密码',
                                   error_messages={
                                       'max_length': '密码最长八位',
                                       'min_length': '密码最短三位',
                                       'required': '密码不能为空'
                                   },widget=forms.widgets.PasswordInput()
                                   )
        confirm_password = forms.CharField(max_length=8, min_length=3, label='确认密码',
                                   error_messages={
                                       'max_length': '确认密码最长八位',
                                       'min_length': '确认密码最短三位',
                                       'required': '确认密码不能为空'
                                   },
                                   )
        # email字段 必须是邮箱格式
        email = forms.EmailField(label='邮箱',error_messages={
                                'required':'邮箱不能为空',
                                'invalid':'邮箱格式错误'
        })
    	 # 校验用户名中不能含有666     局部钩子
        def clean_username(self):
            username = self.cleaned_data.get('username')
            if '666' in username:
                # 给username所对应的框展示错误信息
                # self.add_error('username','光喊666是不行的')
                raise ValidationError('到底对不对啊')
            # 将username数据返回
            return username
    
        # 校验密码 确认密码是否一致     全局钩子
        def clean(self):
            password = self.cleaned_data.get("password")
            confirm_password = self.cleaned_data.get("confirm_password")
            if not password == confirm_password:
                self.add_error('confirm_password','两次密码不一致')
            # 将全局的数据返回
            return self.cleaned_data
    

    其他字段及参数

    label       input对应的提示信息
    initial     input框默认值
    required  	默认为True控制字段是否必填
    widget      给input框设置样式及属性,也能设置input输入框的类型(type)
    widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'li'})  # 增加属性
    widget=forms.widgets.TextInput(attrs={'class':'form-control c1c2','username':'li'})
    
    widget=forms.widgets.TextInput()  # 默认普通文本类型
    
    widget=forms.widgets.PasswordInput()  # 密文密码
    

    radioSelect

    单radio值为字符串

    class LoginForm(forms.Form):
        username = forms.CharField(
            min_length=8,
            label="用户名",
            initial="张三",
            error_messages={
                "required": "不能为空",
                "invalid": "格式错误",
                "min_length": "用户名最短8位"
            }
        )
        pwd = forms.CharField(min_length=6, label="密码")
        gender = forms.fields.ChoiceField(
            choices=((1, "男"), (2, "女"), (3, "保密")),
            label="性别",
            initial=3,
            widget=forms.widgets.RadioSelect()
        )
    

    单选Select

    class LoginForm(forms.Form):
        ...
        hobby = forms.ChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
            label="爱好",
            initial=3,
            widget=forms.widgets.Select()
        )
    

    多选Select

    class LoginForm(forms.Form):
        ...
        hobby = forms.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.SelectMultiple()
        )
    

    单选checkbox

    class LoginForm(forms.Form):
        ...
        keep = forms.ChoiceField(
            label="是否记住密码",
            initial="checked",
            widget=forms.widgets.CheckboxInput()
        )
    

    多选checkbox

    class LoginForm(forms.Form):
        ...
        hobby = forms.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.CheckboxSelectMultiple()
        )
    

    choice字段注意事项

    在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。

    方式一:

    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
     
    class MyForm(Form):
        user = fields.ChoiceField(
            # choices=((1, '上海'), (2, '北京'),),
            initial=2,
            widget=widgets.Select
        )
        def __init__(self, *args, **kwargs):
            super(MyForm,self).__init__(*args, **kwargs)
            # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
            # 或
       self.fields['user'].choices=models.Classes.objects.all().values_list('id','caption')
    

    方式二:

    from django import forms
    from django.forms import fields
    from django.forms import models as form_model
    
     
    class FInfo(forms.Form):
        authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
        # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选
    

    Django Form所有内置字段

    Field
        required=True,               默认为空,True不能为空,False允许为空
        widget=None,                 HTML插件
        label=None,                  用于修改input框前面的提示信息
        initial=None,                input框初始值
        help_text='',                帮助信息(在标签旁边显示)
        error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
        validators=[],               自定义验证规则
        localize=False,              是否支持本地化
        disabled=False,              是否可以编辑
        label_suffix=None            Label内容后缀
     
     
    CharField(Field)
        max_length=None,             最大长度
        min_length=None,             最小长度
        strip=True                   是否移除用户输入空白
     
    IntegerField(Field)
        max_value=None,              最大值
        min_value=None,              最小值
     
    FloatField(IntegerField)
        ...
     
    DecimalField(IntegerField)
        max_value=None,              最大值
        min_value=None,              最小值
        max_digits=None,             总长度
        decimal_places=None,         小数位长度
     
    BaseTemporalField(Field)
        input_formats=None          时间格式化   
     
    DateField(BaseTemporalField)    格式:2015-09-01
    TimeField(BaseTemporalField)    格式:11:12
    DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
     
    DurationField(Field)            时间间隔:%d %H:%M:%S.%f
        ...
     
    RegexField(CharField)
        regex,                      自定制正则表达式
        max_length=None,            最大长度
        min_length=None,            最小长度
        error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
     
    EmailField(CharField)      
        ...
     
    FileField(Field)
        allow_empty_file=False     是否允许空文件
     
    ImageField(FileField)      
        ...
        注:需要PIL模块,pip3 install Pillow
        以上两个字典使用时,需要注意两点:
            - form表单中 enctype="multipart/form-data"
            - view函数中 obj = MyForm(request.POST, request.FILES)
     
    URLField(Field)
        ...
     
     
    BooleanField(Field)  
        ...
     
    NullBooleanField(BooleanField)
        ...
     
    ChoiceField(Field)
        ...
        choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
        required=True,             是否必填
        widget=None,               插件,默认select插件
        label=None,                Label内容
        initial=None,              初始值
        help_text='',              帮助提示
     
     
    ModelChoiceField(ChoiceField)
        ...                        django.forms.models.ModelChoiceField
        queryset,                  # 查询数据库中的数据
        empty_label="---------",   # 默认空显示内容
        to_field_name=None,        # HTML中value的值对应的字段
        limit_choices_to=None      # ModelForm中对queryset二次筛选
         
    ModelMultipleChoiceField(ModelChoiceField)
        ...                        django.forms.models.ModelMultipleChoiceField
     
     
         
    TypedChoiceField(ChoiceField)
        coerce = lambda val: val   对选中的值进行一次转换
        empty_value= ''            空值的默认值
     
    MultipleChoiceField(ChoiceField)
        ...
     
    TypedMultipleChoiceField(MultipleChoiceField)
        coerce = lambda val: val   对选中的每一个值进行一次转换
        empty_value= ''            空值的默认值
     
    ComboField(Field)
        fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                                   fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
     
    MultiValueField(Field)
        PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
     
    SplitDateTimeField(MultiValueField)
        input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
        input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
     
    FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
        path,                      文件夹路径
        match=None,                正则匹配
        recursive=False,           递归下面的文件夹
        allow_files=True,          允许文件
        allow_folders=False,       允许文件夹
        required=True,
        widget=None,
        label=None,
        initial=None,
        help_text=''
     
    GenericIPAddressField
        protocol='both',           both,ipv4,ipv6支持的IP格式
        unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
     
    SlugField(CharField)           数字,字母,下划线,减号(连字符)
        ...
     
    UUIDField(CharField)           uuid类型
    
    
    Django Form内置字段
    
  • 相关阅读:
    Hanoi塔
    采药
    进制转换(大数)
    Load Balancing with NGINX 负载均衡算法
    upstream模块实现反向代理的功能
    epoll
    在nginx启动后,如果我们要操作nginx,要怎么做呢 别增加无谓的上下文切换 异步非阻塞的方式来处理请求 worker的个数为cpu的核数 红黑树
    粘性会话 session affinity sticky session requests from the same client to be passed to the same server in a group of servers
    负载均衡 4层协议 7层协议
    A Secure Cookie Protocol 安全cookie协议 配置服务器Cookie
  • 原文地址:https://www.cnblogs.com/zhangchaocoming/p/11980667.html
Copyright © 2011-2022 走看看