模型和字段

阅读: 175470     评论: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" serial NOT NULL PRIMARY KEY,
    "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个属性:adding和db

  • 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

模型方法

模型的方法其实就是Python的实例方法。Django内置了一些,我们也可以自定义一些。

在模型中添加自定义方法会给你的模型提供自定义的“行级”数据操作能力,也就是说每个模型的实例都可以调用模型方法。与之对应的是类 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):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        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方法生成哈希值。如果实例还未保存,没有主键值,显然会发生错误。哈希值一旦生成就不允许修改。

模型字段fields

字段是模型中最重要的内容之一,也是唯一必须的部分。字段在Python中表现为一个类属性,体现了数据表中的一个列。请不要使用cleansavedelete等Django内置的模型API名字,防止命名冲突。下面是一个展示,注意字段的写法:

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

字段命名约束:

Django不允许下面两种字段名:

  • 与Python关键字冲突。这会导致语法错误。例如:
class Example(models.Model):
    pass = models.IntegerField() # 'pass'是Python保留字! 
  • 字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法。例如:
class Example(models.Model):
    foo__bar = models.IntegerField() # 'foo__bar' 有两个下划线在一起!
  • 字段名不能以下划线结尾,原因同上。

由于你可以自定义表名、列名,上面的规则可能被绕开,但是请养成良好的习惯,一定不要那么起名。

常用字段类型

模型中的每一个字段都应该是某个 Field 类的实例,字段类型具有下面的作用:

  • 决定数据表中对应列的数据类型(例如:INTEGER, VARCHAR, TEXT)
  • HTML中对应的表单标签的类型,例如<input type=“text” />
  • 在admin后台和自动生成的表单中进行数据验证

Django内置了许多字段类型,它们都位于django.db.models中,例如models.CharField,它们的父类都是Field类。这些类型基本满足需求,如果还不够,你也可以自定义字段。

下表列出了所有Django内置的字段类型,但不包括关系字段类型(字段名采用驼峰命名法,初学者请一定要注意):

类型 说明
AutoField 一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键!
BigAutoField 64位整数类型自增字段,数字范围更大,从1到9223372036854775807
BigIntegerField 64位整数字段(看清楚,非自增),类似IntegerField ,-9223372036854775808 到9223372036854775807。在Django的模板表单里体现为一个NumberInput标签。
BinaryField 二进制数据类型。较少使用。
BooleanField 布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果设置了参数null=True,则表现为NullBooleanSelect选择框。可以提供default参数值,设置默认值。
CharField 最常用的类型,字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是text input。
DateField class DateField(auto_now=False, auto_now_add=False, **options) , 日期类型。一个Python中的datetime.date的实例。在HTML中表现为DateInput标签。在admin后台中,Django会帮你自动添加一个JS日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期(注意,它是不可修改的)。设置上面两个参数就相当于给field添加了editable=Falseblank=True属性。如果想具有修改属性,请用default参数。例子:pub_time = models.DateField(auto_now_add=True),自动添加发布时间。
DateTimeField 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。
DecimalField 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits:最大的位数,必须大于或等于小数点位数 。decimal_places:小数点位数,精度。 当localize=False时,它在HTML表现为NumberInput标签,否则是textInput类型。例子:储存最大不超过999,带有2位小数位精度的数,定义如下:models.DecimalField(..., max_digits=5, decimal_places=2)
DurationField 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题!
EmailField 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用Django内置的EmailValidator进行邮箱格式合法性验证。
FileField class FileField(upload_to=None, max_length=100, **options)上传文件类型,后面单独介绍。
FilePathField 文件路径类型,后面单独介绍
FloatField 浮点数类型,对应Python的float。参考整数类型字段。
ImageField 图像类型,后面单独介绍。
IntegerField 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput或者TextInput标签。
GenericIPAddressField class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options),IPV4或者IPV6地址,字符串形式,例如192.0.2.30或者2a02:42fe::4。在HTML中表现为TextInput标签。参数protocol默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。
JSONField JSON类型字段。Django3.1新增。签名为class JSONField(encoder=None,decoder=None,**options)。其中的encoder和decoder为可选的编码器和解码器,用于自定义编码和解码方式。如果为该字段提供default值,请务必保证该值是个不可变的对象,比如字符串对象。
PositiveBigIntegerField 正的大整数,0到9223372036854775807
PositiveIntegerField 正整数,从0到2147483647
PositiveSmallIntegerField 较小的正整数,从0到32767
SlugField slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。
SmallAutoField Django3.0新增。类似AutoField,但是只允许1到32767。
SmallIntegerField 小整数,包含-32768到32767。
TextField 用于储存大量的文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。
TimeField 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。
URLField 一个用于保存URL地址的字符串类型,默认最大长度200。
UUIDField 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char(32)。这个字段是自增主键的最佳替代品,后面有例子展示。

1.FileField

class FileField(upload_to=None, max_length=100, **options)

上传文件字段(不能设置为主键)。默认情况下,该字段在HTML中表现为一个ClearableFileInput标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length参数自定义。真实的文件是保存在服务器的文件系统内的。

重要参数upload_to用于设置上传地址的目录和文件名。如下例所示:

