Django之模型

微笑、不失礼 提交于 2020-02-02 02:03:48

Django模型

  • 模型,就是python中的类对应数据库中的表

  • ORM:对象关系映射


示例

  1. 模型类必须继承models.Model
  2. 每个属性对应数据库中的一个字段
  3. 表名自动使用 mysite_类名 的小写(如:polls_question),可以覆盖
  4. 如果模型类中没有指定 primary_key ,那么会自动创建一个 id 字段,自增主键

模型:


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField()

对应mysql数据库中的表:


CREATE TABLE `polls_question` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `question_text` varchar(200) NOT NULL,
  `pub_date` datetime(6) NOT NULL,
  PRIMARY KEY (`id`)
)


应用模型

当编写好了模型之后,需要将模型应用到数据库中:

1、配置应用

  1. 将模型对应的应用程序添加到项目的settings中:

INSTALLED_APPS = [
    'polls'
]

  1. settings中配置正确的数据库连接:

"""
sqlite3数据库连接方式:
"""
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


"""
mysql数据库连接方式:
"""
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'model_study',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}
  1. 安装对应数据库的驱动

    • 如果是 mysqldjango2.2 需要安装 mysqlclient

    • 如果是sqlite3,是不需要额外安装,python自带驱动库

    • 一些数据库需要首先创建数据库,譬如:mysqloraclemssql

2、预备迁移

​ 在项目根目录的cmd中运行:


python manage.py makemigrations polls

  1. polls 是子应用的名称,如果不指定,那么就是对所有 INSTALLED_APPS 中的应用都进行预备迁移
  2. 指定该命令后,在对应的子应用下的migrations 中会生成一个对应的迁移文件

3、正式迁移

​ 在根目录的cmd中运行:


python manage.py migrate

  • 没有添加子应用名,那么就会把django项目中所有的应用都迁移到数据库中

4、模型修改后重新应用

  • 不管是新增模型,还是修改已有模型后,只需要重复执行第2步和第3步,即可自动实现数据库中的表结构、表关系等信息的修改

5、逆向从数据库表生成模型类

  1. settings中设置好 DATABASES 配置

  2. 在对一个数据库中建立好表、约束表关系

  3. 在根目录的cmd中运行:

    
    python manage.py inspectdb > polls/models.py
    
    
  • polls是子应用名
  1. 第3步执行后会在models中生成对应的模型类

    
    class DjangoSession(models.Model):
        session_key = models.CharField(primary_key=True, max_length=40)
        session_data = models.TextField()
        expire_date = models.DateTimeField()
    
        class Meta:
        	# 这个属性是通知django,不需要进行从模型到数据库的迁移管理
            managed = False  
    
     		# 对应的数据库中的表名
            db_table = 'django_session' 
            
    

字段Field

模型类的属性对应数据库中表的字段,都是对应的Field类的实例

1、字段命名限制

  • 字母,数字,下划线,首字母不能是数字

  • 字段名称不能是Python保留字

  • 由于Django查询查找语法的工作方式,字段名称不能在一行中包含多个下划线

2、AutoField、ID、PRIMARY_KEY

  • 默认会自动创建一个自增,主键的id列

  • 如果指定了primary_key 为其它列,那么不会自动创建id

  • 可以在模型中指定:


id = models.AutoField(primary_key=True)

3、常见Field

AutoField

​BooleanField

CharField

DateField

DateTimeField

FloatField

SmallIntegerField

IntegerField

TextField

UUIDField 使用:


import uuid
from django.db import models

class MyUUIDModel(models.Model):
    # uuid.uuid4 千万别写成 uuid.uuid4() ,不要写 ()
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

4、Field常见参数

  • max_length:字段最大长度,用于字符串等,字符串类型CharField必须设置该值
  • null:如果TrueDjango将在数据库中存储NULL空值。默认是False
  • blank:如果``True,该字段被允许为空白" "。默认是False。请注意,这不同于nullnull纯粹是与数据库相关的,而blank与验证相关。如果一个字段有blank=True,表单验证将允许输入一个空值。如果一个字段有blank=False,该字段将是必需的。
  • choices:示例:YEAR_IN_SCHOOL_CHOICES = (('FR', 'Freshman'),('SO', 'Sophomore'),('JR', 'Junior'),('SR', 'Senior'),('GR', 'Graduate')),中文示例:SEX_CHOICES=((1, '男'),(2, '女')),元组中的第一个元素是将存储在数据库中的值,第二个元素是将在页面中显示的值,最常见用于下拉选择框select
  • default:字段的默认值
  • help_text:用于显示额外的“帮助”文本
  • primary_key:如果True,这个字段是模型的主键,默认是False
  • unique:如果True,该字段在整个表格中必须是唯一的
  • verbose_name:详细字段名,不指定则是属性名的小写,并且用 空格 替换 ‘_’

