Django Model

让人想犯罪 __ 提交于 2019-11-27 22:27:02

1. ORM 简介

MTV 设计模式中,模型(M)就是对数据库的操作,在 Web 开发中,使用最频繁的也是对数据库的操作,那么该怎么样去实现呢?

我们不可能自己手动去写大量的 SQL 语句,因为我们也不是专业 DBA 人员,那么我们就只能奢求有一种能够使用 Python 程序对数据库操作的方法了。这就是 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操作数据库。

其实现模式大致是这样的:

Django 本身给我们提供了强大的 ORM 系统,不需要再额外的
安装,当然还有一些其他的 ORM ,如:SQLAlch 等。

2. 字段

Model 中的字段 fileds 即数据表中的列,用来存储数据(记录),字段类型对应列的数据类型。

2.1 常用字段类型

Django 内置了很多字段类型,都位于 django.db.models 中,以下为常用字段类型:

AutoField(Field)     - int自增列,必须填入参数 primary_key=True  BigAutoField(AutoField)     - bigint自增列,必须填入参数 primary_key=True      注:当model中如果没有自增列,则自动会创建一个列名为id的列     from django.db import models      class UserInfo(models.Model):         # 自动创建一个列名为id的且为自增的整数列         username = models.CharField(max_length=32)      class Group(models.Model):         # 自定义自增列         nid = models.AutoField(primary_key=True)         name = models.CharField(max_length=32)  SmallIntegerField(IntegerField):     - 小整数 -32768 ~ 32767  PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)     - 正小整数 0 ~ 32767  IntegerField(Field)     - 整数列(有符号的) -2147483648 ~ 2147483647  PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)     - 正整数 0 ~ 2147483647  BigIntegerField(IntegerField):     - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807  BooleanField(Field)     - 布尔值类型  NullBooleanField(Field):     - 可以为空的布尔值  CharField(Field)     - 字符类型     - 必须提供max_length参数, max_length表示字符长度  TextField(Field)     - 文本类型  EmailField(CharField):     - 字符串类型,Django Admin以及ModelForm中提供验证机制  IPAddressField(Field)     - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制  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     - auto_now_add=True     当在首次创建对象时自动将字段设置为现在。用于创建时间戳     - auto_add=True         每次保存对象时,自动将字段设置为现在。用于“最后修改”的时间戳  TimeField(DateTimeCheckMixin, Field)     - 时间格式      HH:MM[:ss[.uuuuuu]]  DurationField(Field)     - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型  FloatField(Field)        - 浮点型,精确的数不能用 FloatField  DecimalField(Field)     - 10进制小数,对于比较精确的数可以用 DecimalField     - 参数:         max_digits,小数总长度         decimal_places,小数位长度  BinaryField(Field)     - 二进制类型

2.2 字段参数

null                数据库中字段是否可以为空 db_column           数据库中字段的列名 default             数据库中字段的默认值 primary_key         数据库中字段是否为主键 db_index            数据库中字段是否可以建立索引 unique              数据库中字段是否可以建立唯一索引 unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引 unique_for_month    数据库中字段【月】部分是否可以建立唯一索引 unique_for_year     数据库中字段【年】部分是否可以建立唯一索引  verbose_name        Admin中显示的字段名称 blank               Admin中是否允许用户输入为空 editable            Admin中是否可以编辑 help_text           Admin中该字段的提示信息 choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作                     如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)  error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;                     字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date                     如:{'null': "不能为空.", 'invalid': '格式错误'}  validators          自定义错误验证(列表类型),从而定制想要的验证规则                     from django.core.validators import RegexValidator                     from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\                     MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator                     如:                         test = models.CharField(                             max_length=32,                             error_messages={                                 'c1': '优先错信息1',                                 'c2': '优先错信息2',                                 'c3': '优先错信息3',                             },                             validators=[                                 RegexValidator(regex='root_\d+', message='错误了', code='c1'),                                 RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),                                 EmailValidator(message='又错误了', code='c3'), ]                         )
1. null 用来保存空值,默认值 false。对于保存字符串的字段,尽量避免将其设置为 true,否则可能会导致出现是 null 或空字符串  2. blank 表示字段可以为空,默认 false。常用语表单输入验证是否为空,与数据库无关。  3. choices 页面上选择框标签,二维元组格式。两个参数,第一个为存储在数据库中,第二个显示在页面上内容。 数据库存储的是0 、1 、2,而页面上显示的是篮球、足球、羽毛球 hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]  hobby = models.IntegerField(     choices=hobby_choices )  4. primary_key  主键,如果没有指定主键,那么 Django会自动创建一个 AutoField的自增字段,名为 id,并将其设置为主键。  primary_key 相当于 null=False和unique=True,即唯一且不能为空,你也可以自己将其他字段设置为空,若有必要。  

示例

