• 第八章表单与模型


    表单主要分为两种表单

    django表单功能由Form类实现,主要分为两种
    1.django.forms.Form
    2.django.forms.ModelForm # 结合模型生成的数据表单

    表单form标签中的action用于设置用户提交的表单数据应由哪个路由来接收和处理,若为空,则提交由当前的路由来接收和处理。否则则跳转到属性action所指向的路由地址
    提交请求由method决定

    网页表单的构成

        {% if v.errors %} <!--v为表单类的实例化-->
            <p>
                数据出错啦,错误信息:{{ v.errors }}
            </p>
        {% else %}
            <form action="" method="post">
            {% csrf_token %}
                <table>
                    <!--将表单对象生成网页表单-->
                    {{ v.as_table }} as_table <!--表单的表示纯用模板变量么-->
                </table>
                <input type="submit" value="提交">
            </form>
        {% endif %}
    

    定义表单类,新建表单文件

    定义表单类需要导入模型类

    from django import forms
    from .models import *
    class VocationForm(forms.Form):
        job = forms.CharField(max_length=20, label='职位')
        title = forms.CharField(max_length=20, label='职称')
        payment = forms.IntegerField(label='薪资') # 难道IntegerField设置数字成可自增自减
        # 设置下拉框的值
        # 查询模型PersonInfo的数据
        value = PersonInfo.objects.values('name') # 获取模型字段中的姓名
        # 将数据以为列表格式表示,列表元素为元组格式
        choices = [(i+1, v['name']) for i, v in enumerate(value)]  # 这里又为什么只显示v['name']
        # 表单字段设为ChoiceField类型,用生成下拉框
        person = forms.ChoiceField(choices=choices, label='姓名') # choices=choices下拉框
    

    表单类中定义的字段和html标签的比较

    字段label转化为label标签, forms.CharField转化为
    input type="text",job的命名转换为<input>控件的参数name,表单字段的max_length变为input控件的maxlength参数

    # 表单类VocationForm的表单字段job
    job = forms.CharField(max_length=20, label='职位')
    # html标签
    <tr><th><label for="id_job">职位:</label></th><td><input type="text" name="job" maxlength="20" required id="id_job"></td></tr>
    

    form源码分析

    表单的定义过程、表单的字段类型和表单字段的参数类型是表单的核心功能。
    表单的定义过程
    继承自两个类,具体可看源码位置:django/forms/forms.py

    class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
    

    表单的属性和方法;源码位置:django/forms/forms.py

    #BaseForm中定义的属性
        def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                     initial=None, error_class=ErrorList, label_suffix=None,
                     empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
    #表单的方法
    errors():验证表单数据是否存在异常,若存在,则获取异常信息,异常信息可设为字典或json格式
    is_valid():验证表单数据是否存在异常,若存在,则返回false,否则返回true
    as_table():将表单字段以html的<table>标签生成网页表单
    as_ul():将表单字段以html的<ul>标签生成网页表单
    as_p():将表单字段以html的<p>标签生成网页表单
    has_changed():对比用于提交的表单数据与表单初始化数据是否发送变化。
    

    表单字段的参数类型;源码文件位置:django/forms/fields.py

    #可在文件中查看
    __all__ = (
        'Field', 'CharField', 'IntegerField',
        'DateField', 'TimeField', 'DateTimeField', 'DurationField',
        'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
        'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
        'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
        'SplitDateTimeField', 'GenericIPAddressField', 'FilePathField',
        'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField', 'UUIDField',
    )
    
    # 列举几个常用表单;反正都是为了在html页面上显示出这几类框:文本框;数值框;文本上传框;下拉框;复选框
    CharField:文本框,参数max_length和min_length分别设置文本长度
    IntegerField:数值框,参数max_value设置最大值,min_value设置最小值
    FileField:文本上传框,参数max_length设置上传文件名的最大长度,参数allow_empty_file设置是否允许文件内容为空。
    ChoiceField:下拉框,参数choice与元组形式表示,用于设置下拉框的选项列表
    BooleanField:复选框,设有选项true和false,如果字段带有required=true,复选框就默认为true.
    

    实例

    from django import forms
    from .models import *
    from django.core.exceptions import ValidationError
    # 自定义数据验证函数
    def payment_validate(value): 
        if value > 30000:
            raise ValidationError('请输入合理的薪资')
    
    class VocationForm(forms.Form): # form.Form的字段和models中的字段类似
        job = forms.CharField(max_length=20, label='职位') 
        # 设置字段参数widget、error_messages
        # 参数widwget必须是个forms.widgets对象,而widwget的对象必须和表单字段类型相互对应,对应该则分为4大类,该定义的函数中可以传入css样式的class,id等属性
        title = forms.CharField(max_length=20, label='职称', #下面这句话啥意思
                                # 以修饰的css样式为先,但它这里还能更改html表单的类型,TextInput表述文本框,若是charfield显示下拉框,优先使用文本框
                                widget=forms.widgets.TextInput(attrs={'class': 'c1'}), # 设置表单的css样式,TextInput表示文本输入框,要和charfield输入样式相同
                                error_messages={'required': '职称不能为空'},) # 设置验证失败后的参数信息,字典的键为表单的参数名称,字典的值为错误信息
        # 设置字段参数validators;validators参数一定要列表形式么;该参数是自定义验证函数
        payment = forms.IntegerField(label='薪资',validators=[payment_validate]) # IntegerField字段还可以添加额外验证函数
        # 设置下拉框的值
        # 查询模型PersonInfo的数据
        value = PersonInfo.objects.values('name')
        # 将数据以为列表格式表示,列表元素为元组格式
        choices = [(i+1, v['name']) for i, v in enumerate(value)] # enumerate()生成(索引,值)的列表
        # 表单字段设为ChoiceField类型,用生成下拉框
        person = forms.ChoiceField(choices=choices, label='姓名')
    
        # 自定义表单字段title的数据清洗(修改该字段的返回值)
        # 最好函数名要与获取的表单字段名一样,方便阅读
        def clean_job(self): # 函数应该是自定义的,并不是,返回值的话会返回给该字段
            # 获取字段title的值
            # 类可以调用方法中的属性,是由于有self么
            # 提取的是表单字段的属性
            data = self.cleaned_data['job'] # cleaned_data是默认的属性
            return '初级' + data  # 通过views.py返回给终端
    
    

    ModelForm源码分析

    djano/forms/models.py
    主要类
    class BaseModelForm:中
    表单类ModelForm于模型之间没有直接的数据交互,模型表单与模型之间的数据交互是由函数modelform_factory实现的,该函数将自定义的模型表单与模型进行绑定,从而实现两者之间的数据交互。

    模型表单的主要方法

    clean():重写父类BaseForm的clean()方法,并将属性_validate_unique设为True。
    validate_unique():验证表单数据是否存在异常
    _save_m2m():将带有多对多关系的模型表单保存到数据库里
    save():将模型表单的数据保存到数据库里。如果参数commit为True,就直接保存在数据库;否则生成数据库实例对象。
    

    函数modelform_factory与类ModelForm定义在同一个源码文件中

    模型表单的主要属性

    model:必需属性,用于绑定Model对象
    fields=None,:可选属性,设置模型内那些字段转换成表单字段,默认值为None,代表所有的模型字段,也可以将属性值设为"__all__",同样表示所有的模型字段。若只需部分模型字段,则将模型字段写入一个列表或一个元组里,再把该列表或元组作为属性值。
    exclude=None:与field相反,禁止模型字段转换成表单字段。属性值以列表或元组表示,若设置了该属性,则属性fields无需设置。
    widgets=None:可选属性,设置表单字段的参数widget,属性值以字典表示,字典的键为模型字段
    localized_fields=None:可选参数,将模型字段设为本地化的表单字段,常用于日期类型的模型字段。                     
    help_texts=None:可选属性,设置表单字段的参数help_text.
    error_messages=None:可选属性,设置表单字段的参数error_messages
    field_classes=None:可选属性,将模型字段重新定义,默认情况下,模型字段与表单字段遵从Django内置的转换规则。
    

    模型字段与表单字段的转换规则

    待补
    

    实例

    from django import forms
    from .models import *
    
    class VocationForm(forms.ModelForm):
        # 添加模型外的表单字段;在模型已有的字段下再添加字段;添加的html位置在表单最下方
        LEVEL = (('L1', '初级'),
                 ('L2', '中级'),
                 ('L3', '高级'),)
        level = forms.ChoiceField(choices=LEVEL, label='级别') # 看来choices的前缀L1,L2之类的就是索引
        # 模型与表单设置
        class Meta:
            # 绑定模型
            model = Vocation
            # 以下属性都是函数modelform_factory的属性
            # fields属性用于设置转换字段,'__all__'是将全部模型字段转换成表单字段
            # fields = '__all__'
            # fields = ['job', 'title', 'payment', 'person']
            # exclude用于禁止模型字段转换表单字段
            exclude = []
            # labels设置HTML元素控件的label标签
            labels = {
                'job': '职位',
                'title': '职称',
                'payment': '薪资',
                'person': '姓名'
            }
            # 定义widgets,设置表单字段的CSS样式
            widgets = {
                'job': forms.widgets.TextInput(attrs={'class': 'c1'}),
            }
            # 重新定义字段类型
            # 一般情况下模型字段会自动转换成表单字段
            field_classes = {
                'job': forms.CharField
            }
            # 帮助提示信息
            help_texts = {
                'job': '请输入职位名称'
            }
            # 自定义错误信息
            error_messages = {
                # __all__设置全部错误信息
                '__all__': {'required': '请输入内容',
                            'invalid': '请检查输入内容'},
                # 设置某个字段的错误信息
                'title': {'required': '请输入职称',
                          'invalid': '请检查职称是否正确'}
            }
        # 自定义表单字段payment的数据清洗
        def clean_payment(self):
            # 获取字段payment的值
            data = self.cleaned_data['payment'] + 1
            return data
    
    

    表单与模型表单小结

    表单forms的属性和方法的源文件

    django/forms/forms.py
    主要类class BaseForm: (类中)
    属性调用不加(),函数调用加()


    表单字段类型和模型字段类型类似
    源码位置django/forms/fields.py

    视图里使用form

    属性
    prefix

    prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
                # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
    

    initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
    post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据,把数据库中的数据写到表单上

    无论是统计字符串还是列表
    xx.count(传入统计的参数)

    filter查询字段为0的情况下,就无法使用update,需要使用get_or_create(d),除主键外,只要有一个数据不同就插入
    create(
    d),直接插入

    详情实例待补

    #这么多没写,不做笔记,一点也不记得了
    from django.shortcuts import render
    from django.http import HttpResponse
    from .form import * # 导入表单进视图
    from .models import * # 导入模型进视图
    def index(request):
        # GET请求
        if request.method == 'GET':
            id = request.GET.get('id', '')
            if id:
                d = Vocation.objects.filter(id=id).values() # 获取数据库数据
                d = list(d)[0]
                print(d)
                d['person'] = d['person_id'] # 增加person键值对
                # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
                i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
                # 将参数i传入表单VocationForm执行实例化
                print(i)
                v = VocationForm(**i) # 这是把数据库中的数据写到表单上
            else: # 不带get请求参数,并设置参数prefix,这参数的作用是
                # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
                # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
                v = VocationForm(prefix='vv')
            return render(request, 'index.html', locals())
        # POST请求
        else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
            # 由于在GET请求设置了参数prefix
            # 实例化时必须设置参数prefix,否则无法获取POST的数据
            v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
            if v.is_valid():
                # 获取网页控件name的数据
                # 方法一
                title = v['title']
                # 方法二
                # cleaned_data将控件name的数据进行清洗
                ctitle = v.cleaned_data
                print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
                # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
                id = request.GET.get('id', '')
                d = v.cleaned_data # 获取数据
                # count统计的话一定要传递参数
                print(d['person']) # 这是多的
                # 外键要特别加
                d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
                # 删除字典中的person字段
                del d['person']
                # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
                # Vocation.objects.create(**d) # 更新数据需以字典显示
                # 不这样更新,这样更新太难受了,常报错
                # 是这个Vocation这个模型有毒把
                # result = Vocation.objects.filter(id=id) # 提取所有的id字段
                # result = False
                # if not result:
                #     v.save()
                # else:
                #     return int(value)
    # ValueError: invalid literal for int() with base 10: ''
                # Vocation.objects.filter(id=id).update(**d)
                # 可能是多了一个person字段
                Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
                return HttpResponse('提交成功')
            else:
                # 获取错误信息,并以json格式输出
                error_msg = v.errors.as_json()
                print(error_msg)
                return render(request, 'index.html', locals())
    

    视图里使用ModelForm

    表单类Form和模型实现数据交互最主要的问题是表单字段和模型字段的匹配性,如果将表单类Form改为ModelForm,就无需考虑字段匹配性的问题。
    最好使用模型表单,表单类提取数据保存太难受了,我的报错问题:

    ValueError: invalid literal for int() with base 10: ''
    
    # commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
                    # commit=True则直接将表单数据保存到数据库中
    

    实例

    from django.shortcuts import render
    from django.http import HttpResponse
    from .form import *
    from .models import *
    def index(request):
        # GET请求
        if request.method == 'GET':
            id = request.GET.get('id', '')
            if id:
                i = Vocation.objects.filter(id=id).first() #这里id提取的是int类型么
                # 将参数i传入表单VocationForm执行实例化
                v = VocationForm(instance=i, prefix='vv') # instance参数为空的话,则返回一个新的表单
            else:
                v = VocationForm(prefix='vv')
            return render(request, 'index.html', locals())
        # POST请求
        else:
            # 由于在GET请求设置了参数prefix
            # 实例化时设置参数prefix,否则无法获取POST的数据
            v = VocationForm(data=request.POST, prefix='vv') #实例化的模型字段
            # is_valid()会使字段payment自增加10
            if v.is_valid():
                # 根据请求参数id查询模型数据是否存在
                id = request.GET.get('id')
                result = Vocation.objects.filter(id=id) # 提取所有的id字段
                # 数据不存在,则新增数据
                if not result:
                    # 数据保存方法一
                    # 直接将数据保存到数据库
                    # v.save()
                    # 数据保存方法二
                    # 将save的参数commit=False
                    # 生成数据库对象v1,修改v1的属性值并保存
                    # commit=False就会生成一个数据库对象,然后可以对该数据库对象进行增删改除,然后将修改的数据保存到数据库中。
                    # commit=True则直接将表单数据保存到数据库中
                    v1 = v.save(commit=False) # 直接保存字段
                    v1.title = '初级' + v1.title
                    v1.save()
                    # 数据保存方法三
                    # save_m2m()方法用于保存ManyToMany的数据模型
                    # v.save_m2m()
                    return HttpResponse('新增成功')
                # 数据存在,则修改数据
                else:
                    d = v.cleaned_data
                    d['title'] = '中级' + d['title']
                    result.update(**d)
                    print(d)
                    return HttpResponse('修改成功')
            else:
                # 获取错误信息,并以json格式输出
                error_msg = v.errors.as_json()
                print(error_msg)
                return render(request, 'index.html', locals())
    

    8.4视图里使用Form

    from django.shortcuts import render
    from django.http import HttpResponse
    from .form import * # 导入表单进视图
    from .models import * # 导入模型进视图
    def index(request):
        # GET请求
        if request.method == 'GET':
            id = request.GET.get('id', '')
            if id:
                d = Vocation.objects.filter(id=id).values() # 获取数据库数据
                d = list(d)[0]
                print(d)
                d['person'] = d['person_id'] # 增加person键值对
                # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
                i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
                # 将参数i传入表单VocationForm执行实例化
                print(i)
                v = VocationForm(**i) # 这是把数据库中的数据写到表单上
            else: # 不带get请求参数,并设置参数prefix,这参数的作用是
                # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
                # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
                v = VocationForm(prefix='vv')
            return render(request, 'index.html', locals())
        # POST请求
        else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
            # 由于在GET请求设置了参数prefix
            # 实例化时必须设置参数prefix,否则无法获取POST的数据
            v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
            if v.is_valid():
                # 获取网页控件name的数据
                # 方法一
                title = v['title']
                # 方法二
                # cleaned_data将控件name的数据进行清洗
                ctitle = v.cleaned_data
                print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
                # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
                id = request.GET.get('id', '')
                d = v.cleaned_data # 获取数据
                # count统计的话一定要传递参数
                print(d['person']) # 这是多的
                # 外键要特别加
                d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
                # 删除字典中的person字段
                del d['person']
                # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
                # Vocation.objects.create(**d) # 更新数据需以字典显示
                # 不这样更新,这样更新太难受了,常报错
                # 是这个Vocation这个模型有毒把
                # result = Vocation.objects.filter(id=id) # 提取所有的id字段
                # result = False
                # if not result:
                #     v.save()
                # else:
                #     return int(value)
    # ValueError: invalid literal for int() with base 10: ''
                # Vocation.objects.filter(id=id).update(**d)
                # 可能是多了一个person字段
                Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
                return HttpResponse('提交成功')
            else:
                # 获取错误信息,并以json格式输出
                error_msg = v.errors.as_json()
                print(error_msg)
                return render(request, 'index.html', locals())
    

    8.5### 表单的数据提取判断等逻辑都值得好好推敲
    表单类的定义可参考前面的表单类

    from django.shortcuts import render
    from django.http import HttpResponse
    from .form import * # 导入表单进视图
    from .models import * # 导入模型进视图
    def index(request):
        # GET请求
        if request.method == 'GET':
            id = request.GET.get('id', '')
            if id:
                d = Vocation.objects.filter(id=id).values() # 获取数据库数据
                d = list(d)[0]
                print(d)
                d['person'] = d['person_id'] # 增加person键值对
                # initial是表单实例化的初始化数据,他只适用于模型数据传递给表单,再由表单显示在网页上
                i = dict(initial=d, label_suffix='*', prefix='vv') # 字典参数构成
                # 将参数i传入表单VocationForm执行实例化
                print(i)
                v = VocationForm(**i) # 这是把数据库中的数据写到表单上
            else: # 不带get请求参数,并设置参数prefix,这参数的作用是
                # prefix的作用就是为form命名,以区分多个表单,当然它实例化出来的html标签属性值是带有该参数
                # 您可以将多个 Django 表单放在一个<form>标签中。要给每个 Form自己的命名空间,请使用关键字参数:prefix
                v = VocationForm(prefix='vv')
            return render(request, 'index.html', locals())
        # POST请求
        else: # post请求从表单中获取数据然后保存起来到数据库中,get请求是在url链接上的id获取,然后再模型中找到相应的数据
            # 由于在GET请求设置了参数prefix
            # 实例化时必须设置参数prefix,否则无法获取POST的数据
            v = VocationForm(data=request.POST, prefix='vv') # 参数data是在表单实例化之后,再将数据传递给实例化对象,只适用于接收http的请求
            if v.is_valid():
                # 获取网页控件name的数据
                # 方法一
                title = v['title']
                # 方法二
                # cleaned_data将控件name的数据进行清洗
                ctitle = v.cleaned_data
                print(ctitle) # {'job': '全栈工程师1', 'title': '初级java开发', 'payment': 1234, 'person': '1'}
                # 将数据更新到模型Vocation;这样只能做到只能更改字段,无法做到提交字段
                id = request.GET.get('id', '')
                d = v.cleaned_data # 获取数据
                # count统计的话一定要传递参数
                print(d['person']) # 这是多的
                # 外键要特别加
                d['person_id'] = int((d['person']))# 表单字段转化为模型字段,主要是这里增加了person字段
                # 删除字典中的person字段
                del d['person']
                # 若有重复id则更新,但这里filter对于空白字段的id,提取过滤的是无,自然更新不了,报错
                # Vocation.objects.create(**d) # 更新数据需以字典显示
                # 不这样更新,这样更新太难受了,常报错
                # 是这个Vocation这个模型有毒把
                # result = Vocation.objects.filter(id=id) # 提取所有的id字段
                # result = False
                # if not result:
                #     v.save()
                # else:
                #     return int(value)
    # ValueError: invalid literal for int() with base 10: ''
                # Vocation.objects.filter(id=id).update(**d)
                # 可能是多了一个person字段
                Vocation.objects.create(**d)  # 表单字段可以提取数据出来,但是不能保存到数据库,是表单字段设计出错么
                return HttpResponse('提交成功')
            else:
                # 获取错误信息,并以json格式输出
                error_msg = v.errors.as_json()
                print(error_msg)
                return render(request, 'index.html', locals())
    

    实现表单数据与模型交互需要注意以下事项

    表单字段最好与模型字段相同,否则两种再进行数据交互时,不许将两者的字段进行转化
    使用同一表单并且需要多次实例化表单时,处理参数initial和data的数据不同之外,其他参数设置必须相同,否则无法接收上一个表单对象所传递的数据信息。
    参数initial是表单实例化的初始化数据,它只适用于模型数据传递给表单,再由表单显示在网页上;参数data是在表单实例化后,再将数据传递给实例化对象,只适用于表单接收http请求的请求参数。
    参数prefix设置表单的控件属性name和id的值;若在一个网页里使用同一个表单类生成多个不同的网页表单,参数prefix可区分每个网页表单,则在接收或设置某个表单数据时与其他的表单数据混淆。
    

    模型表单ModelForm实现数据保存只有save()和save_m2m()两种方法。使用save()保存数据时,参数commit的值会影响数据的保存方式。如果参数Commit为True,就直接将表单数据保存到数据库;如果参数commit为False,就会生成一个数据库对象,然后可以对该对象进行增、删、改、查等数据操作。再将修改后的数据保存到数据库。
    save()只适合将数据保存在非多对多关系的数据表中,而save_m2m()只适合将数据保存在多对多关系的数据表中。

  • 相关阅读:
    Android的目录结构说明
    IOS-线程(GCD)
    iOS UI-线程(NSThread)及其安全隐患与通信
    iOS UI-自动布局(AutoLayout)
    iOS UI-自动布局(Autoresizing)
    IOS-Core Data的使用
    OC 数据持久化(数据本地化)- 本地存储
    iOS UI-应用管理(使用Cell模板)
    IOS UI-QQ好友列表
    IOS-多线程
  • 原文地址:https://www.cnblogs.com/wkhzwmr/p/15657317.html
Copyright © 2020-2023  润新知