5、模型之间的关系

  • 主外关系中,关联操作最常用的是: models.CASCADE 级联删除 和 models.SET_NULL 设置为null

  • 一对多关系中,ForeignKey写在一对多关系中,多的那个模型中

1、一对多

使用django.db.models.ForeignKey

例如,如果一个Car模型有一个Manufacturer, 也就是说,一个 Manufacturer可以对应多个汽车,但Car只有一个汽车生产商Manufacturer,那么使用以下定义:


from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=20)

class Car(models.Model):
    # 外键名是 对应类名的小写 
    # on_delete 是必须属性
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    
    name = models.CharField(max_length=20)

2、一对一

​ 使用django.db.models.OneToOneField

例如:地址Place和餐馆Restaurant是一对一的关系,而餐馆Restaurant和服务员Waiter是一对多的关系


from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

    def __str__(self):
        return "%s the place" % self.name

class Restaurant(models.Model):
    place = models.OneToOneField(
        Place,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    # BooleanField 在数据库使用 tinyint 类型
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

    def __str__(self):
        return "%s the restaurant" % self.place.name

class Waiter(models.Model):
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
    name = models.CharField(max_length=50)

    def __str__(self):
        return "%s the waiter at %s" % (self.name, self.restaurant)

3、多对多

1. 自关联:

from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=20)
    friends = models.ManyToManyField("self")
  • 会生成一个中间表,如下

    
    CREATE TABLE `test_app_student_friends` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `from_student_id` int(11) NOT NULL,
      `to_student_id` int(11) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `test_app_student_friends_from_student_id_to_stude_7ef9880e_uniq` (`from_student_id`,`to_student_id`),
      KEY `test_app_student_fri_to_student_id_154a4deb_fk_test_app_` (`to_student_id`),
      CONSTRAINT `test_app_student_fri_from_student_id_c400b5d4_fk_test_app_` FOREIGN KEY (`from_student_id`) REFERENCES `test_app_student` (`id`),
      CONSTRAINT `test_app_student_fri_to_student_id_154a4deb_fk_test_app_` FOREIGN KEY (`to_student_id`) REFERENCES `test_app_student` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
    
    

2. 简易多对多


class SchoolClass(models.Model):
    name = models.CharField(max_length=20)

class Teacher(models.Model):
    name = models.CharField(max_length=10)
    school_class = models.ManyToManyField(SchoolClass)
    
  • 会自动生成一个中间表,DDL语句如下:

    
    CREATE TABLE `test_app_teacher_school_class` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `teacher_id` int(11) NOT NULL,
      `schoolclass_id` int(11) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `test_app_teacher_school__teacher_id_schoolclass_i_f52c7361_uniq` (`teacher_id`,`schoolclass_id`),
      KEY `test_app_teacher_sch_schoolclass_id_7ac34d1e_fk_test_app_` (`schoolclass_id`),
      CONSTRAINT `test_app_teacher_sch_schoolclass_id_7ac34d1e_fk_test_app_` FOREIGN KEY (`schoolclass_id`) REFERENCES `test_app_schoolclass` (`id`),
      CONSTRAINT `test_app_teacher_sch_teacher_id_8c52afbd_fk_test_app_` FOREIGN KEY (`teacher_id`) REFERENCES `test_app_teacher` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
    
    

    其中:

    test_app_teacher_school_class是表名: test_app是应用名, teacher是第一个模型名,school_class是第二个模型名

3. 自定义中间表


from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_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)
    level = models.IntegerField(default=1)
    
  • 通过 through='Membership' 指定Membership作为中间表
  • 通过 through_fields=('group', 'person') 指定中间模型的属性
  • 一般需要自定义中间表时,都是有额外的字段,譬如 level = models.IntegerField(default=1)

