MTV 设计模式中,模型(M)就是对数据库的操做,在 Web 开发中,使用最频繁的也是对数据库的操做,那么该怎么样去实现呢?javascript
咱们不可能本身手动去写大量的 SQL 语句,由于咱们也不是专业 DBA 人员,那么咱们就只能奢求有一种可以使用 Python 程序对数据库操做的方法了。这就是 ORM(Object Relation Mapping)对象关系映射,以面向对象的方式去操做数据库。html
其实现模式大体是这样的:java
Django 自己给咱们提供了强大的 ORM 系统,不须要再额外的
安装,固然还有一些其余的 ORM ,如:SQLAlch 等。python
Model 中的字段 fileds
即数据表中的列,用来存储数据(记录),字段类型对应列的数据类型。mysql
Django 内置了不少字段类型,都位于 django.db.models
中,如下为经常使用字段类型:jquery
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) - 二进制类型
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,即惟一且不能为空,你也能够本身将其余字段设置为空,如有必要。
示例git
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
模型中的元类是指除了字段外的其余非必须内容,如修改数据表的名字,设置代理等。ajax
使用方式sql
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
一对多模型,如:员工部门表,一个部门能够有多个员工。那么 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_DEFAULT
,set_null
仅在字段能够是 null 时才能使用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)
多对多其实就是两个一对多,经过两个外键将三张表相连,其中第三张表存储了前面两张表的对应关系。例如:名字和爱好表,一我的能够有多个爱好,一个爱好也能够是多我的有。
如何建立三张表:
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'), ]
自关联模型,即表中的某列关联表中另外一列。最典型的自关联模型就是地区表(省市县三级联动)省的 pid 为 null,市的 pid 为省的 id,县的 pid 为市的 id。具体以下:
自关联表现形式
parent_id
为 nullparent_id
为对应省的 id
parent_id
为对应市的 id
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
,用一张表作两张表才能作的事。
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'), ]
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})
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 请求,从而得到相应市的信息,同理区也是同样。
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('添加成功')
参考博客:
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){}); });
总共四张表,分别是班级、学生、学生我的信息以及老师表,之间的关系以下:
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
app_teacher
:app_student_detail
:app_student
:app_class
:app_teacher_tc
: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('查询成功!')
一对1、一对多、多对多,最好都设置好 related_name
参数,没有设置时,反向查找用 **要查询的命名.查询字段 或 _set.all()**。
查询分为:
models.Models.objects.filter(过滤字段)
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
,就用这个名字查询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
字段查询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 一对1、一对多、多对多操做、经常使用方法
Django中全部的模型都必须继承 django.db.models.Model
模型,一样地咱们也能够本身建立一个父模型用来保存公共部分,子模型继承父模型。这样的好处就是有时会省去不少重复代码。
一样地 Django 也支持继承多个父类。
Django 中三种继承方式:
Abstract base classes
,将子类共同的数据抽离,供子类复用,它不会建立实际的数据表Multi-table inheritance
,每个模型都有本身的数据库表只需在模型中元类添加 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
若抽象基类中存在 Foreignk
和 ManyToManyField
字段,且设置了 related_name
或 related_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_related
;反向查询名(reverse query name)为 app01_childas
app01_childb_related
和 app01_childbs
父类和子类都是可正常使用的模型,且都有本身的数据表,其本质为 一对一 关系。
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 = []
与 Python 同样,模型也能够继承多个父类模型,当有多个父类都含有 Meta 类时,那么只有第一个父类的会被继承,其他将被忽略掉。
Tips
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
代理模型就是对源模型的一种代理,能够建立、删除、更新代理模型的实例,与源模型用的是同一张数据表,可是 它对源模型数据没有影响。
要想将一个模型设置为代理模型,只需在 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) # 1七、18 、20 print(obj.name) # lila、rose、tom
Tips
abstract=True
便可设置为抽象基类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
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}
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 | +------+-------------+
连表分组查询
按照部门分组,查询每一个部门的平均薪水:
# 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=()
按照国家分组,查询每一个国家的平均薪水:
>>> 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
inner john
)查询values(fields)
为分组字段,第二个 values(fileds1, fields2
) 为要显示的字段select
选中什么字段,group by
就只能是什么字段上面咱们都是在比较字段与某个常亮,要想两个字段进行比较,那就要用到 F 查询
from django.db.models import F models.Book.objects.filter(price__gt=F('comment_count'))
models.Book.objects.update(price=F('price')+5)
models.Book.objects.update(price=F('price')*5)
Q 构建搜索条件(在这里解决了或、与的问题)
obj1 = models.StudentDetail.objects.filter(Q(age=18)) # 构建过滤条件
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') # 将 q一、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;
models.StudentDetail.filter(Q(age=18), email__startswith='123')
import datetime obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15))) print(obj)