zoukankan      html  css  js  c++  java
  • Python学习第七模块笔记(Web开发进阶之Django数据库操作)

    1、连接数据库

    创建Django工程后运行该工程,会在工程根目录下创建db.sqlite3文件,为Django自带的sqlite3数据库(Django自带的功能也需要数据库支持),如果没有在settings.py文件中进行配置的话,数据将会保存在该数据库中。

    Django使用MySQL:

    # 修改settings.py中DATABASES = {}的内容为
    DATABASES = {
        'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME':'数据库名称',
        'USER': '用户名',
        'PASSWORD': '密码',
        'HOST': '主机',
        'PORT': '端口',
        }
    }
    
    # Python 3.x中让Django使用pymysql连接MySQL,在工程配置目录下的__init__.py文件中添加如下内容
    import pymysql
    pymysql.install_as_MySQLdb()

    2、创建表结构

    在Django中定义一个类来自动生成表结构,类写在APP目录下的models.py文件中。

    要使models.py文件生效,需要先在settings.py文件中注册该APP:

    # 在settings.py中的INSTALLED_APPS = 下添加
    ‘APP名’,

    在models.py文件下编写:

    from django.db import models
    
    
    class Foo(models.Model):
        xxx = models.字段类型(参数)
    
        # 自定义表名、联合索引及联合唯一索引
        class Meta:
            db_table = "表名"
            index_together = [(字段1, 字段2, ...)]    # 联合索引,遵循最左前缀模式,即使用字段1和字段1+字段2均可命中索引,而使用字段2则无法命中
            unique_together = ((字段1, 字段2, ...))    #联合唯一索引
            verbose_name = ""    # 在Django Admin中显示的表名,后面加s
            verbose_name_plural = ""    # 在Django Admin中显示的表名,后面不加s

    以下为具体字段类型:

    # 自增
    AutoField(Field),int自增列,必须填入参数 primary_key=True
    BigAutoField(AutoField),bigint自增列,必须填入参数 primary_key=True
    # 注:当model中如果没有自增列,则自动会创建一个列名为id的列
    
    # 数字
    SmallIntegerField(IntegerField),小整数 -32768 ~ 32767
    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField),正小整数 0 ~ 32767
    IntegerField(Field),整数列(有符号的) -2147483648 ~ 2147483647
    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField),正整数 0 ~ 2147483647
    BigIntegerField(IntegerField),长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
    自定义无符号整数字段
    class UnsignedIntegerField(models.IntegerField):
        def db_type(self, connection):
            return 'integer UNSIGNED'
    PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
                'AutoField': 'integer AUTO_INCREMENT',
                'BigAutoField': 'bigint AUTO_INCREMENT',
                'BinaryField': 'longblob',
                'BooleanField': 'bool',
                'CharField': 'varchar(%(max_length)s)',
                'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
                'DateField': 'date',
                'DateTimeField': 'datetime',
                'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
                'DurationField': 'bigint',
                'FileField': 'varchar(%(max_length)s)',
                'FilePathField': 'varchar(%(max_length)s)',
                'FloatField': 'double precision',
                'IntegerField': 'integer',
                'BigIntegerField': 'bigint',
                'IPAddressField': 'char(15)',
                'GenericIPAddressField': 'char(39)',
                'NullBooleanField': 'bool',
                'OneToOneField': 'integer',
                'PositiveIntegerField': 'integer UNSIGNED',
                'PositiveSmallIntegerField': 'smallint UNSIGNED',
                'SlugField': 'varchar(%(max_length)s)',
                'SmallIntegerField': 'smallint',
                'TextField': 'longtext',
                'TimeField': 'time',
                'UUIDField': 'char(32)',
    
    # 布尔
    BooleanField(Field),布尔值类型
    NullBooleanField(Field),可以为空的布尔值
    
    # 字符    
    CharField(Field),字符类型,必须提供max_length参数, max_length表示字符长度
    TextField(Field),文本类型
    EmailField(CharField),字符串类型,Django Admin以及ModelForm中提供验证机制
    GenericIPAddressField(Field),字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
        - 参数:
            protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
            unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"
    URLField(CharField),字符串类型,Django Admin以及ModelForm中提供验证URL
    SlugField(CharField),字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
    CommaSeparatedIntegerField(CharField),字符串类型,格式必须为逗号分割的数字
    UUIDField(Field),字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
    FilePathField(Field),字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
        - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹
    FileField(Field),字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
    ImageField(FileField),字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
            width_field=None,   上传图片的高度保存的数据库字段名(字符串)
            height_field=None   上传图片的宽度保存的数据库字段名(字符串)
    
    # 日期时间
    DateTimeField(DateField),日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
    DateField(DateTimeCheckMixin, Field),日期格式      YYYY-MM-DD
    TimeField(DateTimeCheckMixin, Field),时间格式      HH:MM[:ss[.uuuuuu]]
    DurationField(Field),长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
    
    # 浮点
    FloatField(Field),浮点型
    DecimalField(Field),10进制小数
        - 参数:
            max_digits,小数总长度
            decimal_places,小数位长度
    
    # 二进制
    BinaryField(Field),二进制类型

    以下为具体参数:

    null                 -> 是否为空
    default              -> 默认值
    primary_key          -> 主键
    db_column            -> 列名
    db_index             -> 索引
    unique               -> 唯一索引
    unique_for_date      -> 基于日期的唯一索引
    unique_for_month     -> 基于月份的唯一索引
    unique_for_year      -> 基于年份的唯一索引
    auto_now             -> 创建时自动生成时间
    auto_now_add         -> 更新时自动更新时间,只能在使用obj.save()更新时生效
    
    # 以下参数应用与Django Admin中
    choices              -> 显示下拉框,避免连表查询
    blank                -> 是否可以为空
    verbose_name         -> 显示字段中文
    editable             -> 是否可以编辑
    error_messages       -> 错误信息
    help_text            -> 提示
    validators           -> 自定义错误信息

    表结构创建完成后,使用以下命令写入数据库:

    python manage.py makemigrations
    python manage.py migrate

    3、表间一对一、一对多、多对多关联的创建

    3.1、一对一关联

    from APP import models
    
    
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
        xxx1 = models.OneToOneField(to = "Foo1", to_field = "id")
        # 也可以简写为xxx1 = models.OneToOneField("Foo1"),将自动与表的主键关联
    
    # 参数:
        to,                 # 要进行关联的表名
        to_field=None       # 要关联的表中的字段名称
        on_delete=None,     # 当删除关联表中的数据时,当前表与其关联的行的行为,具体参见一对多描述
    
    # 一对一其实就是 一对多 + 唯一索引
    # 当两个类之间有继承关系时,默认会创建一个一对一字段

    3.2、一对多表的创建

    from APP import models
    
    
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
        xxx1 = models.ForeignKey(to = "Foo1", to_field = "id")
        # 也可以简写为xxx1 = models.ForeignKey("Foo1"),将自动与表的主键关联
    
    # Foo2表中将生成xxx1_id列,关联Foo1表的id列,而此处的xxx1为一个包含Foo1表内容的对象
    
    # 参数:
        to,                        # 要进行关联的表名
        to_field=None,             # 要关联的表中的字段名称
        on_delete=None,            # 当删除关联表中的数据时,当前表与其关联的行的行为
                                               - models.CASCADE,删除关联数据,与之关联也删除
                                               - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                                               - models.PROTECT,删除关联数据,引发错误ProtectedError
                                               - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                                               - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                                               - models.SET,删除关联数据,
                                                       a. 与之关联的值设置为指定值,设置:models.SET(值)
                                                       b. 与之关联的值设置为函数的返回值,设置:models.SET(函数)
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                         - limit_choices_to={'nid__gt': 5}
        db_constraint=True          # 是否在数据库中创建外键约束
        parent_link=False           # 在Admin中是否显示关联数据

    3.3、多对多表的创建

    为了实现两张表之间的多对多关联,必须借助第三张表实现;用以下三种方式创建两张表间的多对多关联:

    from APP import models
    
    # 自定义关系表1(无ManyToMany字段)
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
    
    
    class Foo1ToFoo2(models.Model):
        f1 = models.ForeignKey(to="Foo1", to_field="id")
        f2 = models.ForeignKey(to="Foo2", to_field="id")
        # 通过创建两个ForeignKey来关联两个表
    # 使用该方式的好处是可定制性强,在第三张表中可以自定义字段;缺点是操作时必须自己对第三张表进行增删改查操作
    
    
    # 自定义关系表2(有ManyToMany字段)
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
         x = models.ManyToManyField("Foo1", through = "Foo1ToFoo2", through_fields = ["f1", "f2"])
        # 指定通过Foo1ToFoo2表进行关联,并指定关联的字段
    
    
    class Foo1ToFoo2(models.Model):
        f1 = models.ForeignKey(to="Foo1", to_field="id")
        f2 = models.ForeignKey(to="Foo2", to_field="id")
        f3 = models.字段类型()
    # 可以通过ManyToMany字段进行查询和清除(clear)操作
    
    
    # Django自动创建关系表
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
        x = models.ManyToManyField("Foo1")
    # 使用该方式Django将自动维护关联的第三张表,用户无法对该表进行操作

    参数:

    to,                         # 要进行关联的表名
    to_field=None,              # 要关联的表中的字段名称
    related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】
    related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】
    limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
    symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                     # 做如下操作时,不同的symmetrical会有不同的可选字段
                                            models.BB.objects.filter(...)
    
                                     # 可选字段有:code, id, m1
                                     class BB(models.Model):
                                         code = models.CharField(max_length=12)
                                         m1 = models.ManyToManyField('self',symmetrical=True)
                                     # 可选字段有: bb, code, id, m1
                                     class BB(models.Model):
                                         code = models.CharField(max_length=12)
                                         m1 = models.ManyToManyField('self',symmetrical=False)
    
    through=None,               # 自定义第三张表时,使用字段用于指定关系表
    through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
    db_constraint=True,         # 是否在数据库中创建外键约束
    db_table=None,              # 默认创建第三张表时,数据库中表的名称

    4、数据库操作

    4.1、基本增删改查操作

    向表中添加数据:

    from APP import models
    models.类名.objects.create(xxx=' ',...)
    
    dict = {}
    models.类名.objects.create(**dict)
    
    obj = models.类名(xxx=' ',...)
    obj.save()

    查询数据:

    from APP import models
    
    #查询全部数据,可使用以下三种方式
    
    models.类名.objects.all()
    #返回一个QuerySet(类似列表),内部元素都是对象
    
    models.类名.objects.all()[a, b]
    # SQL语言中的limit与offset
    
    models.类名.objects.all().values('字段', ...)
    # 返回一个QuerySet,内部元素都是字典
    
    models.类名.objects.all().values_list('字段', ...)
    # 返回一个QuerySet,内部元素都是元组
    
    
    # 条件查询
    
    models.类名.objects.filter(条件1, 条件2, ...)
    dict = {条件1, 条件2, ...}
    models.类名.objects.filter(**dict)
    # 按条件查询,返回一个QuerySet
    
    models.类名.objects.get(条件1, 条件2, ...)
    # 使用该方式获取时数据不存在会报错
    
    models.类名.objects.exclude(条件1, 条件2, ...)
    # 排除指定的条件,返回一个QuerySet
    
    # 条件表达式
        - =                  --> 等于
        - __gt =             --> 大于
        - __lt =             --> 小于
        - __gte =            --> 大于等于
        - __lte =            --> 小于等于
        - __in = []          --> 在列表中的值
        - __isnull = True    --> 是否为空
        - __contains =       --> SQL语句中的like,__icontains为不区分大小写,需数据库支持不区分大小写
        - __range = []       --> 范围
        - __startswith =     --> 以什么开头,__istartswith不区分大小写
        - __endswith =       --> 以什么结尾,__iendswith不区分大小写
        - __regex =          --> 正则匹配,__iregex不区分大小写
        - __date =           --> 年月日
        - __year =           --> 年
        - __month =          --> 月
        - __day =            --> 日
        - __week_day =       --> 周
        - __hour =           --> 小时
        - __minute =         --> 分钟
        - __second =         --> 秒

    修改和删除数据:

    # 要进行修改和删除操作,都必须先查询数据,然后进行下一步操作
    
    from APP import models
    
    # 修改,两种方式
    models.类名.objects.filter(条件).update(xxx='', ...)
    
    dict = {}
    models.类名.objects.filter(条件).update(**dict)
    
    obj = models.类名.objects.filter(条件)
    obj.xxx = 新值
    obj.save()
    
    
    # 删除
    models.类名.objects.filter(条件).delete()

    4.2、其他操作

    一些基本操作:

    # 计数,返回QuerySet
    models.类名.objects.filter(xx=xxx).count()
    
    # 排序,返回QuerySet
    models.类名.objects.filter(xx=xxx).order_by("xx")    # ASC
    models.类名.objects.filter(xx=xxx).order_by("-xx")    #DESC
    # order_by可加多个参数,优先按照id排序
    
    # 倒序,必须配合order_by使用,如果有多个排序则一一倒序,返回QuerySet
    models.类名.objects.filter(xx=xxx).order_by("xx").reverse()
    
    # 分组,返回QuerySet
    from django.db.models import Count, Min, Max, Sum
    models.Tb1.objects.filter(xx=xxx).values("id").annotate(c=Count('num'))
    # 对应SQL语句 SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"
    # 这里的values不是取值,而是根据values中的字段分组
    models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
    # 对应SQL语句 SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
    
    # 去重,只有在PostgreSQL中才能使用,返回QuerySet
    models.类名.objects.filter(xx=xxx).distinct("xx")
    
    # 指定使用的数据库
    models.类名.objects.using()
    
    # 生成空的QuerySet
    models.类名.objects.none()
    
    # 聚合,获取字典类型结果
    from django.db.models import Count, Avg, Max, Min, Sum
    result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) ===> {'k': 3, 'n': 4}
    
    # 批量插入数据
    obj = [models.类名(xxx="", ...), models.类名(xxx="", ...), ...]
    models.类名.objects.bulk_create(obj, 一次插入的个数)
    
    # 数据存在则获取,不存在则创建
    models.类名.objects.get_or_create(xx=xxx, defaults=其他字段数据)
    
    # 数据存在则获取,不存在则更新
    models.类名.objects.update_or_create(xx=xxx, defaults=其他字段数据)
    
    # 获取查询出的第一个数据
    models.类名.objects.filter(xx=xxx).first()
    
    # 获取查询出的最后一个数据
    models.类名.objects.filter(xx=xxx).last()
    
    # 根据主键ID进行查找
    id_list = []
    models.类名.objects.in_bulk(id_list)
    
    # 是否有结果
    models.类名.objects.filter(xx=xxx).exists()

    获取指定字段数据:

    # 获取指定字段数据
    models.类名.objects.only("xx", "xxx", ...)
    
    # 排除指定字段数据
    models.类名.objects.defer("xx", "xxx", ...)
    
    # 注意:没有获取到的字段照样可以使用,但Django将重新发起一次到数据库的请求以获取数据

    提高跨表查询性能:

    # 当有跨表查询出现时,在查询时并不进行跨表操作,只有当调用跨表的数据时Django才会向数据库发起请求读取需要的数据,所以只要有一次跨表调用Django就会向数据库发起一次请求,这样将会对性能照成影响,可以使用以下方法解决
    
    # 查询时直接跨表,一次性获取关联数据
    models.类名.objects.all().select_related("外键字段")
    # 不加外键字段时默认获取所有关联表的数据
    
    # 先查询当前表,然后根据从当前表中获取的id到关联表中进行查询取得数据
    models.类名.objects.all().select_related("外键字段")
    # 不加外键字段时默认获取所有关联表的数据

    根据时间去重并获取指定内容:

    # 根据时间进行某一部分去重并获取指定内容及指定时间格式
    models.类名.objects.dates(xxx, "year(年)/month(年-月)/day(年-月-日)", order="ASC/DESC")
    
    # 根据时间进行某一部分去重并获取指定内容及将时间转换为时区时间
    models.类名.objects.datetimes(xxx, "year/month/day/hour/minute/second", order="ASC/DESC", tzinfo=时区)
    # 可以使用pytz模块设置时区,pytz为第三方模块,需要使用pip安装
    # pytz.all_timezones      --> 获取所有时区名称
    # pytz.timezone("时区")    --> 指定使用的时区

    构造额外查询条件:

    models.类名.objects.extra(select={}, where=[], params= , tables=表名, order_by=xxx, select_params=)
    # 参数:
    #    select          --> select中的额外查询条件
    #    where           --> where中的额外查询条件
    #    params          --> 当where中出现%s时的值
    #    select_params   --> 当select中出现%s时的值
    
    # e.g:
    models.Tb1.objects.extra(select={"id": %s}, select_params=(1, ))
    # 对应SQL语句
    select *, 1 as id form tb1
    
    models.Tb1.objects.extra(where=["id=1", "name='xxx' "])    # ,相当于and
    models.Tb1.objects.extra(where=["id=1 or name='xxx' "])

    使用原生SQL:

    # 执行原生SQL
    models.UserInfo.objects.raw('select * from userinfo')
    
    # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
    models.UserInfo.objects.raw('select id as nid from 其他表')
    
    # 为原生SQL设置参数
    models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
    
    # 将获取的到列名转换为指定列名
    name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
    Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
    
    # 指定数据库
    models.UserInfo.objects.raw('select * from userinfo', using="default")

    5、表间一对多和多对多关联数据操作

    5.1、表间一对多关联操作

    # 表间一对多关联
    from APP import models
    
    
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
        xxx1 = models.ForeignKey(to = "Foo1", to_field = "id")
    
    
    # 正向跨表,即通过表foo2对foo1进行操作
    
    obj = models.Foo2.objects.filter(id=n).first()
    obj.xxx1.foo1_xxx
    
    models.Foo2.objects.all().values(xxx1__foo1_xxx)
    
    # 反向跨表,即通过表foo1对表foo2进行操作
    
    obj = models.Foo1.objects.filter(id=n).first()
    obj.foo2_set.all()
    
    models.Foo1.objects.all().values(foo2__foo2_xxx)

    5.2、表间多对多关联操作

    Django自动创建关系表情况下的第三张表操作:

    # 表间多对多关联
    from APP import models
    
    
    class Foo1(models.Model):
        foo1_xxx = models.字段类型()
    
    
    class Foo2(models.Model):
        foo2_xxx = models.字段类型()
        x = models.ManyToManyField("Foo1")
    
    
    # 第三张表操作
    
    # 正向操作
    obj = models.Foo2.objects.filter(id=n).first()
    
    # 添加
    obj.x.add(n)
    obj.x.add(n, n1, ...)
    obj.x.add(*[n, n1, ...])
    
    # 删除
    obj.x.remove(n)
    obj.x.remove(n, n1, ...)
    obj.x.remove(*[n, n1, ...])
    
    # 清除所有
    obj.x.clear()
    
    # 重新设置值
    obj.x.set([n, n1, ...])
    
    # 查询
    obj.x.all()
    obj.x.all().filter(xxx="...")
    
    # 反向操作
    obj = models.Foo1.objects.filter(id=n).first()
    obj.foo2_set.add(n)
    ...
    # 其他操作同正向

    6、models模块的数据验证功能

    # 在创建表的类中设置相应的字段类型
    # 使用以下方法在数据写入表前进行验证
    obj = models.类名(xxx=" ", ...)
    obj.full_clean()
    obj.save()
    # 如果写入的数据没有通过验证,则在后台抛出异常
    
    # 使用Django提供的钩子自定义验证
    # 在创建表的类中构建
    from django.core.exceptions import ValidationError
    def clean(self):
        自定义验证
        失败主动抛出异常
        raise ValidationError(message="错误信息", code="自定义错误代码")
    
    # models验证机制
    # 首先验证数据是否符合每个字段Django定义的规则
    # 通过则验证clean中定义的规则
  • 相关阅读:
    第三章节 BJROBOT 角速度校正 【ROS全开源阿克曼转向智能网联无人驾驶车】
    第二章节 BJROBOT IMU 自动校正 【ROS全开源阿克曼转向智能网联无人驾驶车】
    【扩展】链式编程初识
    【扩展】随机数
    一、.Net基础【1.5】封装MessageBox
    一、.Net基础【1.4】不引入第三变量,交换两个变量的值
    一、.Net基础【1.3】AndAlso & OrElse Operators in C#短路运算符
    一、.Net基础【1.2】变量和数据类型
    一、.Net基础【1.0】入门
    ArcGIS Desktop 10.X 复习与提高【1.1】ArcGIS数据格式的介绍 Esri
  • 原文地址:https://www.cnblogs.com/yu2006070/p/9028768.html
Copyright © 2011-2022 走看看