方法

  • 除了运行程序时,可以测试模型,还可以在根目录的cmd执行:

python manage.py shell

  • 打开django脚本控制台,测试执行模型的方法,会比启动项目更方便
  • 首先需要导入:from Model_app.models import *
  • 模型对象中有一个objects属性,该属性是管理器Manager类型的对象,几乎所有的方法都是通过该对象执行。

1、新增

savecreate


p = Person.objects.create(first_name="Bruce", last_name="Springsteen")

或
p1 = Person(first_name="Bruce", last_name="Springsteen")
p1.save()

2、修改

save


p = Person(first_name="Bruce", last_name="Springsteen")
p.first_name = 'James'

"""
force_insert=True 强制刷新
force_update=True 强制新增
不能同时使用2个参数,几乎用不上这2个参数
"""
p.save(force_insert=True)

obj = MyModel.objects.create(val=1)

# 需要使用F来保证不会出现并发冲突
MyModel.objects.filter(pk=obj.pk).update(age=F('age') + 1)

# 更新多个值
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

3、查询

  • 大部分检索是懒惰执行的,只在真实获取值的时候,才会去连接数据库获取数据
  • 查询集通过过滤器进行查询,允许链式调用
  • pk是主键的别名primary key,如果真实主键是id,那么 pkid使用是一样的效果

1. 过滤器


"""
# get 获取一个对象
# 查询主键等于 1 的 , 如果主键是ID,也可以使用 id=1
# 如果条件找不到对应的记录,会抛出 DoesNotExist 错误
# 如果条件找到多个记录,会抛出 MultipleObjectsReturned 错误
"""
person = Person.objects.get(pk=1)


"""
# all 获取所有对象
# 查询所有,得到的QuerySets对象
"""
all_persons = Person.objects.all()


"""
# exclude 例外
# 查询 日期年份 不是2006的
"""
persons = Person.objects.exclude(pub_date__year=2006)


"""
# filter 获取对象列表
# 查询 日期年份 是 2006 的
"""
persons = Person.objects.filter(pub_date__year=2006)


"""
# filter 获取对象列表,支持切片,但是不支持负数切片
# limit  5 :前5个
"""
persons = Person.objects.filter(pub_date__year=2006)[:5]
# limit  5,5 : 第6个到10个
persons = Person.objects.filter(pub_date__year=2006)[5:10]
# Entry.objects.all()[-1] 不支持


"""
# 返回前10个记录的, 0 ,2 , 4, 6, 8, 10 ,
并且会立刻执行,而不是懒惰执行
"""
Entry.objects.all()[:10:2]


"""
# 可以在结果集上,继续检索,允许链式调用
# 结果集是一个QuerySet
"""
q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())
# 等同于:
q3 = Entry.objects.filter(headline__startswith="What").exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime.date.today())


"""
# order_by() 对结果集排序
"""
person_li = Person.objects.filter(pub_date__year=2006).order_by('pub_date')
# 支持多个字段, 类似 SQL:  order by pub_date, headline
person_li = Person.objects.filter(pub_date__year=2006).order_by('pub_date', 'headline')


"""
# count() 对结果集统计
"""
count = Person.objects.filter(pub_date__year=2006).count()



"""
# first() 返回结果集的第一个对象
"""
person = Person.objects.filter(pub_date__year=2006).first()
person = Person.objects.first()


"""
# last() 返回结果集的最后一个对象
"""
person = Person.objects.filter(pub_date__year=2006).last()



"""
# values() 返回一个 字典对象 列表
"""
person_dict_li = Person.objects.filter(pub_date__year=2006).values()

  • 大部分检索是懒惰的,只在真实获取值的时候,才会真正去连接数据库获取数据:

# 懒惰执行
q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)   # 此处才会真的去连接数据库获取记录

# 返回前10个记录的, 0 ,2 , 4, 6, 8, 10 ,并且会马上执行,而不是懒惰执行
q = Entry.objects.all()[:10:2]  # 已经获取到数据了
print(q)

