模型

阅读: 176987     评论:24

一个模型(model)就是一个单独的、确定的数据信息源,包含了数据的字段和操作方法。通常,每个模型映射为一张数据库中的表。

基本的原则如下:

  • 每个模型在Django中的存在形式为一个Python类
  • 每个类都是django.db.models.Model的子类
  • 模型(类)的每个字段(属性)代表数据表的某一列
  • Django自动为你生成访问数据库的API

一、简单示例

下面的模型定义了一个“人”,它具有first_namelast_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自动生成,默认格式为“项目名称+下划线+小写类名”,你可以重写这个规则。
  • Django会自动创建自增主键id,当然,你也可以自己指定主键。
  • 上面的SQL语句基于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个属性:addingdb

  • adding:一个标识符,如果当前的模型实例还没有保存到数据库内,则为True,否则为False
  • 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)
  • baby_boomer_status作为一个自定义的模型方法,可以被任何Person的实例调用,进行高峰期人口的判断
  • full_name模型方法被Python的属性装饰器转换成了一个类属性

具体使用操作:

>>>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方法不会在批量操作中调用,批量 creatingupdating 操作不支持上述操作,因为这两种操作未调用 save()pre_savepost_save方法。

四、通过包管理大量模型

manage.py startapp 命令会帮我们自动创建应用的代码结构,它默认包含一个 models.py 文件。

若你有很多 models.py 文件,用独立的文件管理它们会很实用且利于管理。

为了达到此目的,我们可以创建一个 models 包。

具体步骤:

  • 删除 models.py,创建一个 myapp/models 目录
  • 在其中创建一个 __init__.py 文件和一些存储模型代码的模块文件
  • __init__.py 文件中导入这些模块文件

比如,假设你在 models 目录下有 fruit.pyvegetable.py两个模块,那么myapp/models/__init__.py文件中可以这么写代码:

from .fruit import Apple
from .vegetable import Carrot

一定要显式导入每个模块,而不是使用 from .models import * ,这样不会打乱命名空间,使代码更具可读性,让代码分析工具更有用。

在需要使用模型的地方,依然是传统的方式:

from .models import Apple

 第一章:模型层 字段类型和参数 

评论总数: 24


点击登录后方可评论

刘老师,这节的示例是新建一个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岁之间



就是生成表的时候 给字段添加上备注



没有