from django.db import models  class UserInfo(models.Model):     username = models.CharField(         null=True,         db_column='user',         max_length=32,         db_index=True,         verbose_name='用户名',         help_text='帮助信息',         default='',     )      hobby_choices = [(0, '篮球'), (1, '足球'), (2, '羽毛球'),]      hobby = models.IntegerField(         choices=hobby_choices     )      def __str__(self):         return self.username

2.3 元类 Meta

模型中的元类是指除了字段外的其他非必须内容,如修改数据表的名字,设置代理等。

使用方式

class User(models.Model):     ...      class Meta:         verbose_name = '用户'
1. abstract 为 true 时,模型会被认为是一个抽象类,长用来作为其他模型的父类被继承。  2. app_label 如果没有在 settings 中注册 app,那么必须在元类中声名是属于哪个 app app_label = 'polls'  3. base_manager_name 自定义模型的 _base_manager 管理器的名字,模型管理器是 Django 为模型提供的 API  4. db_table 指定模型生成数据表时,数据表的表名 db_table = 'user'  5. db_tablespace 自定义数据库表空间的名字。默认值是工程的DEFAULT_TABLESPACE设置  6. default_manager_name 自定义模型的_default_manager管理器的名字  7. default_related_name 反向查询时,默认我们使用的 `<model_name>_set` 也就是源模型名字+下划线+set 方法查询,当我们定义了 default_related_name时,就可以使用它来反向查询。  8. ordering 指定该模型生成的所有对象的排序方式,接收一个字段名组成的元组或列表。默认按升序排列,如果在字段名前加上字符“-”则表示按降序排列,如果使用字符问号“?”表示随机排列。  ordering = ['pub_date']             # 表示按'pub_date'字段进行升序排列 ordering = ['-pub_date']            # 表示按'pub_date'字段进行降序排列 ordering = ['-pub_date', 'author']  # 表示先按'pub_date'字段进行降序排列,再按`author`字段进行升序排列。  9. permissions 该元数据用于当创建对象时增加额外的权限。它接收一个所有元素都是二元元组的列表或元组,每个元素都是(权限代码, 直观的权限名称)的格式。比如下面的例子:  permissions = (("can_deliver_pizzas", "可以送披萨"),)  10. default_permissions Django默认给所有的模型设置('add', 'change', 'delete')的权限,也就是增删改。你可以自定义这个选项,比如设置为一个空列表,表示你不需要默认的权限,但是这一操作必须在执行migrate命令之前  11. proxy  若为 true,表示使用代理模式的模型继承方式。  12. required_db_vendor  声明模型支持的数据库。Django默认支持sqlite, postgresql, mysql, oracle  13. indexes  接收一个应用在当前模型上的索引列表  from django.db import models  class Customer(models.Model):     first_name = models.CharField(max_length=100)     last_name = models.CharField(max_length=100)      class Meta:         indexes = [             models.Index(fields=['last_name', 'first_name']),             models.Index(fields=['first_name'], name='first_name_idx'),         ]  14. unique_together  等同于数据库的联合约束,无法应用多对多字段。 比如一张用户表,保存用户姓名、出生日期、地址等,现在有两个叫张伟的人,那么就可以使用联合唯一。  # 表示 name、birth_day、address 联合唯一,即不能在同一地方、同一时刻出生且都叫张伟 unique_together = (('name', 'birth_day', 'address'),)  14. verbose_name 给 Admin 提供人性化的名称,支持中文,如: verbose_name = '用户'   # 那么 Admin 中显示的就是 用户  15. label 只读元数据,不可修改,相当于 polls.Question

3. 多表关系及参数

  • 一对一:Foreignkey 基础上加一个唯一索引 unique
  • 一对多:ForeignKey
  • 多对多:ManyToMany(两个一对多,两个 ForeignKey 相连)

3.1 一对多模型

一对多模型,如:员工部门表,一个部门可以有多个员工。那么 Django 怎么建立这种关系呢?

其实就是通过外键 ForeignKey 进行关联,在多的一方,字段指定外键即可

ForeignKey 字段参数

ForeignKey(ForeignObject) # ForeignObject(RelatedField)         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(可执行对象)                          def func():                             return 10                          class MyModel(models.Model):                             user = models.ForeignKey(                                 to="User",      # 关联 User 表                                 to_field="id"   # 关联 User 表 的  id 字段                                 on_delete=models.SET(func),)          related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()         related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')                  limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:                 # 如:                 - limit_choices_to={'nid__gt': 5}                 - limit_choices_to=lambda : {'nid__gt': 5}                  from django.db.models import Q                 - limit_choices_to=Q(nid__gt=10)                 - limit_choices_to=Q(nid=8) | Q(nid__gt=10)                 - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')                          db_constraint=True          # 是否在数据库中创建外键约束         parent_link=False           # 在Admin中是否显示关联数据

示例:

