一个模型(model)就是一个单独的、确定的数据信息源,包含了数据的字段和操作方法。通常,每个模型映射为一张数据库中的表。
基本的原则如下:
django.db.models.Model
的子类下面的模型定义了一个“人”,它具有first_name
和last_name
字段:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
每一个字段都是一个类属性,每个类属性表示数据表中的一个列。
上面的代码,相当于下面的原生SQL语句:
CREATE TABLE myapp_person ( "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
注意:
myapp_person
由Django自动生成,默认格式为“项目名称+下划线+小写类名
”,你可以重写这个规则。id
,当然,你也可以自己指定主键。PostgreSQL
语法。通常,我们会将模型编写在其所属app下的models.py
文件中,没有特别需求时,请坚持这个原则,不要自己给自己添加麻烦。
创建了模型之后,在使用它之前,你需要先在settings文件中的INSTALLED_APPS
处,注册models.py
文件所在的myapp
。看清楚了,是注册app,不是模型,也不是models.py
。如果你以前写过模型,可能已经做过这一步工作,可跳过。
INSTALLED_APPS = [ #... 'myapp', #... ]
当你每次对模型进行增、删、修改时,请务必执行命令python manage.py migrate
,让操作实际应用到数据库上。这里可以选择在执行migrate之前,先执行python manage.py makemigrations
让修改动作保存到记录文件中,方便github等工具的使用。
每个模型都可以有很多属性,其中有Django内置的,也可以有你自定义的。
模型当中最重要的属性是 Manager管理器。它是 Django 模型和数据库查询操作之间的API接口,用于从数据库当中获取数据实例。如果没有指定自定义的 Manager ,那么它默认名称是 objects
,这是Django自动为我们提供和生成的。Manager 只能通过模型类来访问,不能通过模型实例来访问,也就是说,只能Person.objects
,不可以jack.objects
。
模型还有一个不为人知的隐藏属性_state
。
_state
属性指向一个ModelState
类实例,它持续跟踪着模型实例的生命周期。
_state
自己又有2个属性:adding
和db
所以:
adding=True
并且db=None
adding=False
并且db='数据库名'
>>> blog = Blog.create('mary', 'ss') >>> blog._state <django.db.models.base.ModelState object at 0x00000203CD717D30> >>> blog._state.adding True >>> blog._state.db # None
在模型中添加实例方法会给你的模型提供自定义的“行级”数据操作能力,也就是说每个模型的实例都可以调用模型方法。与之对应的是类 Manager 的方法提供的是“表级”的数据操作。
在后面的章节有对Django内置API方法的详细介绍。
建议:如果你有一段需要针对每个模型实例都有效的业务代码,应该把它们抽象成为一个函数,放到模型中成为模型方法,而不是在大量视图中重复编写这段代码,或者在视图中抽象成一个函数。
下面的例子展示了如何自定义模型方法:
class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() def baby_boomer_status(self): "判断某人是否处于生育高峰期诞生的" import datetime if self.birth_date < datetime.date(1945, 8, 1): return "高峰期之前" elif self.birth_date < datetime.date(1965, 1, 1): return "高峰期" else: return "高峰期之后" @property def full_name(self): "返回此人的全名" return '%s %s' % (self.first_name, self.last_name)
具体使用操作:
>>>jack = Person.objects.get(pk=1) >>>jack.baby_boomer_status() # 以执行函数的方式调用 # ... >>>jack.full_name # 以属性的方式调用 # jack Tomas
Django内置了一些模型方法,有些我们直接使用即可,有些会进行自定义重写:
__str__()
: 这个其实是Python的魔法方法,用于返回实例对象的打印字符串。为了让显示的内容更直观更易懂,我们往往自定义这个方法:class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() def __str__(self): return self.first_name + self.last_name
get_absolute_url()
: 这个方法是返回每个模型实例的相应的访问url。具体看后面的章节。
__hash__()
:
实际上,Django在内部还为models.Model
实现了__hash__()
魔法方法,给模型实例提供唯一的哈希值。
这个方法的核心是hash(obj.pk)
,通过模型主键的值,使用内置的hash方法生成哈希值。如果实例还未保存,没有主键值,显然会发生错误。哈希值一旦生成就不允许修改。
__eq__()
这个方法用来判断两个对象是否是同一个模型实例。使用双等号运算符来比较。具有相同主键值和相同具体类的实例被认为是相等的。但主键值为 None
的实例除自身外对任何事物都不相等。
例子:
from django.db import models class MyModel(models.Model): id = models.AutoField(primary_key=True) class MyProxyModel(MyModel): class Meta: proxy = True class MultitableInherited(MyModel): pass # 对比主键 MyModel(id=1) == MyModel(id=1) MyModel(id=1) != MyModel(id=2) # 主键是None的时候 MyModel(id=None) != MyModel(id=None) # 相同的实例 instance = MyModel(id=None) instance == instance # 代理模型的情况 MyModel(id=1) == MyProxyModel(id=1) # 多继承的情况 MyModel(id=1) != MultitableInherited(id=1)
Django为模型内置了一些方法,我们可以重写这些方法来更改行为,其中最重要的是save和delete方法。
一个典型的重写内置save方法的场景,比如你想在保存对象时额外做些事:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): # 注意save方法的参数 # do_something() super().save(*args, **kwargs) # 调用原本的save方法,保证基本的Django运行机制不被破坏 # do_something_else()
你也可以在需要时打断保存动作:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): if self.name == "挪威的森林": return # 不允许博客的名字叫挪威的森林 else: super().save(*args, **kwargs) # 其的可以
调用父类的方法非常重要——这里指 super().save(*args, **kwargs)
——它能确保对象正确的写入数据库。若你忘记调用父类方法,默认行为不会被触发,数据库也不会被操作,相当于前面的白干了。
save方法的参数*args, **kwargs
也很重要 。由于Django 会不时地扩展模型内置方法的功能,也会添加新参数。在重写的save方法中使用 *args, **kwargs
,可以确保你的新方法能接受这些新加的参数,不至于出现语法错误。
如果你希望更新save方法中的字段值,也可以将此字段添加到“update_fields”关键字参数中。这将确保在指定“update_fields”时保存字段。例如
from django.db import models from django.utils.text import slugify class Blog(models.Model): name = models.CharField(max_length=100) slug = models.TextField() def save( self, force_insert=False, force_update=False, using=None, update_fields=None ): self.slug = slugify(self.name) if update_fields is not None and "name" in update_fields: update_fields = {"slug"}.union(update_fields) super().save( force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields, )
要注意,重写的save方法不会在批量操作中调用,批量 creating
和 updating
操作不支持上述操作,因为这两种操作未调用 save()
,pre_save
和 post_save
方法。
manage.py startapp
命令会帮我们自动创建应用的代码结构,它默认包含一个 models.py
文件。
若你有很多 models.py
文件,用独立的文件管理它们会很实用且利于管理。
为了达到此目的,我们可以创建一个 models
包。
具体步骤:
models.py
,创建一个 myapp/models
目录__init__.py
文件和一些存储模型代码的模块文件__init__.py
文件中导入这些模块文件比如,假设你在 models
目录下有 fruit.py
和 vegetable.py
两个模块,那么myapp/models/__init__.py
文件中可以这么写代码:
from .fruit import Apple from .vegetable import Carrot
一定要显式导入每个模块,而不是使用 from .models import *
,这样不会打乱命名空间,使代码更具可读性,让代码分析工具更有用。
在需要使用模型的地方,依然是传统的方式:
from .models import Apple
刘老师,这节的示例是新建一个myapp应用,在myapp/models里进行练习么? 作为初学者面对这么多函数、类、以及很多不懂的地方,要死磕记忆还是保留疑问疑惑继续按照您教程学习呢
是要跟着练习。如果你的Python基础薄弱,那建议先去学习一下我的Python教程。如果只是刚开始学习Django,那确实需要先死记硬背一些东西,保留疑问先继续学下去,整体过一遍。第二遍再来重新精度精研,加深理解。想要一遍一次掌握Django,基本是不可能的。
完成
我在下面的语句添加了age = models.CharField(max_length=5) from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) age = models.CharField(max_length=5) 执行了python manage.py migrate,python manage.py makemigrations后,发现后面无法跟着练习下去,请问怎么办?
删除生成的0002_Person.py, 再执行一次就可以了。谢谢。 小白的自问自答
您好,看完您的文章,对FilePathField 和 FileField 两个之间的区别存在疑问,网上百度了一下http://landcareweb.com/questions/36545/yao-shi-yong-django-filefieldhuan-shi-filepathfield,我觉得 这个讲解的挺清楚的。 同时建议您可以增加一些关于这两个的讲解,谢谢
感谢您的宝贵建议。其实我在视频教程里有详细讲解,只是没想到文字教程也要写得更细一点。
老师你好,请问你的视频教程哪里可以看到
最顶上导航栏有链接^-^
老师您好, 文中:表名myapp_person由Django自动生成,默认格式为“项目名称+下划线+小写类名”,你可以重写这个规则。 这里应该是 应用名称+ 下划线 + 小写类名吧
对。
老师您好,有个字段定义问题想问您, 平常定义字段是这么定义的:first_name = models.CharField(max_length=50) 我从网上看了其他的一个例子,例子的链接是https://stackoverflow.com/questions/24569687/searching-by-related-fields-in-django-admin/55740168#55740168,里面有个Result model,这个model定义的字段是 year = models.IntegerField(_("Year")) time = models.CharField(_("Time"), max_length=8) 但是在我的环境这种写法报错,我想问下,这种方法是为了什么才这么定义那?还是这个例子给的不全本身有误呢?谢谢您
一、将字符串作为第一个参数提供给Field,表示这是它的verbose_name 二、类似_('string')的写法是国际化和本地化的操作,外国人经常需要这么做 三、你报错是因为你没有导入:‘from django.utils.translation import ugettext as _’ 四、参考http://www.liujiangblog.com/course/django/180
原来这样,学习了;导入后没错了,感谢老师指导。
可以将在settings文件中,配置MEDIA_ROOT的详细过程写一下吗
刚百度了一下,参考一下这个,http://www.zhimengzhe.com/bianchengjiaocheng/qitabiancheng/338810.html。
刚开始学习,这篇实例偏少,枯燥了些
auto id字段在form中看不到这个,所以更新数据时候都是新增了,其实我想要的时候update。这是为什么?
DateField 在后台有时间选择器, 在前台却没有选择器, 怎么让前台也显示时间选择器;
我在一个model里面定义了一个字段对应的外键 在另一个model里面,如何引用进来,直接引用会报错
makemigrations会生成一个.py文件,那么它对于多人开发使用git进行版本控制有什么作用吗?能不能稍微详细地讲解 一下,谢谢谢谢!
假如一个学生的年龄,限制在15-40岁之间
就是生成表的时候 给字段添加上备注
没有