2. 字段查找

  • 字段检索,是在字段名后加 __双下划线,再加关键字,类似 SQL 语句中的 where 后面的部分, 如: 字段名__关键字

  • 在查找中指定的字段必须是模型字段的名称

  • 如果是ForeignKey字段,则是属性名+ _id:Entry.objects.filter(blog_id=4), 定义的ForeignKeyblog

  • 常见的字段检索:

    exact :判断是否等于value,一般不使用,而直接使用 ‘=’

    contains:是否包含,大小写敏感,如果需要不敏感的话,使用icontains

    startswith:以value开头,大小写敏感

    endwith:以value结尾,大小写敏感

    in:是否包含在范围内

    isnull:是否为null, 如:filter(name__isnull=Flase)

    gt:大于,如:filter(sage__gt=30) , 年龄大于30

    gte:大于等于

    lt:小于

    lte:小于等于

    year/month/day/week_day/hour/minute/second:时间查询
    如:filter(pub_date\_\_year=2015) 年份是2015的,filter(pub_date__day=15) 天数是15的

    
    # 确定的搜索 ,SQL: where id = 14
    Blog.objects.get(id__exact=14)
    # 等同于
    Blog.objects.get(id=14)
    
    # 不区分大小写的确定搜索,匹配 beatles blog 、Beatles blog等
    Blog.objects.get(name__iexact="beatles blog")
    
    # 包含,contains ,SQL:WHERE headline LIKE '%Lennon%'
    Entry.objects.get(headline__contains='Lennon')
    # 不区分大小写的包含
    Entry.objects.get(headline__icontains='Lennon')
    
    # 以什么开头, SQL: WHERE headline LIKE 'Lennon%'
    # 还有 不区分大小写的 istartwith
    Entry.objects.get(headline__startswith='Lennon')
    
    # 同样有 endswith ,SQL : WHERE headline LIKE '%Lennon'
    # 还有 不区分大小写的 iendswith
    Entry.objects.get(headline__endswith='Lennon')
    
    # in
    Entry.objects.get(headline__in=['Lennon', 'Terry'])
    
    

3. 多对象关联

  • 通过Blog模型中,关联的另一个模型对象entry的属性进行过滤:
  • entry__headline__contains ,即使是访问模型对象entry的属性 headline,也必须使用 __

# 检索所有Blog具有至少一个对象Entry ,其headline包含'Lennon'
Blog.objects.filter(entry__headline__contains='Lennon')

# Blog中 有一个对象 entry  的 headline 中包含“Lennon”并且 是 2008年发布的
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
# 通过 , 分割多个条件, 相当于数据库中的 'and'
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

# 取上面相反的值
Blog.objects.exclude(entry__in=Entry.objects.filter(headline__contains='Lennon', pub_date__year=2008, ))

  • 一对多关系中,通过一个模型获取另一个模型:

from django.db import models

class Manufacturer(models.Model):
    name = models.CharField(max_length=20)

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
    
    name = models.CharField(max_length=20)
    
# 从 一的模型 查找 多的模型
# 通过 '多的模型小写名_set' 查找
manufacturer = Manufacturer.objects.first()
cars = manufacturer.car_set.all()

# 从 多的模型 查找 一的模型
car = Car.objects.first()
manufacturer = car.manufacturer

  • 多对多关系中,通过一个模型获取另一个模型:

class SchoolClass(models.Model):
    name = models.CharField(max_length=20)

class Teacher(models.Model):
    name = models.CharField(max_length=10)
    school_class = models.ManyToManyField(SchoolClass)


"""
从 没有写 ManyToManyField 的模型查找另一 写了 ManyToManyField 的模型
需要在 查询的模型名的小写后 加 _set
"""    
schoolClass = SchoolClass.objects.first()
teachers = schoolClass.teacher_set.all()


"""
从 写了 ManyToManyField 的模型查找另一个模型
"""
teacher = Teacher.objects.first()
schoolClasses = teacher.school_class.all()

4. 聚合函数

  • 使用aggregate()函数返回聚合函数的值

  • 常用的聚合函数有:AvgCountMaxMinSum

    
    # Max 找出最大的
    from dango.db.models import Max
    Person.objects.aggregate(Max('age'))
    # 结果是一个字典 {'age__max': 30}
    
    # 可以使用 max=Max('age') 指定 别名为 max,而不使用  age__max
    Person.objects.aggregate(max=Max('age'))
    
    # 多个聚合函数一起使用
    Person.objects.aggregate(Max('age'), Min('age'), Avg('age'))
    
    