class MyModel(models.Model):
    # 文件被传至`MEDIA_ROOT/uploads`目录,MEDIA_ROOT由你在settings文件中设置
    upload = models.FileField(upload_to='uploads/')
    # 或者
    # 被传到`MEDIA_ROOT/uploads/2015/01/30`目录,增加了一个时间划分
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

Django很人性化地帮我们实现了根据日期生成目录或文件的方式!

upload_to参数也可以接收一个回调函数,该函数返回具体的路径字符串,如下例:

def user_directory_path(instance, filename):
    #文件上传到MEDIA_ROOT/user_<id>/<filename>目录中
    return 'user_{0}/{1}'.format(instance.user.id, filename)

class MyModel(models.Model):
    upload = models.FileField(upload_to=user_directory_path)

例子中,user_directory_path这种回调函数,必须接收两个参数,然后返回一个Unix风格的路径字符串。参数instace代表一个定义了FileField的模型的实例,说白了就是当前数据记录。filename是原本的文件名。

从Django3.0开始,支持使用pathlib.Path 处理路径。

当你访问一个模型对象中的文件字段时,Django会自动给我们提供一个 FieldFile实例作为文件的代理,通过这个代理,我们可以进行一些文件操作,主要如下:

  • FieldFile.name : 获取文件名
  • FieldFile.size: 获取文件大小
  • FieldFile.url :用于访问该文件的url
  • FieldFile.open(mode='rb'): 以类似Python文件操作的方式,打开文件
  • FieldFile.close(): 关闭文件
  • FieldFile.save(name, content, save=True): 保存文件
  • FieldFile.delete(save=True): 删除文件

这些代理的API和Python原生的文件读写API非常类似,其实本质上就是进行了一层封装,让我们可以在Django内直接对模型中文件字段进行读写,而不需要绕弯子。

2. ImageField

class ImageField(upload_to=None, height_field=None, width_field=None, max_length=100, **options)

用于保存图像文件的字段。该字段继承了FileField,其用法和特性与FileField基本一样,只不过多了两个属性height和width。默认情况下,该字段在HTML中表现为一个ClearableFileInput标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length参数自定义。真实的图片是保存在服务器的文件系统内的。

height_field参数:保存有图片高度信息的模型字段名。 width_field参数:保存有图片宽度信息的模型字段名。

使用Django的ImageField需要提前安装pillow模块,pip install pillow即可。

3. 使用FileField或者ImageField字段的步骤:

  1. 在settings文件中,配置MEDIA_ROOT,作为你上传文件在服务器中的基本路径(为了性能考虑,这些文件不会被储存在数据库中)。再配置个MEDIA_URL,作为公用URL,指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。
  2. 添加FileField或者ImageField字段到你的模型中,定义好upload_to参数,文件最终会放在MEDIA_ROOT目录的“upload_to”子目录中。
  3. 所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设你有一个ImageField字段,名叫mug_shot,那么在Django模板的HTML文件中,可以使用{{ object.mug_shot.url }}来获取该文件。其中的object用你具体的对象名称代替。
  4. 可以通过namesize属性,获取文件的名称和大小信息。

安全建议:

无论你如何保存上传的文件,一定要注意他们的内容和格式,避免安全漏洞!务必对所有的上传文件进行安全检查,确保它们不出问题!如果你不加任何检查就盲目的让任何人上传文件到你的服务器文档根目录内,比如上传了一个CGI或者PHP脚本,很可能就会被访问的用户执行,这具有致命的危害。

4. FilePathField

class FilePathField(path='', match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100, **options)

一种用来保存文件路径信息的字段。在数据表内以字符串的形式存在,默认最大长度100,可以通过max_length参数设置。

它包含有下面的一些参数:

path:必须指定的参数。表示一个系统绝对路径。path通常是个字符串,也可以是个可调用对象,比如函数。

match:可选参数,一个正则表达式,用于过滤文件名。只匹配基本文件名,不匹配路径。例如foo.*\.txt$,只匹配文件名foo23.txt,不匹配bar.txtfoo23.png

recursive:可选参数,只能是True或者False。默认为False。决定是否包含子目录,也就是是否递归的意思。

allow_files:可选参数,只能是True或者False。默认为True。决定是否应该将文件名包括在内。它和allow_folders其中,必须有一个为True。

allow_folders: 可选参数,只能是True或者False。默认为False。决定是否应该将目录名包括在内。

比如:

FilePathField(path="/home/images", match="foo.*", recursive=True)

它只匹配/home/images/foo.png,但不匹配/home/images/foo/bar.png,因为默认情况,只匹配文件名,而不管路径是怎么样的。

例子:

import os
from django.conf import settings
from django.db import models

def images_path():
    return os.path.join(settings.LOCAL_FILE_DIR, 'images')

class MyModel(models.Model):
    file = models.FilePathField(path=images_path)

5. UUIDField

数据库无法自己生成uuid,因此需要如下使用default参数:

import uuid     # Python的内置模块
from django.db import models

class MyUUIDModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # 其它字段

注意不要写成default=uuid.uuid4()


 第一章:模型层 关系类型字段 

评论总数: 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岁之间



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



没有