class Department(models.Model):     """部门表"""     name = models.CharField(max_length=32)     create_data = models.DateField(auto_now_add=True)   # 创建时间     id_delete = models.BooleanField(default=False)      # 是否可删除          class Meta:         db_table = 'department'           class Employee(models.Model):     """员工表"""     name = models.CharField(max_length=32)     age = models.IntegerField()     gender = models.IntegerField(default=0)     salary = models.DecimalField(max_digits=8, decimal_places=2)    # max_digits 表示八个数字,包括两位小数,decimal_places 表示保留两位小数          # null=True 表示可以为空, blank=True 表示 Django admin 后台可以输入空     comment = models.CharField(max_length=300, null=True, blank=True)        hire_data = models.DateField(auto_now_add=True)     department = models.ForeignKey('Department')        # 外键字段          class Meta:         db_table = 'employee'       # 数据表名字

Tips

在设置外键时,需要给子表(有外键的表)指定当主表(被连接的表)删除数据时,从表该如何处理。Django 通过设置 on_delete 属性来控制,它有三种值:

  • 删除时报错:DO_NOTHING/PROTECT
  • 级联删除:主表删除数据,从表与之关联的数据也将被删除:CASCADE
  • 设置默认值:SET_NULL/SET_DEFAULTset_null 仅在字段可以是 null 时才能使用

3.2 一对一模型

OneToOneField(ForeignKey)     to,                         # 要进行关联的表名     to_field=None               # 要关联的表中的字段名称     on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为 
###### 对于一对一 ###### # 1. 一对一其实就是 一对多 + 唯一索引 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 # 如下会在A表中额外增加一个c_ptr_id列且唯一: class C(models.Model):     nid = models.AutoField(primary_key=True)     part = models.CharField(max_length=12)  class A(C):     id = models.AutoField(primary_key=True)     code = models.CharField(max_length=1)      # 使用 OneToOneField 字段创建一对一模型 class Person(models.Model):     name = models.CharField(max_length=32)     o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)      class Person_detal(models.Model):     age = models.IntegerField()     gender_choices = [(0, '男'), (1, '女')]     gender = models.IntegerField(         choices=gender_choices,         default=0,     )     height = models.PositiveIntegerField()      # 正整数     email = models.EmailField(max_length=64) 

3.3 多对多模型

多对多其实就是两个一对多,通过两个外键将三张表相连,其中第三张表存储了前面两张表的对应关系。例如:名字和爱好表,一个人可以有多个爱好,一个爱好也可以是多个人有。

如何创建三张表:

  • 自动创建:ManyToMangField 字段自动创建,不能新增列
  • 手动创建:元 Meta,可以新增列
  • 手动加自动:元 Meta + ManyToMangField

多对多 ManyToManyField 字段参数:

ManyToManyField(RelatedField)         to,                         # 要进行关联的表名         related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()         related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')         limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:                                     # 如:                                             - limit_choices_to={'nid__gt': 5}                                             - limit_choices_to=lambda : {'nid__gt': 5}                                              from django.db.models import Q                                             - limit_choices_to=Q(nid__gt=10)                                             - limit_choices_to=Q(nid=8) | Q(nid__gt=10)                                             - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')                                              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,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表                                         from django.db import models                                          class Person(models.Model):                                             name = models.CharField(max_length=50)                                          class Group(models.Model):                                             name = models.CharField(max_length=128)                                             members = models.ManyToManyField(                                                 Person,                                                 through='Membership',                                                 through_fields=('group', 'person'),                                             )                                          class Membership(models.Model):                                             group = models.ForeignKey(Group, on_delete=models.CASCADE)                                             person = models.ForeignKey(Person, on_delete=models.CASCADE)                                             inviter = models.ForeignKey(                                                 Person,                                                 on_delete=models.CASCADE,                                                 related_name="membership_invites",                                             )                                             invite_reason = models.CharField(max_length=64)         db_constraint=True,         # 是否在数据库中创建外键约束         db_table=None,              # 默认创建第三张表时,数据库中表的名称 

自动创建

class UserInfo(models.Model):     username = models.CharField(max_length=32, verbose_name='用户名')     m = models.ManyToManyField(         to = 'Tag',         related_name = 'bb',     )      class Tag(models.Model):     title = models.CharField(max_length=32) 

手动创建

class UserInfo(models.Model):     username = models.CharField(max_length=32, verbose_name='用户名')      class Tag(models.Model):     title = models.CharField(max_length=32)      # 手动创建第三张表 class UserToTag(models.Model):     tid = models.AutoField(primary_key=True)     u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 通过外键相连     t = models.ForeignKey('Tag', on_delete=models.CASCADE)      #  联合唯一索引     class Meta:         unique_together = [             ('u', 't'),         ] 

手动加自动