5. 分组查询

使用annotate()函数实现分组查询,得配合其他函数:

  • annotate:用于分组,配合 AvgCount等聚合函数,如:annotate(max=Max('age'))
  • filter: 用于过滤,在 annotate之前使用表示 where 条件,在annotate之后使用表示having条件
  • values:在annotate之前使用表示分组字段,在annotate之后使用表示取值

"""
基本应用
以 group_id 分组,找出level的最大值,最小值,和平均值
"""
Membership.objects.values('group_id').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))

"""
以 group_id 分组 并且 group_id 大于 2 ,找出level的最大值,最小值,和平均值
"""   
Membership.objects.filter(group_id__gt=2).values('group_id').annotate(max=Max('level'), min=Min('level'), avg=Avg('level'))

4、删除

  • delete方法

person = Person.objects.get(pk=1)
person.delete()

5、刷新对象

  • 通过 refresh_from_db 从数据库中重新获取对象的内容

person = Person.objects.get(pk=1)
person.refresh_from_db()

6、Q对象

  • filter() 等方法中的关键字参数查询都是并且(‘AND’)的, 如果你需要执行更复杂的查询(例如or语句),那么可以使用Q 对象
  • Q 对象 (django.db.models.Q) 对象用于封装一组关键字参数,可以使用 &| 操作符组合起来,当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象

from django.db.models import Q

# 等同于 select * from poll where question like 'Who%' or question like 'What%'
poll = Poll.objects.filter(Q(question__startswith='Who') | Q(question__startswith='What'))

# 等同于 select * from poll WHERE question like 'Who%' and (pub_date = '2005-05-02' or pub_date = '2005-05-06')
poll = Poll.objects.filter(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

# Q对象可以使用 ~ 操作符取反, 相当于SQL中 not 
poll = Poll.objects.filter(
    Q(question__startswith='Who'),
    ~Q(pub_date__year=2005)
)

# Q对象可以和一般的关键字参数混用, 但是Q对象必须在一般关键字参数的前面
Poll.objects.filter(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who')

7、F对象

模型的属性名出现在操作符的右边,就使用F对象进行包裹

  • 可以使用模型的A属性与B属性进行比较
"""
找出女生数量大于男生数量的年级
对应sql:select * from grade where girlnum > boynum
"""
grades = Grade.objects.filter(girlnum__gt=F('boynum'))
 
  • 支持算数运算
"""
找出女生数量大于 男生数量+10 的年级
对应的sql: select * from grade where girlnum > boynum + 10
"""
Grade.objects.filter(girlnum__gt=F('boynum') + 10)
  
"""
所有书籍的价格 +1
对应的 sql: update book set price = price + 1 
"""
Book.objects.update(price=F("price")+1)


使用SQL语句

1、通过模型使用SQL

  • 通过raw函数执行原始SQL语句进行查询
  • 主键字段必须包含在查询的字段中,不然会引发错误

# 定义个 person 模型
class Person(models.Model):
    first_name = models.CharField()
    last_name = models.CharField()
    birth_date = models.DateField()

"""
# 执行 原始 SQL
# 表名前面必须加 应用名myapp,即在数据库中的真实表名,否则会抛出异常
"""
for p in Person.objects.raw('SELECT * FROM myapp_person'):
    print(p)

# 字段先后顺序没关系
Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
# 等同于
Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')

"""
# 可以从其他表格中查询出匹配 person 模型的记录集
# 总之最终的数据集的结构必须和 Person一样
"""
Person.objects.raw('SELECT first AS first_name, last AS last_name,bd AS birth_date,pk AS id,FROM some_other_table')

# 返回的结果集一样可以执行切片
first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]
# 但是上述语句会返回所有结果,基于节省传输的需要,在数据库缩小结果集范围更正确
first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]


# 传递参数
lname = 'Doe'
Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

2、避开模型使用SQL

  • 不应用模型,直接使用SQL语句进行增删改查

from django.db import connection

def my_custom_sql(obj):
    with connection.cursor() as cursor:
        cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [obj.baz])
        cursor.execute("SELECT foo FROM bar WHERE baz = %s", [obj.baz])
        row = cursor.fetchone()

    return row

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!