模型 Model
Django 提供了一个抽象的模型(“models”)层,为了构建和操纵你的 Web 应用的数据
一般来说,每一个模型都映射一张数据库表。
- 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
- 模型类的每个属性都相当于一个数据库的字段。
- 模型类中可以自定义或重写改变数据的行为(方法)
- Django 提供了一个自动生成访问数据库的 API
设计数据库Model
from django.db import models
import datetime
from django.utils import timezone
class CommonInfo(models.Model):
""" 抽象基类 """
create_time = models.DateField(verbose_name='创建日期', auto_now_add=True)
update_time = models.DateTimeField(verbose_name='修改时间', auto_now=True)
class Meta:
abstract = True # 该模型将不会创建任何数据表。它的字段会自动添加至子类。
class Blog(CommonInfo):
""" 博客表 """
name = models.CharField(verbose_name='名称', max_length=100)
tagline = models.TextField(verbose_name='标语')
# file will be saved to MEDIA_ROOT/logos/2022/03/09
logo = models.FileField(upload_to='logos/%Y/%m/%d/', default='logo.jpg')
def __str__(self):
return self.name
# upload_to 也可以是一个可调用对象,如函数。
# 这个可调用对象必须接受两个参数:instance,filename,并返回一个 Unix 风格的路径(带斜线)
def user_directory_path(instance, filename):
"""
instance:FileField 的模型实例
filename:最初给文件的文件名
"""
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class Author(CommonInfo):
""" 作者表 """
name = models.CharField(verbose_name='名称', max_length=200)
email = models.EmailField(verbose_name='邮箱地址')
def __str__(self):
return self.name
class AuthorDetail(CommonInfo):
""" 作者详情表 """
author = models.OneToOneField(Author, verbose_name='关联作者', on_delete=models.CASCADE) # OneToOneField 一对一的关系
avatars = models.ImageField(verbose_name='头像', upload_to=user_directory_path, default='logo.jpg') # 需要安装Pillow库
settings = models.JSONField(verbose_name='设置', default=dict)
SEX_CHOICES = [(1, '男性'), (2, '女性')]
sex = models.IntegerField(verbose_name='性别', choices=SEX_CHOICES, default=1)
married = models.BooleanField(verbose_name='婚否')
website = models.URLField(verbose_name='个人网页', null=True, blank=True)
def __str__(self):
return self.author.name
class Entry(CommonInfo):
""" 文章表 """
blog = models.ForeignKey(Blog, verbose_name='关联博客', on_delete=models.CASCADE,
null=True) # ForeignKey 多对一的关系 CASCADE 级联删除
headline = models.CharField(verbose_name='文章标题', max_length=255)
body_text = models.TextField(verbose_name='文章内容')
authors = models.ManyToManyField(Author, verbose_name='关联作者') # ManyToManyField 多对多的关系
number_of_comments = models.IntegerField(verbose_name='评论数', default=0)
number_of_likes = models.IntegerField(verbose_name='点赞数', default=0)
rating = models.IntegerField(verbose_name='评级', default=1)
def __str__(self):
return self.headline
class Meta:
ordering = ['-create_time']
def was_modified_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=3) <= self.create_time <= now
@property
def numbers(self):
return self.number_of_comments + self.number_of_likes
def save(self, *args, **kwargs):
# do_something_before_save...
super().save(*args, **kwargs) # Call the "real" save() method.
# do_somethin_after_save
print(f"Save Entry success! headline:{self.headline}")
字段类型与属性选项说明
null
:
避免在基于字符串的字段上使用 null
,如 CharField
和 TextField
。如果一个基于字符串的字段有 null=True
,这意味着它有两种可能的“无数据”值:NULL
,和空字符串
blank
:
如果一个字段有 blank=True
,表单验证将允许输入一个空值。如果一个字段有 blank=False
,则该字段为必填字段。
choices
:
默认的表单部件将是一个带有这些选择的选择框,而不是标准的文本字段。
ImageField
:
- 需要安装
Pillow
来支持
pip install Pillow
FileField
,ImageField
upload_to 需要配置settings.py
中的MEDIA_ROOT
来管理上传根目录
## 设置文件上传的根目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
JSONField
¶
一个用于存储 JSON 编码数据的字段。在 Python 中,数据以其 Python 本地格式表示:字典、列表、字符串、数字、布尔值和 None
。
JSONField
在 MariaDB 10.2.7+、MySQL 5.7.8+、Oracle、PostgreSQL 和 SQLite(在 JSON1 扩展被启用的情况下)都支持。
**注意:要在 SQLite 上使用 JSONField
,你需要在 Python 的 sqlite3
库中启用 JSON1 扩展 **
多对一关联设计
定义一个多对一的关联关系,使用 django.db.models.ForeignKey
类
通常,我们在多的一端设置ForeignKey
多对多关联设计
定义一个多对多的关联关系,使用 django.db.models.ManyToManyField
类
通常,我们根据逻辑关系,将ManyToManyField实例放到需要在表单中被剪辑的对象中
一对一关联设计
使用 OneToOneField
来定义一对一关系,当一个对象以某种方式“继承”另一个对象时,这对该对象的主键非常有用。
通常,我们在继承(扩展)的一方设置OneToOneField
使用模型
一旦你定义了你的模型,你需要告诉 Django 你准备 使用 这些模型。你需要修改设置文件中的 INSTALLED_APPS
,在这个设置中添加包含 models.py
文件的模块名称。
INSTALLED_APPS = [
# ...
'app_demo',
# ...
]
当你向 INSTALLED_APPS
添加新的应用的时候,请务必进行表结构迁移。
python manage.py makemigrations
python manage.py migrate
Django默认数据库为SQLite3,表结构迁移后,可见在根目录下生成了一个db.sqlite3 的数据库文件。打开该文件即可使用这个数据库。
数据库Model API
在控制台中通过命令 python manage.py shell
或 通过pycharm->Tools->Run manage.py Task...
然后输入dbshell
进入django shell 环境
创建对象
>>> from app_demo.models import Blog,Author,AuthorDetail,Entry
>>> b = Blog(name='学习之家',tagline='好好学习,天天向上!')
>>> b.save()
>>>
>>> b1 = Blog.objects.create(name='我爱编程',tagline='世界和平,人间有爱~')
>>> b1
<Blog: 我爱编程>
>>>
>>> a1 = Author.objects.create(name='a1',email='a1@126.com')
>>>
>>> a1_detail = AuthorDetail.objects.create(author=a1,married=True,website='a1.com')
>>>
>>> a2 = Author.objects.create(name='a2',email='a2@126.com')
>>>
>>> a2_detail = AuthorDetail(married=False,website='a2.com')
>>> a2_detail.author = a2
>>> a2_detail.save()
>>>
>>> entry = Entry(blog=b1,headline='Django',body_text='Django入门到精通...')
>>> entry.save()
Add Entry success! headline:Django
>>> entry.authors.add(a1,a2)
save()
没有返回值create()
返回对象实例
为模型提供初始化数据
在进行查询练习前,让我们先在数据库中初始化一批练习数据。
- 创建固定内容数据
app_demo/fixtures/test_data.json
[
{
"model": "app_demo.Blog",
"pk": 1,
"fields": {
"name": "学习之家",
"tagline": "好好学习,天天向上~",
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.Blog",
"pk": 2,
"fields": {
"name": "我爱编程",
"tagline": "世界和平,人间有爱~",
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.Author",
"pk": 1,
"fields": {
"name": "张三",
"email": "zhangsan@126.com",
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.Author",
"pk": 2,
"fields": {
"name": "李四",
"email": "lisi@126.com",
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.Author",
"pk": 3,
"fields": {
"name": "王五",
"email": "wangwu@126.com",
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.AuthorDetail",
"pk": 1,
"fields": {
"author_id": 1,
"married": true,
"website": "http://zhangsan.com",
"settings": {
"ext_info": {
"lang": "zh-hans",
"focus": ["python","java"]
},
"collaps": true
},
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.AuthorDetail",
"pk": 2,
"fields": {
"author_id": 2,
"married": true,
"website": "http://lisi.com",
"settings": {
"ext_info": {
"lang": "en-us",
"focus": ["php","java"]
},
"collaps": false
},
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.AuthorDetail",
"pk": 3,
"fields": {
"author_id": 3,
"married": false,
"website": "http://wangwu.com",
"settings": {
"ext_info": {
"lang": "en-us",
"focus": ["c++","English"]
},
"collaps": true,
"flag":true
},
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
},
{
"model": "app_demo.Entry",
"pk": 1,
"fields": {
"blog_id": 1,
"headline": "python入门到精通",
"body_text": "人生苦短,我用python!",
"authors": [
1
],
"number_of_comments": 10,
"number_of_likes": 9,
"rating": 1,
"create_time": "2020-03-01",
"update_time": "2020-03-01 11:11:11"
}
},
{
"model": "app_demo.Entry",
"pk": 2,
"fields": {
"blog_id": 1,
"headline": "php入门到精通",
"body_text": "PHP是世界上最好的语言!",
"authors": [
1,
2
],
"number_of_comments": 9,
"number_of_likes": 8,
"rating": 1,
"create_time": "2020-03-01",
"update_time": "2020-03-01 11:11:11"
}
},
{
"model": "app_demo.Entry",
"pk": 3,
"fields": {
"blog_id": 1,
"headline": "java入门到精通",
"body_text": "一门面向对象的编程语言...",
"authors": [
1,
3
],
"number_of_comments": 7,
"number_of_likes": 8,
"rating": 1,
"create_time": "2020-03-01",
"update_time": "2020-03-01 11:11:11"
}
},
{
"model": "app_demo.Entry",
"pk": 4,
"fields": {
"blog_id": 2,
"headline": "c语言入门到精通",
"body_text": "Basic Combined Programming Language...",
"authors": [
2,
3
],
"number_of_comments": 4,
"number_of_likes": 4,
"rating": 1,
"create_time": "2021-03-01",
"update_time": "2021-03-01 11:11:11"
}
},
{
"model": "app_demo.Entry",
"pk": 5,
"fields": {
"blog_id": 1,
"headline": "汇编语言入门到精通",
"body_text": "面向机器的程序设计语言...",
"authors": [
1,
3
],
"number_of_comments": 4,
"number_of_likes": 0,
"rating": 1,
"create_time": "2022-03-01",
"update_time": "2022-03-01 11:11:11"
}
}
]
- 加载数据到数据库
>>>python manage.py loaddata test_data.json
查询对象
获取所有对象(记录)
>>>Blog.objects.all()
<QuerySet [<Blog: 学习之家>, <Blog: 我爱编程>]>
>>>Blog.objects.first()
<Blog: 学习之家>
>>>Blog.objects.last()
<Blog: 我爱编程>
>>>Blog.objects.get(pk=1)
<Blog: 学习之家>
>>>Blog.objects.get(name='学习之家')
<Blog: 学习之家>
>>>Blog.objects.count()
2
通过过滤器检索指定对象
all()
返回的QuerySet
包含了数据表中所有的对象- filter(\kwargs)** 返回一个新的
QuerySet
,包含的对象满足给定查询参数。 - exclude(\kwargs)** 返回一个新的
QuerySet
,包含的对象 不 满足给定查询参数。
>>>Entry.objects.filter(headline='c语言入门到精通')
<QuerySet [<Entry: c语言入门到精通>]>
**基本的查询关键字参数遵照 field__lookuptype=value
。(有个双下划线) **
字符串类型字段:__startswith,__endswith,__contains
>>>Entry.objects.filter(headline__startswith='c语言')
<QuerySet [<Entry: c语言入门到精通>]>
>>>
>>>Entry.objects.filter(headline__endswith='精通')
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: c语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>, <Entry: java入门到精通>]>
>>>
>>>Entry.objects.filter(headline__contains='精通').exclude(headline__contains='java')
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: c语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>]>
时间类型字段:__year
>>>Entry.objects.filter(create_time__year=2021)
<QuerySet [<Entry: c语言入门到精通>]>
>>>
>>>Entry.objects.filter(create_time__year__lt=2021)
<QuerySet [<Entry: python入门到精通>, <Entry: php入门到精通>, <Entry: java入门到精通>]>
>>>
>>>Entry.objects.filter(create_time__year__lt=2021)[1:3]
<QuerySet [<Entry: php入门到精通>, <Entry: java入门到精通>]>
F()为模型指定字段
>>>from django.db.models import F
>>>Entry.objects.filter(number_of_comments__gt=F('number_of_likes'))
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>]>
>>>
>>>Entry.objects.filter(create_time__year=F('update_time__year'))
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: c语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>, <Entry: java入门到精通>]>
查询 JSONField
>>>AuthorDetail.objects.filter(settings__collaps=True)
<QuerySet [<AuthorDetail: 张三>, <AuthorDetail: 王五>]>
>>>
>>>AuthorDetail.objects.filter(settings__ext_info__lang='zh-hans')
<QuerySet [<AuthorDetail: 张三>]>
>>>
>>>AuthorDetail.objects.filter(settings__ext_info__focus__0='python')
<QuerySet [<AuthorDetail: 张三>]>
>>>
>>>AuthorDetail.objects.filter(settings__flag__isnull=True)
<QuerySet [<AuthorDetail: 张三>, <AuthorDetail: 李四>]>
>>>
>>>AuthorDetail.objects.filter(settings__has_key='flag')
<QuerySet [<AuthorDetail: 王五>]>
>>>
>>>AuthorDetail.objects.filter(settings__has_keys=['collaps','flag'])
<QuerySet [<AuthorDetail: 王五>]>
>>>
>>>AuthorDetail.objects.filter(settings__has_any_keys=['collaps','flag'])
<QuerySet [<AuthorDetail: 张三>, <AuthorDetail: 李四>, <AuthorDetail: 王五>]>
>>>
通过 Q
对象完成复杂查询
在类似 filter()
中,查询使用的关键字参数是通过 “AND” 连接起来的。如果你要执行更复杂的查询(例如,由 OR
语句连接的查询),你可以使用 Q 对象
。
Q
对象能通过 &
和 |
操作符连接起来。当操作符被用于两个 Q
对象之间时会生成一个新的 Q
对象。
>>>from django.db.models import Q
>>>Entry.objects.filter(Q(headline__startswith='java') | Q(headline__startswith='c语言'))
<QuerySet [<Entry: c语言入门到精通>, <Entry: java入门到精通>]>
>>>
>>>Entry.objects.filter(Q(headline__startswith='java') | Q(headline__startswith='c语言'),create_time__year__lt=2021)
<QuerySet [<Entry: java入门到精通>]>
跨关联查询:多对一关联模型
通过1模型的条件过滤获得关联的M模型实例列表
>>>Entry.objects.filter(blog__name='我爱编程')
<QuerySet [<Entry: c语言入门到精通>]>
>>>
>>>Entry.objects.filter(blog__name='学习之家')
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>, <Entry: java入门到精通>]>
通过M模型实例获取其关联的1模型对象
若模型有个 ForeignKey
,该模型的实例能通过其属性访问关联(外部的)对象。
>>>e = Entry.objects.get(pk=2)
>>>e.blog
<Blog: 学习之家>
通过M模型的条件过滤获得关联的1模型实例列表
>>>Blog.objects.filter(entry__headline='python入门到精通')
<QuerySet [<Blog: 学习之家>]>
通过1模型实例获取其关联的M模型对象列表
若模型有 ForeignKey
,外键关联的模型实例将能访问 Manager
,后者会返回第一个模型的所有实例。默认情况下,该 Manager
名为 FOO_set
, FOO
即源模型名的小写形式。例如 某个Blog
实例对象blog
,可通过blog.entry_set.all()
可获得该blog
对应的所有Entry
实例列表。
>>>blog = Blog.objects.get(pk=1)
>>>blog.entry_set.all()
<QuerySet [<Entry: 汇编语言入门到精通>, <Entry: python入门到精通>, <Entry: php入门到精通>, <Entry: java入门到精通>]>
>>>blog.entry_set.filter(headline='python入门到精通')
<QuerySet [<Entry: python入门到精通>]>
重写模型关联名称related_name
你可以在定义 ForeignKey
时设置 related_name
参数重写这个 FOO_set
名。例如,若修改 Entry
模型为 blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
,前文示例代码会看起来像这样:
>>>blog = Blog.objects.get(pk=1)
>>>blog.entry_set.all()
add(obj1,** obj2, **…) 加入关联对象
>>>blog = Blog.objects.get(pk=2)
>>>e = Entry.objects.get(pk=1)
>>>blog.entry_set.add(e)
create(**kwargs)创建关联对象
>>>blog = Blog.objects.get(pk=1)
>>>blog.entry_set.create(headline='重要思想理论',body_text='马克思思想...')
Add Entry success! headline:重要思想理论
<Entry: 重要思想理论>
remove(obj1,obj2,…) 删除关联对象
注意:remove()方法仅在Foreign Key对象设置了null=True时才存在
>>>blog = Blog.objects.get(pk=2)
>>>blog.entry_set.all()
<QuerySet [<Entry: c语言入门到精通>, <Entry: python入门到精通>]>
>>>e=Entry.objects.get(headline='python入门到精通')
>>>
>>>blog.entry_set.remove(e)
>>>
>>>blog.entry_set.all()
<QuerySet [<Entry: c语言入门到精通>]>
clear() 从关联对象集合删除所有对象
注意:clear()方法仅在Foreign Key对象设置了null=True时才存在
>>>blog.entry_set.all()
<QuerySet [<Entry: c语言入门到精通>]>
>>>
>>>blog.entry_set.clear()
>>>
>>>blog.entry_set.all()
<QuerySet []>
跨关联查询:多对多关联模型
多对多关联的两端均自动获取访问另一端的 API
不同点在为属性命名上:定义了 ManyToManyField
的模型使用字段名作为属性名,而 “反向” 模型使用源模型名的小写形式,加上 '_set'
(就像反向一对多关联一样)
观察数据库,可见数据库中,为多对多关联关系,创建了一张额外的关联表
entry_authors
在定义了 ManyToManyField
的模型中,使用字段名获得关联对象模型Manager
>>>e=Entry.objects.get(id=3)
>>>e.authors.all()
<QuerySet [<Author: 张三>, <Author: 王五>]>
在 ManyToManyField
的“反向”模型中,使用默认的源模型名的小写形式,加上 '_set' 获取管理对象模型Manager
>>>a = Author.objects.get(id=2)
>>>a.entry_set.all()
<QuerySet [<Entry: c语言入门到精通>, <Entry: php入门到精通>]>
跨关联查询:一对一关联模型
一对一关联与多对一关联非常类似。若在模型中定义了 OneToOneField
,该模型的实例只需通过其属性就能访问关联对象。
定义了 OneToOneField
的模型,通过其字段获取关联模型对象
>>>ad = AuthorDetail.objects.get(id=2)
>>>ad.author
<Author: 李四>
>>>ad.author.email
'lisi@126.com'
在反向查询中,以定义了 OneToOneField
的模型类名小写作为字段获取关联模型对象
>>>a=Author.objects.get(id=2)
>>>a.authordetail
<AuthorDetail: 李四>
>>>a.authordetail.website
'http://lisi.com'
修改对象
通过实例对象修改
>>>e = Entry.objects.get(pk=1)
>>>e.headline
'python入门到精通'
>>>e.headline='python入门到放弃'
>>>e.save()
Add Entry success! headline:python入门到放弃
要将修改保存至数据库中已有的某个对象,使用 save()
。
通过QuerySet批量修改
>>>Entry.objects.filter(headline__contains='精通').update(number_of_likes=1000)
4
删除对象
通过实例对象删除
>>>e = Entry.objects.get(pk=1)
>>>e.delete()
(2, {'app_demo.Entry_authors': 1, 'app_demo.Entry': 1})
通过QuerySet批量删除
>>>Entry.objects.filter(headline__contains='精通').delete()
(12, {'app_demo.Entry_authors': 8, 'app_demo.Entry': 4})
数据库配置
SQLite
django默认使用SQLite数据库,settings.py
中数据库配置如下
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
MySQL
Django 支持 MySQL 5.7 及以上版本。
在使用MySQL数据库前,需在对应数据库服务器上创建相应的数据库,然后修改settings.py
中的数据库连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_demo', # 指定连接的数据库名称
'USER': 'xxx', # 数据库连接账号
'PASSWORD': 'xxx',# 数据库连接密码
'HOST': 'xxx.xxx.xxx.xxx', # 数据库连接地址
'PORT': '3306' # 数据库连接端口
}
}
注意,使用mysql数据库,还需要安装mysql数据驱动如下
pip install mysqlclient
数据迁移同步
python manage.py makemigrations
python manage.py migrate
注意:一旦model发生了变化,都需要执行以上俩命令进行数据结构同步。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。