class UserInfo(models.Model):     username = models.CharField(max_length=32, verbose_name='用户名')     m = models.ManyToManyField(         through = 'UserToTag',      # 指定第三张表,不自动创建         through_fields = ['u', 't']     # 指定第三张表的字段     )      class Tag(models.Model):     title = models.CharField(max_length=32)      # 手动创建第三张表 class UserToTag(models.Model):     tid = models.AutoField(primary_key=True)     u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 通过外键相连     t = models.ForeignKey('Tag', on_delete=models.CASCADE)      #  联合唯一索引     class Meta:         unique_together = [             ('u', 't'),         ] 

3.4 自关联模型

自关联模型,即表中的某列关联表中另一列。最典型的自关联模型就是地区表(省市县三级联动)省的 pid 为 null,市的 pid 为省的 id,县的 pid 为市的 id。具体如下:

自关联表现形式

  • 省的 parent_id 为 null
  • 市的 parent_id 为对应省的 id
  • 区的 parent_id 为对应市的 id
  1. 数据库设计 models.py
class Area(models.Model):     name = models.CharField(max_length=32, verbose_name='名称')     parent = models.ForeignKey('self',          # 自关联字段的外键指向自身,也可以是 Area                                verbose_name='上级行政区划',                                on_delete=models.SET_NULL,                                related_name='subs',                                null=True,                                blank=True                                )      class Meta:         db_table = 'tb_areas'       # 自定义表名         verbose_name = '行政区划'   # admin 中显示的名称         verbose_name_plural = '行政区划'      def __str__(self):         return self.name 

自关联模型,最核心的地方就是自己关联自己 self/Area,用一张表做两张表才能做的事。

  1. 路由系统 app/urls.py
from django.urls import path from app import views  urlpatterns = [     path('area/', views.area, name='area'),     path('getPro/', views.getPro, name='getArea'),     path('getCity/', views.getCity, name='getCity'),     path('getDis/', views.getDis, name='getDis'), ] 
  1. 视图函数 views.py
from django.shortcuts import render, HttpResponse from app import models from django.http import JsonResponse  # 访问 http://127.0.0.1:8000/app/area/,返回 area.html def area(request):     return render(request, 'area.html')   def getPro(request):     """获取省份信息"""     pro_list = models.Area.objects.filter(parent_id=None)       # 省份的 parent_id 为 None     res = []     for i in pro_list:         res.append([i.id, i.name])     print(res)      # [[1, '广东省'], [7, '湖南省']]     return JsonResponse({'pro_list': res})      # JsonResponse 打包成 json 格式字符串   def getCity(request):     """获取市信息"""     city_id = request.GET.get('city_id')     city_list = models.Area.objects.filter(parent_id=int(city_id))     res = []     for i in city_list:         res.append([i.id, i.name])      print('city', res)      #  [[2, '深圳市'], [3, '广州市'], [6, '湛江市']]     return JsonResponse({'city_list': res})   def getDis(request):     """获取区信息"""     dis_id = request.GET.get('dis_id')     dis_list = models.Area.objects.filter(parent_id=int(dis_id))     res = []     for i in dis_list:         res.append([i.id, i.name])     return JsonResponse({'dis_list': res}) 
  1. 模板 area.html
{% load static %} <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Title</title> </head> <body>     <select id="pro">         <option value="">请选择省份</option>     </select>      <select id="city">         <option value="">请选择城市</option>     </select>      <select id="dis">         <option value="">请选择区</option>     </select>  <script src="{% static 'jquery-3.1.1.js' %}"></script> <script>     $(function () {         var pro = $('#pro');         var city = $('#city');         var dis = $('#dis');          // 查询省信息,$.get() 为 $.ajax 的简化版         // 向 /app/getPro/ 发送 ajax 请求,arg 为请求成功返回的信息         $.get('/app/getPro/', function (arg) {                       console.log(arg);   // {'pro_list': [[1, '广东省'],[7, '湖南省']]}              $.each(arg.pro_list, function (index, item) {                 console.log(item);      // [1, '广东省'] 、[7, '湖南省']                 console.log(index);     // 0、1                                  // 将从数据库中取出的数据添加到 option 标签中,其中 value 为 id,文本值为 name                 var $new = $("<option value="+ item[0] +">" + item[1] + "</option>");                 pro.append($new);             });              {# 或者使用 JavaScript for 循环#} {#            console.log(arg.pro_list);#} {#            for (var i = 0, len=arg.pro_list.length; i<len; i++){#} {#                var $new = $('<option value='+ arg.pro_list[i][0] +'>' + arg.pro_list[i][1] + '</option>')#} {#                pro.append($new);#} {#            }#}          });          // 根据省的变化查询市,改变省时,清空市和区         pro.change(function () {             city.empty().append('<option value="">请选择市</option>');             dis.empty().append('<option value="">请选择市</option>');             $.ajax({                 url: '/app/getCity/',                 type: 'get',                 data: {'city_id': $(this).val()},                 success: function (arg) {                     $.each(arg.city_list, function (index, item) {                         var $new = $('<option value='+ item[0]+'>' + item[1] + '</value>');                         city.append($new);                     })                 }             })         });          // 根据市变化查询区,改变市,清空区         city.change(function () {             dis.empty().append('<option value="">请选择市</option>');             $.ajax({                 url: '/app/getDis/',                 type: 'get',                 data: {'dis_id': $(this).val()},                 success: function (arg) {                     console.log('区', arg.dis_list);     // [[4, '宝安区'], ]                         $.each(arg.dis_list, function (index, item) {                             console.log(item[1]);                             var $new = $('<option value='+ item[0] +'>' + item[1] + '</value>');                             dis.append($new);                     })                 }             })         })      }) </script> </body> </html> 

当访问 http://127.0.0.1:8000/app/area/ 时,会向 /app/getPro/ 发送 ajax 请求,请求成功获得包含省份 id、name 的信息 {'pro_list': [[1, '广东省'],[7, '湖南省']]}。取出数据遍历循环,添加到 option 标签中,从而获得省份信息。

当我们改变省份时,将上一次省份的市、区信息清空,避免添加重复。获取当前省份的 id,并向 /app/getDis/ 发送 ajax 请求,从而获得相应市的信息,同理区也是一样。

  1. 添加数据 views.py

添加数据,可以用 pycharm 自带的数据库添加,也可以手动添加:

def add(request):     models.Area.objects.create(id=7, name='湖南省', parent_id=None)     models.Area.objects.create(id=8, name='长沙市', parent_id=7)     models.Area.objects.create(id=9, name='雨花区', parent_id=8)      return HttpResponse('添加成功') 

参考博客:

3.5 知识点补充

Ajax 的 get() 方法,它可以向后台发送 ajax 请求,请求成功可调用回调函数。如果需要在出错时执行函数,请使用 $.ajax,它是 $.ajax 简化版本。

语法

$(selector).get(url, data, success(response, status, xhr), dataType);   // 除了 url 必需,其他都可选,status 为请求状态,xhr - 包含 XMLHttpRequest 对象 
$('button').click(function(){     get('/app/getdata/', function(arg){}); }); 

3.6 Model 常用设计模型示例及方法

3.6.1 Model 设计

总共四张表,分别是班级、学生、学生个人信息以及老师表,之间的关系如下:

class Class(models.Model):     """班级表"""     class_name = models.CharField(max_length=32)     create_data = models.DateField()      def __str__(self):         return self.class_name      class Meta:         verbose_name = '班级'   class Student(models.Model):     """     学生表     一每个学生都有对应的个人信息(一对一),一个班级可以有多个学生(一对多)     """     student_name = models.CharField(max_length=32)      # 一对多,与班级关联     sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)      # 一对一,与学生个人信息关联     detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)      class Meta:         verbose_name = '学生'      def __str__(self):         return self.student_name   class StudentDetail(models.Model):     """学生个人信息表"""     age = models.IntegerField()     gender_choices = [(0, '男'), (1, '女')]     gender = models.IntegerField(         choices=gender_choices,         default=0,     )     height = models.PositiveIntegerField()      # 正整数     email = models.EmailField(max_length=64)   class Teacher(models.Model):     """     老师表     一个老师可以教多个班,一个班也可以有多个老师     """     teacher_name = models.CharField(max_length=32)     tc = models.ManyToManyField(to='Class', related_name='b')      class Meta:         verbose_name = '老师'      def __str__(self):         return self.teacher_name 
  1. 老师表 app_teacher

  1. 学生个人信息表 app_student_detail

  1. 学生表 app_student

  1. 班级表 app_class

  1. 老师、班级关系表(第三张表)app_teacher_tc

3.6.2 查询操作

def query(request):     ########### 一对一 #####################     # 正向查询(根据外键字段)     # 根据学生名字查询其个人信息     # obj = models.Student.objects.filter(student_name='rose')[0]     # print(obj)     # print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)     # # 17 1 170 456@qq.com       # 反向查询(根据要查询的数据表名)     # 根据邮箱查询学生名字     # obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]     # print(obj2)         # <QuerySet [<StudentDetail: StudentDetail object (2)>]>     #     # print(obj2.student.student_name)        # rose       ############## 一对多(班级表和学生表) ##################     # 正向查询      # 根据学生名查询所属班级     # obj3 = models.Student.objects.get(student_name='rose')     # print(obj3.sc_id)   # 1     # print(type(obj3.sc))      # <class 'app.models.Class'>     # print(obj3.sc.class_name)   # 一班       # 反向查询     # 二班有哪些学生     # obj4 = models.Class.objects.filter(class_name='二班')[0]     # print(obj4)     # # res = obj4.student_set.all()     # 如果外键字段没有设置 related_name 就用 表名_set     # res = obj4.stu.all()     # print(res)       # <QuerySet [<Student: john>, <Student: lila>]>     # for i in res:     #     print(i.student_name)   # john、lila      # 方法二     # ret = models.Student.objects.filter(sc=obj4).values('student_name')   # 字典形式     # print(ret)      # <QuerySet [{'student_name': 'john'}, {'student_name': 'lila'}]>     # for i in ret:     #     print(i['student_name'])    # john、lila      # # 双下划线     # 正向     obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')      # 反向     obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')    ############################### 多对多(老师表与班级表) ############################################     # 查看 孙老师教哪几个班     # 正向查询(通过多对多字段)      # obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0]     # ret = obj5.tc.all()     # print(ret)      # <QuerySet [<Class: 一班>, <Class: 二班>]>     # for i in ret:     #     print(i.class_name)      # 查看一班有哪几个老师     # 反向查询     # obj5 = models.Class.objects.filter(class_name='一班')[0]     # ret = obj5.b.all()     # print(ret)          # < QuerySet[ < Teacher: 孙老师 >, < Teacher: 刘老师 >] >     # for i in ret:     #     print(i.teacher_name)       # 孙老师、刘老师      # # 双下划线     # # 如果没有设置 related_name = b,那么就是 values('teacher__name`) 即要查的表的表名__要查询的字段     # obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')     # print(obj6)     # <QuerySet [{'b__teacher_name': '孙老师'}, {'b__teacher_name': '刘老师'}]>      # # 正向  tc = models.ManyToManyField(to='Class', related_name='b')     # obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')      return HttpResponse('查询成功!') 

3.6.3 总结

一对一、一对多、多对多,最好都设置好 related_name 参数,没有设置时,反向查找用 **要查询的命名.查询字段 或 _set.all()**。

查询分为:

  • 基于对象查询(相当于 SQL 中子查询)
    • 先获取相关对象:models.Models.objects.filter(过滤字段)
    • 再通过对象(正向、反向)查询相关字段
  • 基于 QuerySet 和双下划线查询(相当于 SQL 中连表查询)
    • 前面是过滤条件,后面跟要显示(查询)的字段
    • models.Model.objects.filter(过滤字段).values(要显示的字段)

一对一

  • 正向查询:根据 OneToOneField 字段查询
  • 反向查询:根据 要查询的命名.查询字段查询
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE) # 正向 obj = models.Student.objects.filter(student_name='rose')[0] print(obj.detail.age)  # 反向 obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0] print(obj2.student.student_name)    # 这里没设置 related_name 因此用表名 

一对多

  • 正向查询:根据外键字段查询
  • 反向查询
    • 设置了related_name,就用这个名字查询
    • 没有设置,用表名_set 方式查询
  • 双下划线查询
    • 正向:filter(条件).values(要查询的表名__要查询字段)
    • 反向:filter(外键字段__条件).values(要查询字段)
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE) # 正向 obj3 = models.Student.objects.get(student_name='rose') print(obj3.sc.class_name)   # 根据外键字段 sc 查询  # 反向 obj4 = models.Class.objects.filter(class_name='二班')[0] # res = obj4.student_set.all()  # 没设置 related_name,用表名_set res = obj4.stu.all()        # 用 related_name 名字  # 双下划线 # 正向 obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')  # 反向 obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name') 

多对多

  • 正向查询:ManyToManyField 字段查询
  • 反向查询:表名_set、related_name名字查询
  • 双下划线与一对多一样
# tc = models.ManyToManyField(to='Class', related_name='b') # 正向 obj5 = models.Teacher.objects.filter(teacher_name='孙老师')[0] print(obj5.tc.all())  # 反向 obj5 = models.Class.objects.filter(class_name='一班')[0] print(obj5.b.all()) 

参考博客:django 一对一、一对多、多对多操作、常用方法

4. 模型继承

Django中所有的模型都必须继承 django.db.models.Model 模型,同样地我们也可以自己创建一个父模型用来保存公共部分,子模型继承父模型。这样的好处就是有时会省去很多重复代码。

同样地 Django 也支持继承多个父类。

Django 中三种继承方式:

  • 抽象基类:被用来继承的模型被称为 Abstract base classes,将子类共同的数据抽离,供子类复用,它不会创建实际的数据表
  • 多表继承: Multi-table inheritance,每一个模型都有自己的数据库表
  • 代理模型:如果你只想修改模型的Python层面的行为,并不想改动模型的字段,可以使用代理模型

4.1 抽象基类

只需在模型中元类添加 abstract=True,即可将模型转换为抽象基类。但是它不会创建实际数据库表,只能用来被继承。

from django.db import models  class CommonInfo(models.Model):     username = models.CharField(max_length=32)     password = models.CharField(max_length=64)      class Meta:         abstract = True   class User(CommonInfo):     email = models.EmailField(max_length=60) 

模型 User 将会有 username、password、email 三个字段。

抽象基类元类

如果子类没有元类,那么它将继承基类的元类,同样地子类也可以对基类的元类进行拓展:

class CommonInfo(models.Model):     username = models.CharField(max_length=32)     password = models.CharField(max_length=64)      class Meta:         abstract = True         ordering = ['username']   class User(CommonInfo):     email = models.EmailField(max_length=60)      # 继承基类的元类,并进行拓展     class Meta(CommonInfo.Meta):         db_table = 'username' 
  • 基类有元类,子类也没有的话,直接继承
  • 基类有元类,子类也有,直接覆盖
  • 子类可以添加额外元数据
  • 基类的 abstract=true 不会被继承
  • 基类的 db_table 元数据无效,因为抽象基类不会创建数据表

related_name 和 related_query_name

若抽象基类中存在 ForeignkManyToManyField 字段,且设置了 related_namerelated_query_name 参数,其子类也将继承这两个参数。但是在查询时就会出现错误。

例如,对于 app01/models.py 中:

class Base(models.Model):     m2m = models.ManyToManyField(User, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss")     class Meta:         abstract = True          class ChildA(Base):     pass  class ChildB(Base):     pass 
  • app01.ChildA.m2m 字段:反向关系名(reverse name)为 app01_childa_related;反向查询名(reverse query name)为 app01_childas
  • app01.ChildB.m2m 字段:分别为: app01_childb_relatedapp01_childbs
  • 如果没有设置 related_name 或 related_query_name 就没有上述情况

4.2 多表继承

父类和子类都是可正常使用的模型,且都有自己的数据表,其本质为 一对一 关系。

class UserInfo(models.Model):     name = models.CharField(max_length=32)     age = models.IntegerField()           class PersonalDetail(UserInfo):     email = models.EmailField()     description = models.CharField(max_length=4000) 

PersonalDetail 继承 UserInfo,它会继承父类所有的字段,两个模型内部是一对一关系,如下所示:

>>> from polls.models import UserInfo, PersonalDetail >>> q = PersonalDetail.objects.all()  >>> for i in q: ...     i.name ... 'rose' 'lina' 

多表继承之元类

多表继承中子类不会继承父类的元类,但是有两个元类数据除外: ordering、get_latest_by,因此若希望不继承父类的元类的所有元数据,就需要指定或重写这两个元数据:

class PersonalDetail(UserInfo):     email = models.EmailField()     description = models.CharField(max_length=4000)      class Meta:     # 重写 ordering,移除父类元类的影响         ordering = [] 

4.3 多重继承

与 Python 一样,模型也可以继承多个父类模型,当有多个父类都含有 Meta 类时,那么只有第一个父类的会被继承,其余将被忽略掉。

Tips

  • 尽量避免不用多重继承
  • 当父类有相同 id 主键字段时,将会报错,因此需要给父类显示地添加 AutoField 字段。
class Base1(models.Model):     base1_id = models.AutoField(primary_key=True)     pass  class Base2(models.Model):     base2_id = models.AutoField(primary_key=True)     pass  class Child(Base1, Base2):     pass 

4.4 代理模型

代理模型就是对源模型的一种代理,可以创建、删除、更新代理模型的实例,与源模型用的是同一张数据表,但是 它对源模型数据没有影响

要想将一个模型设置为代理模型,只需在 Meta 类中设置 proxy=True

class A(models.Model):     name = models.CharField(max_length=32)     age = models.IntegerField()   class B(A):     # 代理模型     class Meta:         proxy = True         ordering = ['age']      def do_something(self):         pass 

只会创建一张数据表,同时代理模型也可以操作数据表:

from .models import A, B   >>> A.objects.create(name='rose', age=18)  >>> q = B.objects.get(name='rose') >>> q.age 18  # 对代理模型排序,按照年龄从小到大排列,被代理的模型,查询不会排序  obj_list = B.objects.all() for obj in obj_list:     print(obj.age)      # 17、18 、20     print(obj.name)     # lila、rose、tom 

Tips

  • 代理模型必须继承自一个非抽象的基类,并且不能同时继承多个非抽象基类;
  • 代理模型可以同时继承任意多个抽象基类,前提是这些抽象基类没有定义任何模型字段。
  • 代理模型可以同时继承多个别的代理模型,前提是这些代理模型继承同一个非抽象基类。

4.5 总结

  • 抽象基类不会创建数据表,直接设置 abstract=True 即可设置为抽象基类
  • 多表继承实质是一对一关系,不会继承基类 Meta,除 ordering 和 get_latest_by
  • 多表继承最好不要用
  • 代理模型与被代理模型通用一张数据表,可以对代理数据进行创建、删除、修改等操作,但对源模型无影响
  • 若基类是非抽象基类,基类与子类有相同名字的字段,那么将会覆盖子类(抽象基类除外)
# A 中的 name 将会覆盖 B 中 的 name,除非 A 是抽象基类 class A(models.Model):     name = models.CharField(max_length=32)      # class Meta:     #     abstract = True  class B(A):     name = models.CharField(max_length=32) 

组织模型

当模型有很多时,我们最好将模型分割开来,分类存储,这样有利于组织。我们可以利用包来组织模型。

在应用中创建一个名为 models 的包(pycharm 可以直接创建包,不需要手动创建 init.py文件),然后将模型分类到各个 .py 文件中,最后将其导入 __init__.py 文件中:

# app/models/__init__.py from polls.models.modelone import User 

5. 聚合分组

5.1 聚合 aggregate

aggregate() 返回一个字典,键为聚合值标识符,值为计算处理的聚合值。

使用时需要先导入内置函数:

from django.db.models import Avg, Sum, Max, Min, Count 

示例:

from django.db.models import Avg, Sum, Max, Min, Count >>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'))   # 指定键为 avg_age,也可以用默认的 {'avg_age': 18} 

多个聚合:

from django.db.models import Avg, Sum, Max, Min, Count >>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'), max_length=Max('height'))  {'avg_age': 18, 'max_length': 182} 

5.2 分组 annotate

ORM 中 values()values_list() 选字段,就相当于原生 SQL 中 select 什么字段:

ret = models.Employee.objects.all()    # 相当于 select * from employee """ SELECT `employee`.`id`, `employee`.`name`, `employee`.`age`, `employee`.`salary`, `employee`.`province`, `employee`.`dept` FROM `employee` LIMIT 21; args=() """  ret = models.Employee.objects.all().values("dept", "age") """ SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=() """ 

select选中什么字段,group by 就只能是什么字段:

mysql> select name, Avg(salary) from app_employee group by name; +------+-------------+ | name | Avg(salary) | +------+-------------+ | rose |      2000.1 | | lila |     3000.45 | | john |     4000.56 | | tom  |     5005.78 | +------+-------------+ 

  1. 连表分组查询

    按照部门分组,查询每个部门的平均薪水:

# ORM 操作 # 前一个 values() 表示按照什么分组,后一个表示要显示的字段 >>> employee_list = models.Employee.objects.values('dept__name').annotate(avg=Avg('salary')).values('dept__name', 'avg') >>> print(employee_list)          <QuerySet [{'dept__name': '财务部', 'avg': 2000.1}, {'dept__name': '事业部', 'avg': 4003.115}, {'dept__name': '人事部', 'avg': 4000.56}]> 
# 对应 SQL 语句 SELECT `app_department`.`name`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` INNER JOIN `app_department` ON (`app_employee`.`dept_id` = `app_department`.`id`) GROUP BY `app_employee`.`name`, `app_department`.`name` ORDER BY NULL  LIMIT 21; args=() 
  1. ORM 分组查询

按照国家分组,查询每个国家的平均薪水:

>>> employee_list = models.Employee.objects.values('addr').annotate(avg=Avg('salary')).values('addr', 'avg') >>> print(employee_list)       <QuerySet [{'addr': '美国', 'avg': 3502.9399999999996}, {'addr': '英国', 'avg': 3000.45}, {'addr': '德国', 'avg': 4000.56}]> 
SELECT `app_employee`.`addr`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` GROUP BY `app_employee`.`addr` ORDER BY NULL  LIMIT 21; args=() 

Tips

  • ORM 中的连表查询(跨表)相当于 SQL 中的连表(inner john)查询
  • ORM 查询,第一个 values(fields) 为分组字段,第二个 values(fileds1, fields2) 为要显示的字段
  • select选中什么字段,group by 就只能是什么字段

5. F 查询和 Q 查询

5.1 F 查询

上面我们都是在比较字段与某个常亮,要想两个字段进行比较,那就要用到 F 查询

  1. 查询书的价格大于评论数的书籍
from django.db.models import F models.Book.objects.filter(price__gt=F('comment_count'))         
  1. 将书的价格整体提升 5 元
models.Book.objects.update(price=F('price')+5) 
  1. F 对象和 F 对象以及常量之间可以进行加减乘除、取模等
models.Book.objects.update(price=F('price')*5) 

5.2 Q 查询

Q 构建搜索条件(在这里解决了或、与的问题)

  1. Q 对象可以对关键字参数进行封装,从而更好地应用多个查询
obj1 = models.StudentDetail.objects.filter(Q(age=18))       # 构建过滤条件 
  1. 组合使用 &、| 、~ 操作符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19))   # 解决了 Django API 中没有或的问题 obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19))   # 解决了 Django API 中没有与的问题 obj3 = models.StudentDetail.objects.filter(~ Q(age=19)) # 解决了 Django API 中没有非的问题 

手动创建逻辑关系

q1 = Q()        # 创建 Q 对象 q1.connector = 'OR'     # q1 内部为 or 关系,即 age=18 或 age =19 q1.children.append(('age', 18)) q1.children.append(('age', 19))  q2 = Q()        # 创建 Q 对象 q2.connector = 'AND'    # q2 内部为 and 关系 q2.children.append(('gender', 0))  q3 = Q()            # 创建 Q 对象 q3.add(q1, 'AND')       # 将 q1、q2 都添加到 q3 中,创建总的过滤条件 q3.add(q2, 'AND') obj = models.StudentDetail.objects.filter(q3) print(obj)  # <QuerySet [<StudentDetail: StudentDetail object (1)>]>  # 原生 SQL SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0)  LIMIT 21;         
  1. Q 对象 可以和关键字参数一起查询使用,不管一定要在关键字参数前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
  1. 应用
import datetime obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15))) print(obj)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!