【django知识手册】models.py文件

我们都知道models.py文件是和数据库相关的,models.py文件里面储存的是数据,是建立网站的基石。对于如何写好models.py文件是我们学习过程中必不可少的。

目录

一、字段类型

详细请查看:https://docs.djangoproject.com/en/3.2/ref/models/fields/

AutoField, BigAutoField

自增长字段。在 settings.py 中设置:

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

BooleanField

布尔类型。为一个复选框。

BinaryField

储存二进制数据,基本数据库不存储二进制数据。

CharField

字符串,为文本输入框。必须设置 max_length 属性,然后 sqlite 数据库不会限制最大长度。所以,如果使用 sqlite 数据库,这个设置无效,但是必须要设置。

DateField

日期。有两个属性:auto_now和auto_now_add。设置auto_now=True即每次保存的时间。设置auto_now_add=True,即每次创建对象的时间。同时还可以设置为:

from django.utils import timezone

class Post(models.Model):
    publish = models.DateTimeField(default=timezone.now)

这样会显示日期表单框。

DateTimeField

同上。

DurationField

储存时间。这个只能储存datetime.timedelta类型。用于储存时间间隔。关于timedelta我也说说吧。如果觉得我说的不够详细可以查看:https://docs.python.org/3/library/datetime.html#datetime.timedelta。

这个类的参数如下:

class datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)

不过它只有三个只读属性:days,seconds,microseconds。

感觉写教程自己也学到了挺多的。好多以前django没有出的新字段还有自己以前啃文档没办法啃懂的现在就当又学了一遍一样……

DecimalField

小数,不过这个不常用。多了两个额外的参数。

max_digits

数字中允许的最大位数(有效数字位数)。请注意,这个数字必须大于或等于 decimal_places

decimal_places

与数字一起存储的小数位数(小数位数)。

EmailField

类似于CharField。不同的是它不必要设置max_length,且会验证储存的字符串是不是电子邮箱格式。

FloatField

储存浮点数。

FileField

必须要写一个参数upload_to,来指定保存文件的位置,使用方法如下:

class MyModel(models.Model):
    # file will be uploaded to MEDIA_ROOT/uploads
    upload = models.FileField(upload_to='uploads/')
    # or...
    # file will be saved to MEDIA_ROOT/uploads/2015/01/30
    upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

此时,必须要设置MEDIA_ROOT属性。

一般使用如下:

>>> f = open("../课程表.txt")
>>> from django.core.files import File
>>> myfile = File(f)
>>> from myapp.models import MyModel
>>> a = MyModel()
>>> a.upload = myfile
>>> a
<MyModel: MyModel object (None)>
>>> a.upload
<FieldFile: ../课程表.txt>
>>> a.upload.url
'%E8%AF%BE%E7%A8%8B%E8%A1%A8.txt'

一般调用它的url属性来返回图片资源的指定位置。

文件还有个写法(最新发现的):

from django.db import models

def user_directory_path(instance, filename):  # instance : 实例对象。filename:文件名
    file_name = instance.username + '_pic.' + filename.split(".")[-1]
    return 'media/user_{}/{}'.format(instance.username, file_name)

class User(models.Model):
    username = models.CharField(max_length=20, verbose_name="用户名")
    password = models.CharField(max_length=20, verbose_name="密码")
    pic = models.FileField(upload_to=user_directory_path)

    def __str__(self):
        return self.username

ImageField

和FileField差不多,就是多了两个参数,一个是height_field,还有一个是width_field。用于指定图片的宽和高。不过使用ImageField字段需要安装有pillow库。

FilePathField

注意

这个字段在终端创建的时候是不会做验证的。如果是前端传入或者后台创建是要验证的。

用来存放文件路径的字符串。有几个额外的参数:

max_length

默认不写代表100个字符。

path

必选的选项。是一个路径。

match

可选,填入一个正则表达式,筛选目录下符合正则表达式的文件名。

recursive

可选,默认是False,表示该目录下的所有子文件。如果为True则也包括该目录子文件下的子文件。

allow_files

可选,默认是True。表示寻找的符合条件的文件。其中allow_filesallow_folders必须有一个要为True

allow_folders

可选,默认是False,表示寻找符合条件的目录。其中allow_filesallow_folders必须有一个要为True

例子写法如下:

file = models.FilePathField(path=setting.BASE_DIR/"media", recursive=True)

不过使用之前要导入os库和settings文件:

import os
from django.conf import settings

GenericIPAddressField

储存ip地址。IPv4IPv6ip地址。又一个额外的参数:

protocol

储存协议的类型,有如下取值:bothIPv4IPv6

IntegerField, BigIntegerField

整数类型。

JSONField

储存JSON字段。也不常用,不过现在前后端传输数据都是JSON格式,你需要好好了解下JSON。百度吧。

这个字段需要比较新的数据库才能支持,sqlite3数据库要想支持它还需要安装扩展。如果是python3.9以上的sqlite3数据库会自带这个扩展。添加扩展的链接为:https://code.djangoproject.com/wiki/JSON1Extension。这个我照着试了下,我是64位的系统,然后装这个64位dll文件的时候发现报错,但是装32位的dll的时候就不会报错了。对了,装这个的时候,如果使用的是虚拟环境的话,虚拟环境是没有dll文件夹的,放到主环境下的dll文件夹即可。如果你不知道什么是虚拟环境,就不用考虑我刚才的话了。

这个字段可以设置JSON编码器和解码器,这个默认就很好用了。

TextField

文本类型。

URLField

url类型。

ForeignKey

用法如下:

from django.db import models

class Car(models.Model):
    manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE, related_name="car")
    # ...

class Manufacturer(models.Model):
    # ...
    pass

第一个参数是多对一关联的类,第二个参数是删除后的方式,第三个参数是反向关联。还有个参数related_query_name是查询的时候的查询字段名,如果不设置,设置了related_name,则这个参数于related_name的值一致,如果设置了related_query_name,则在进行QuerySet查找的时候要使用related_query_name,而不能使用related_name。

class Book(models.Model):
	title = models.CharField(max_length=111)
	author = models.ManyToManyField(User, related_name="one", related_query_name="two")
	# user_obj.one -> 获得book对象
	# User.objects.filter(two=book_obj) -> 根据book对象查询

OneToOneField

和ForeignKey类似。如果没有指定related_name参数,Django将时候当前模型的小写名称作为默认值。OnetoOneField能够接受所有ForeignKey的额外参数外加一个参数:

OneToOneField.parent_link

如果子类想对父类进行一对一关联的时候要把这个字段设置为True。这样在创建子类的时候会自动创建一个对应的父类。

one_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, related_name="three", parent_link=True)

如果在设置 OneToOneField 的时候添加了一个绑定,在迁移模型的时候会提示输入默认值,此时只需要设置对应的模型的主键即可进行默认值的设置和绑定。如果设置错了,migrate报错不要慌,在最近的迁移文件中将default参数设定的值改掉就可以了。

ManyToManyField

编辑models.py文件为:

from django.db import models

class People(models.Model):
    CHOICES = (("men", "男"), ("women", "女"))
    name = models.CharField(max_length=20)
    grade = models.CharField(choices=CHOICES, max_length=20)

class Hobby(models.Model):
    person = models.ManyToManyField("People", related_name="hobby")

然后两者分别创建一个对象:

>>> from myapp.models import People, Hobby
>>> a = People.objects.create(name="catfish", grade="men")
>>> b = Hobby().save()   
>>> b.person.add(a)       # 把a关联到b
>>> b.person.remove(a)     # 移除ab之间的关联
>>> b.person.set([a,])     # 批量设置关联对象,新关联的会覆盖原来关联的

要先保存再add添加,否则会报错:

ValueError: "<Article: Article object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.

设置中间表(图来源于网上):

PositiveBigIntegerField

正整数长整数字段,取值: 0 到 9223372036854775807。

PositiveIntegerField

正整数字段。取值:0到2147483647。

PositiveSmallIntegerField

正整数小整数字段,取值:0到32767。

二、自定义Field字段

自定义Field百度到的东西都太老旧了,然后只能看官网和源码了。

2.1 定义储存数据库内的字段类型(get_internal_type & db_type)

继承自Field的字段的时候给我烦的错误就是:

django.db.utils.OperationalError: table myapp_user has no column named password

这个错误的原因是需要设置数据库内的字段类型,如果不设置(或者设置错了)使用的时候就会报这个错误,Django我试了下,设置的时候不对它不会报错,但是输入错了一用就显示没有创建相关的列。

避免这个错误有两种设置方式,第一种使用 get_internal_type ,这个函数是设置为Django的字段类的名称,然后这个自定义字段就与这个字段名称设置的数据库类型相同了:

def get_internal_type(self):
    return 'IntegerField'

还有一种是用 db_type 设置数据库里面字段的sql的类型名。这个设置方式的优先级高于上面的设置方式,代码如下:

def db_type(self, connection):
    return 'smallint'

Django的设置字段的方式都可以找到,但是 sql 的设置方式怎么找呢?我翻了下源码,发现在 django.db.backends 目录下有几个数据库名称的文件夹,每个文件夹下的 base.py 文件都有个 DatabaseWrapper 类,这个类下的 data_types 里面有 Django sql 支持的几个名称。

然后其他函数还是关系还是蛮复杂的。

2.2 使用get/filter查询的时候预处理的传入的值(get_prep_value)

get_prep_value:可以预处理create/get和filter查询的值。如果不重写get_db_prep_save的父类方法(或者重写的时候调用了 super() )还可以预处理 save/create 的值。

def get_prep_value(self, value):
    return value

2.3 保存的时候预处理值(get_db_prep_save && get_db_prep_value)

每次保存的时候都会调用这个函数把值写入数据库,无论是后台表单还是终端调用都会触发这个函数:

def get_db_prep_save(self, value, *args, **kwargs):
    value = super().get_db_prep_value(value, *args, **kwargs)
    return value

get_db_prep_saveget_db_prep_value 用处差别差不多。get_db_prep_save 直接调用的 get_db_prep_value 就一行代码,所以,本差不多。但是官网建议用 get_db_prep_value

还有一个保存时候的预处理函数:pre_save,发生在 get_db_prep_value 之前先调用,这个函数用起来没有 get_db_prep_value 方便,但是这个函数可以判断当前保存操作是新增还是修改。例子如下:

def pre_save(self, model_instance, add):
    '''
			self.字段选项:获得字段选项的值   eg. self.max_length
			self.attname:这个字段的名称,是字符串
			add:是创建还是修改,创建是True,修改是False
			model_instance:模型实例
		'''
    # 默认应该返回:(可以用if来判断是否新增和创建进行修改操作)
    return getattr(model_instance, self.attname)

用gettattr可以获得这个模型对应的属性的值,还可以这样直接获得值,等效:

def pre_save(self, model_instance, add):
    return self.value_from_object(model_instance)

2.4 自定义模型表单(formfield)

默认是CharField,自定义表单如下:

def formfield(self, **kwargs):
    return super().formfield(**{
        'form_class': forms.IntegerField,
        **kwargs,
    })

同时还可以设置这个表单的一些选项,一并加入字典即可:

def formfield(self, **kwargs):
    return super().formfield(**{
        'max_digits': self.max_digits,
        'decimal_places': self.decimal_places,
        'form_class': forms.DecimalField,
        **kwargs,
    })

2.5 序列化时候的钩子函数(value_to_string)

序列化的时候会调用这个函数,返回一个字符串即可:

def value_to_string(self, obj):
    return str(self.value_from_object(obj))

2.6 字段结构(deconstruct)

这个函数在django迁移文件的时候调用。这个不太懂,个人理解,这个函数做两件事情:

  1. 将自己强制设置的原本自带的参数删除,以便提高可读性:
from django.db import models

class HandField(models.Field):

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super(HandField, self).__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(HandField, self).deconstruct()
        del kwargs["max_length"]
        return name, path, args, kwargs
  1. 自己设置了自己自定义的参数(原来参数列表中没有的参数),则需要写代码将这个值自动添加到kwargs中:
from django.db import models

class CommaSepField(models.Field):
    "Implements comma-separated storage of lists"

    def __init__(self, separator=",", *args, **kwargs):
        self.separator = separator
        super(CommaSepField, self).__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(CommaSepField, self).deconstruct()
        # Only include kwarg if it's not the default
        if self.separator != ",":
            kwargs['separator'] = self.separator
        return name, path, args, kwargs

2.7 检查参数的函数(check)

这个函数和验证器不同,验证器是验证后台表单输入的值。这个检查参数的函数是用来验证程序员编写字段的选项是否写的正确。函数名称自定义,不过还是建议跟着django源码的名称保持一致(_check_xxx):

from django.core import checks

def _check_list_length(self):
    if self.max_length > 5:
        return [checks.Error("列表长度不能大于5!")]
    else:
        return []

然后在 check 函数中注册:

def check(self, **kwargs):
    return [
        *super().check(**kwargs),
        *self._check_list_length(),   # 注册
    ]

即可。

2.8 QuerySet调用时候显示的值(from_db_value)

调用 from_db_value 方法,这个方法是每次QuerySet查询值的时候,利用实例索引属性的时候返回的值,可以用来修改返回值的类型。代码如下:

def from_db_value(self, value, *args, **kwargs):
    return eval(value)

2.9 自定义Field字段与后台表单(to_python & clean)

后台表单输入值后提交的时候有一个预处理函数:to_python 。还有个后台表单的验证函数 clean ,可以在这个函数中进行后台表单的自定义验证操作。clean 函数内的 valueto_python 预处理后的值。代码如下:

def clean(self, value, model_instance):
    if isinstance(value, list):
        return value
    else:
        raise ValidationError("传入的值必须要是一个列表!")

def to_python(self, value):
	return eval(value)

2.10 给自定义字段添加自定义验证器的两种方式(validators)

  1. 直接在__init__上添加:
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.validators.append(MyValidator)
  1. 在validators函数中添加:
from django.utils.functional import cached_property

@cached_property
def validators(self):
    return super().validators + [
        MyValidator,
    ]

三、字段选项

注:标有【*】的选项表示之能在后台提交表单的时候起作用,在其他情况下不起作用。

null

数据库中该值是否能为空值,默认为false。

blank【*】

表单验证中是否为空值,默认为false。

choices

枚举类型,小括号内第一个为机器可读语言,第二个为人类可读语言。使用如下:

from django.db import models

class People(models.Model):
    CHOICES = (("men", "男"), ("women", "女"))
    name = models.CharField(max_length=20)
    grade = models.CharField(choices=CHOICES, max_length=20)

Django 3.2 新增用法:

from django.utils.translation import gettext_lazy as _

class Student(models.Model):

    class YearInSchool(models.TextChoices):
        FRESHMAN = 'FR', _('Freshman')
        SOPHOMORE = 'SO', _('Sophomore')
        JUNIOR = 'JR', _('Junior')
        SENIOR = 'SR', _('Senior')
        GRADUATE = 'GR', _('Graduate')

    year_in_school = models.CharField(
        max_length=2,
        choices=YearInSchool.choices,
        default=YearInSchool.FRESHMAN,
    )

    def is_upperclass(self):
        return self.year_in_school in {
            self.YearInSchool.JUNIOR,
            self.YearInSchool.SENIOR,
        }

定义为一个二元元组,元组第一个是机器可读,第二个是人类可读。机器可读不能重复,人类可读支持国际化。这个类似与枚举的类有几个属性可以用:

self.YearInSchool.choices  # (机器可读,人类可读) 二元元组列表
self.YearInSchool.labels   # 人类可读列表
self.YearInSchool.values   # 机器可读列表
self.YearInSchool.names    # 查看变量名列表

获得类中单个对象:

 YearInSchool.SENIOR   # 自变量名
 YearInSchool['SENIOR'] # 自变量名
 YearInSchool('SR') # 机器可读名

假设单个对象用 obj 表示,这个 obj 又有一系列方法:

obj.value   # 机器可读
obj.name    # 变量名
obj.label   # 人类可读

这个枚举类如果值是整数也会转换为字符串。如果想要枚举类型是整数,可以使用 IntegerChoices 类:

class Card(models.Model):

    class Suit(models.IntegerChoices):
        DIAMOND = 1, 'diamond'
        SPADE = 2, 'spade'
        HEART = 3, 'heart'
        CLUB = 4, 'club'

    suit = models.IntegerField(choices=Suit.choices)

如果是日期类型:

class MoonLandings(datetime.date, models.Choices):
    APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)'
    APOLLO_12 = 1969, 11, 19, 'Apollo 12 (Intrepid)'
    APOLLO_14 = 1971, 2, 5, 'Apollo 14 (Antares)'
    APOLLO_15 = 1971, 7, 30, 'Apollo 15 (Falcon)'
    APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)'
    APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'

如果想设置机器可读为 None 的时候返回人类可读,可以这样设置:

class Answer(models.IntegerChoices):
    NO = 0, _('No')
    YES = 1, _('Yes')

    __empty__ = _('(Unknown)')

还有一种设置枚举类型的简便方法,字符串的设置方法的使用设计的我不太喜欢用,所以,只记录整数的使用方法:

num = models.IntegeField(choices=models.IntegerChoices('', 'a b c d').choices)

其实这个整数的设置方法也不太好,毕竟第一个参数虽然说是设置的类名但是觉得并没有什么卵用,所以,写一个空字符串占位置即可。

# 输出:[(1, 'A'), (2, 'B'), (3, 'C'), (4, 'D')]
models.IntegerChoices('', 'a b c d').choices

db_tablespace

设置表空间。

db_column

数据库内的列名。

db_index

如果为 True ,数据库将会创建这个字段的索引。

default

设置默认值。

editable【*】

如果是 False ,则在后台不能查看和编辑这个字段。

error_messages【*】

自定义错误信息。null, blank, invalid, invalid_choice, unique和unique_for_date。这六个是所有Field都包含的,如果想看各自Field有哪些自定义错误,可以查看源码类下的 default_error_messages 属性。

help_text【*】

后台表单的提示文本,支持html代码。

primary_key

主键。设置primary_key=True来指定主键,如果不指定django默认有个id为主键。

unique

是否是唯一的,默认False。

unique_for_date【*】

数据中不存在该字段与指定的日期字段同时两同的两个数据。这个约束只能约束后台表单,如果不是后台表单,则不起效果。

unique_for_month, unique_for_year【*】

同上。换成月和年。

verbose_name【*】

汉化后台。

validators【*】

函数验证器

用于添加自定义验证,先定义一个自定义验证的函数:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(_('%(value)s is not an even number'),
            params={'value': value},
        )

使用方法如下:

from django.db import models

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

注意:这个验证器只能约束后台的表单,其他地方都能约束。

类验证器

我还发现了一件事,这个验证器只能写函数名,也就是说,使用函数验证器不能传参,但是使用类验证器则可以,代码如下:

class MyValidator:
    def __init__(self, regex, message="与所验证的不匹配!"):
        self.regex = regex
        self.message = message

    def __call__(self, value):
        import re
        if not re.compile(self.regex).search(str(value)):
            raise ValidationError(self.message)

使用:

username = models.CharField(max_length=8888, validators=[MyValidator(regex=".{5}"),])

这样就可以传参手搓一个正则表达式的验证器了。但是Django有自带的正则表达式验证器。用法和我这个手搓的差不多:

from django.core.validators import RegexValidator

class User(model.Model):
	username = models.CharField(max_length=8888, validators=[RegexValidator(regex=".{3}", message="式只能是三位以上!"),])
    ...

参数:

  • regex:正则表达式。
  • message:报错的提示信息。

自己的手搓的验证器进行makemigration时候会报错:

ValueError: Cannot serialize: <myapp.models.MyValidator object at 0x04190B80>
There are some values Django cannot serialize into migration files.
For more, see https://docs.djangoproject.com/en/3.2/topics/migrations/#migration-serializing

解决:(序列化问题需要使用deconstructible装饰器解构)

from django.utils.deconstruct import deconstructible

@deconstructible
class MyValidator:
	...

使用BaseValidator验证器

引入BaseValidator可以让我们代码少写或者更简洁。自己手搓了个代码,功能就是当max_length大于0的时候报错(测试下,虽然很没用),代码如下:

from django.core.validators import BaseValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _

@deconstructible
class MyValidator(BaseValidator):
	message = _("it happens error in your code.your value is %(value)s")
	code = 'max_length'

	def compare(self, value, arg):
		return arg > 0

	def clean(self, value):
		return value

里面有几个值得注意的地方:

  1. message:里面可以写参数。
    • %(limit_value)s:code指定参数传入的值,如code指定的是max_length,则这个值就是max_length传入的值。
    • %(show_value)s:clean处理后的表单传入的值。
    • %(value)s:clean处理前表单传入的值。
  2. code:指定名字。
  3. clean:用于预处理数据。
  4. compare:第一个参数是clean处理后的值,第二参数是code指定的参数传入的值。当compare为True的时候引发异常。

所以,只用指定属性名字和关心下compare返回为True的条件就可以了。

四、Meta选项

abstract

设置为True表示抽象基类。抽象基类迁移的时候不创建表,可以被继承。

app_label

这个选型只在一种情况下使用,就是你的模型不在默认的应用程序包下的models.py文件中,这时候需要指定你这个模型是哪个应用程序的。

比如:你的项目有两个app文件夹,一个叫 myapp ,另一个叫 testapp ,如果你在 testapp.models 文件中定义一个模型,它的Meta子类中写 app_label="myapp" ,那么,这个模型就是 myapp 的模型,而不是testapp 的模型。

base_manager_name

设置默认管理器的名称。模型可以通过: Article._base_manager 来获基础管理器( Article 是模型类名),一般来说默认管理器是 objects (未重写的管理器),即等效于 Article.objects 。当模型中设置了其他管理器,则 objects 就不存在了,但是 Article._base_manager 指向的还是空的未重写的管理器。可以通过这个属性来指定 Article._base_manager 的管理器。代码如下:

# Meta 类内:
base_manager_name = 'nerd'  # 前提是要有nerd这个管理器

这个使用感觉用处不大,但是如果在类视图里面只设置model属性而不设置queryset属性时可能会有用。

default_manager_name

Article._default_manager 来获得默认管理器,一般来说第一个赋值的管理器是默认管理器。类视图里面的queryset指定的是 _default_manager 。也可以通过手动指定Meta这个选项来指定管理器。

db_table

修改数据库的表名,默认为<app_name>_<model_name>

db_tablespace

设置表空间,如果不设置就是项目 settings.py 内的 DEFAULT_TABLESPACE 。默认 DEFAULT_TABLESPACE 为空字符串。如果数据库不支持,那么这个选项会被忽略。

到底设计多少个表空间合理,没有统一的说法,这主要根据企业的实际需求去判断。如企业需要对用户进行磁盘限额控制的,则就需要根据用户的数量来设置表空间。当企业的数据容量比较大,而其又对数据库的性能有比较高的要求时,就需要根据不同类型的数据,设置不同的表空间,以提高其输入输出性能。

另外要注意,不同的表空间有不同的权限控制。用户对于表空间A具有完全控制权限,可能对于表空间B就只有查询权限,甚至连连接的权限的都没有。所以,合理为用户配置表空间的访问权限,也是提高数据库安全性的一个方法。

数据库支持:(来自于:https://www.osgeo.cn/django/topics/db/tablespaces.html)

PostgreSQL和Oracle支持表空间。SQLite、MariaDB和MySQL没有。

当使用不支持表空间的后端时,Django会忽略所有与表空间相关的选项。

这个名字会默认被用于一个关联对象到当前对象的关系。默认为 <model_name>_set
由于一个字段的反转名称应该是唯一的,当你给你的模型设计子类时,要格外小心。为了规避名称冲突,名称的一部分应该含有 '%(app_label)s''%(model_name)s' ,它们会被应用标签的名称和模型的名称替换,二者都是小写的。代码如下:

# Meta 元类下:
default_related_name = '%(app_label)s_%(model_name)s_nerd'

app_labelmodel_name 单词写错了会报错。

get_lastest_by

在model中指定一个 DateField 或者 DateTimeField 。这个设置让你在使用 modelManager 上的lastest 方法时,默认使用指定字段来排序。

indexes

索引是 B-Trees / B +树 为索引方式。

给常用的查询字段加个索引能够增加数据库查询的效率。代码如下:

indexes = [
    models.Index(fields=['last_name', '-first_name']),
    models.Index(fields=['first_name'], name='first_name_idx'),
]
  • fields 指定索引的排列,默认为升序排列,如果想变成降序排列加上 - 。(如第一行的 first_name ),MySQL不支持索引排序,这种情况下会将降序索引变成一个普通的索引。这个是索引排序,并不是数据库里面排序。如果是数据库里面的排序请用 ordering

  • name 如果不指定 django 会自动默认生成一个 name 。为了兼容不同的数据库,索引名称不能超过 30 个字符,并且不应以数字 (0-9) 或下划线 (_) 开头。

以上是基本使用,然后 Django3.2 新增的用法:

class Index(*expressions, fields=(), name=None, db_tablespace=None, opclasses=(), condition=None, include=None)
  • expression 表示可以写表达式(数据库函数,F和Q都可以),或者多个表达式都可以,但是用这个参数必须要指定 name 。代码如下:
Index(Lower('title').desc(), 'pub_date', name='lower_title_date_idx')

注意:expression中必须是已有字段,且不能加连接符变成降序,如果要变成降序需要结束 .desc() 方法。

注意:field和expression只能同时使用一种,不然会报错。

  • db_tablespace 表示表空间。如果索引这个字段没有声明 db_tablespace ,这个字段将被设置为这个表空间。如果索引的字段没有设置 db_tablespace ,索引中也没有设置 db_tablespace 将会使用 Meta 中的 db_tablespace 。(MySQL和Sqlite3都不支持Django的表空间)

个人理解

简单的说:Field的db_tablespace优先级 > 索引的db_tablespace > Meta里面的db_tablespace。

剩下的参数基本 MySQL 不支持,相反 Django 倒是很偏爱 PostgreSQL ,可能这个数据库在国外比较火吧。国内火的还是 MySQL

managed

默认为True,意思是Django在migrate命令中创建合适的数据表,并且会在 flush 管理命令中移除它们。换句话说,Django会管理这些数据表的生命周期。如果是False,Django 就不会为当前模型创建和删除数据表。对于带有managed=False的模型的测试,你要确保在测试启动时建立正确的表。

order_with_respect_to

通常用于指定外键。使这个字段被索引的时候排序返回。代码如下:

from django.db import models

class Question(models.Model):
    text = models.TextField()
    # ...

class Answer(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="answer")
    # ...

    class Meta:
        order_with_respect_to = 'question'

当反向索引时候,可以用 get_RELETED_order 方法获得排序的主键值:

>>> question = Question.objects.get(id=1)
>>> question.get_answer_order()
[1, 2, 3]

可以用 set_RELETED_order 方法来设置排序的值:

>>> question.set_answer_order([3, 1, 2])

还可以用 get_next_in_order 方法获得下一个值或者用 get_previous_in_order 方法获得上一个值。

>>> answer = Answer.objects.get(id=2)
>>> answer.get_next_in_order()
<Answer: 3>
>>> answer.get_previous_in_order()
<Answer: 1>

这个选项不能和 ordering 同时使用。

permission

传入一个二元元组列表,二元元组第一个是额外权限的名字(机器可读),第二个是对该权限的解释(人类可读)。代码如下:

permissions = [('can_deliver_pizzas', 'Can deliver pizzas')]

django默认有四个权限:查看(view)、创建(add)、更改(change)、删除(delete) 。

4.1.1 默认权限名

权限名一般有app名(app_label),权限动作和模型名组成。以blog应用为例,Django为Article模型自动创建的4个可选权限名分别为:

  • 查看文章(view): blog.view_article

  • 创建文章(add): blog.add_article

  • 更改文章(change): blog.change_article

  • 删除文章(delete): blog.delete_article

4.1.2 判断一个对象是否有权限

代码如下:

obj.has_prem('blog.add_article')

如果有返回True,没有返回False。

4.1.3 查看用户所在组的权限

代码如下:

obj.get_group_permissions()

4.1.4查看某个用户的所有权限

代码如下:

obj.get_all_permission()

4.1.5 新增权限

有两种方式,第一种方式是使用Meta的permission选项,这个前面有。第二种是使用万能外键ContentType创建:

from blog.models import Article
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
 
content_type = ContentType.objects.get_for_model(article)
permission1 = Permission.objects.create(
    codename='publish_article',
    name='Can publish articles',
    content_type=content_type,
)
 
permission2 = Permission.objects.create(
    codename='comment_article',
    name='Can comment articles',
    content_type=content_type,
)

4.1.6 通过代码来添加删除权限

代码如下:

# 给用户添加权限
myuser.user_permissions.add(permission1, permission2, ...)
# 给组添加权限
mygroup.permissions.add(permission1, permission2, ...)
# 给用户或组删除权限
myuser.user_permissions.remove(permission, permission, ...)
# 给用户和组清除所有权限
myuser.user_permissions.clear()

4.1.7 权限也有缓存机制

通过修改用户的权限后要重新获取用户一次,不然的话还是会显示修改前的权限。

4.1.8 绑定权限

4.1.8.1 FBV视图验证

代码如下:

from django.contrib.auth.decorators import permission_required
 
@permission_required('polls.can_vote')
def my_view(request):
    ...
4.1.8.2 CBV视图验证

需要继承PermissionRequiredMixin这个类:

from django.contrib.auth.mixins import PermissionRequiredMixin
 
class MyView(PermissionRequiredMixin, View):
    permission_required = 'polls.can_vote'
    # Or multiple of permissions:
    permission_required = ('polls.can_open', 'polls.can_edit')
4.1.8.3 模板中验证

在模板中验证用户权限主要需要学会使用perms这个全局变量。perms对当前用户的user.has_module_perms和user.has_perm方法进行了封装。当我们需要判断当前用户是否拥有blog应用下的所有权限时,我们可以使用:

{{ perms.blog }}

我们如果判断当前用户是否拥有blog应用下发表文章讨论的权限,则使用:

{{ perms.blog.comment_article }}

这样结合template的if标签,我们可以通过判断当前用户所具有的权限,显示不同的内容了.

{% if blog.article %}
    <p>You have permission to do something in this blog app.</p>
    {% if perms.blog.add_article %}
        <p>You can add articles.</p>
    {% endif %}
	{% if perms.blog.comment_article %}
        <p>You can comment articles!</p>
   {% endif %}
{% else %}
    <p>You don't have permission to do anything in the blog app.</p>
{% endif %}

4.1.9 用户组

使用用户组是为了能批量给用户设置权限,降低工作量。将用户添加到用户组或者给用户组(group)添加权限,一般建议直接通过django admin进行。如果你希望手动给group添加或删除权限,你可以使用如下方法。

mygroup.permissions = [permission_list]
mygroup.permissions.add(permission, permission, ...)
mygroup.permissions.remove(permission, permission, ...)
mygroup.permissions.clear()

如果你要将某个用户移除某个用户组,可以使用如下方法。

myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()

default_permissions

默认权限。默认是:('add', 'change', 'delete', ’view')。

require_db_features

声明模型依赖的数据库功能。比如['gis_enabled'],表示模型的建立依赖GIS功能。

require_db_vendor

此模型特定于的受支持数据库供应商的名称。当前的内置供应商名称是:sqlitepostgresqlmysqloracle。如果此属性不为空且当前连接供应商不匹配,则不会同步模型。

select_on_save

确定是否只用 django 1.6 之前的save算法。旧的算法是使用SELECT sql语句,新算法是使用 UPDATE 查询语句,只用设置False就好,默认也是False。

verbose_name

汉化表名。

verbose_name_plural

汉化表名。

proxy

设置proxy=True为代理模型。代理模型继承于一个模型,相当于给模型起了个别名,不会生成数据库,也不能添加字段,但是可以修改默认管理器或者添加新的操作模型的方法。

代理模型只能继承一个非抽象模型类,不能是多个。

ordering

排序规则

unique_together(将被废除)

联合约束。代码如下:

# Meta 元类中
unique_together = (('username', 'password'),)

表示数据库内 usernamepassword 字段不能够同时都相同。

constraints

传入对模型字段的约束。代码如下:

from django.db import models

class Customer(models.Model):
    age = models.IntegerField()

    class Meta:
        constraints = [
            models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'),
        ]

这个约束对后台和代码都有效,不符合会引发异常。

五、自定义的模型方法

就是自己写一个带有返回值的模型方法,还可以使用修饰器。

@property

将一个方法伪装成属性,被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式,实现一个实例属性的get,set,delete三种方法的内部逻辑。

代码如下:

@property   # 不能用于filter查找
def hello(self):
    return self.hello

@hello.setter
def hello(self, value):
    self.hello = value

@hello.deleter
def hello(self):    # del obj.A 
    print("删除属性的时候调用!")

@abstractmethod

用于程序接口的控制,正如上面的特性,含有@abstractmethod修饰的父类不能实例化,但是继承的子类必须实现@abstractmethod装饰的方法。

from abc import abstractmethod

@abstractmethod
def get_absolute_url(self):
    pass

使用这个装饰器记得设置 abstract=True

六、内置可调用的模型方法

delete

obj.delete()

save

obj.save()

七、重写的模型方法

clean【*】

只能用于验证后台的表单,不能用于验证QuerySet代码。

from django.core.exceptions import ValidationError

def clean(self):  # 在模型类中 ... 
    if self.comment_type == 0 and len(self.content)== 0 :
        raise ValidationError(u'评论不能为空')

__str__【*】

对象名字。

get_absolute_url【*】

def get_absolute_url(self):
    return "/people/%i/" % self.id

一般与reverse方法一起使用:

def get_absolute_url(self):
    from django.urls import reverse
    return reverse('people.views.details', args=[str(self.id)])

save

save抛出异常会给程序抛出异常,clean抛出异常只是验证表单失败。

def save(self, *args, **kwargs):
    # do_something()
    super().save(*args, **kwargs)  # Call the "real" save() method.
    # do_something_else()

八、继承

  • 继承的子类不能有和非抽象基类的父类同名的字段。如果父类是抽象基类,则可以有同名的字段。但是可以通过 field_name=None 来删除模型。
field_name = None
  • Meta元类继承默认指定,如果多继承模型表单则需要指定Meta元类继承。

九、模型包

可以删掉models.py文件然后创建一个包,名称叫models的文件夹,文件夹下面要有 __init__.py 文件,然后在 __init__.py 中导入即可:

from .organic import Person
from .synthetic import Robot

十、管理器

10.1 自定义管理器方法

class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book

class Book(models.Model):
    title = models.CharField(max_length=100)
    objects = BookManager()

book = Book.objects.create_book("Pride and Prejudice")

可以不反悔queryset。可以通过 self.model 来获得模型。

10.2 重写get_queryset方法

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super(PublishedManager, self).get_queryset().filter(status='published')
    
class Article(models.Model):
    objects = PublishedManager()

10.3 _default_manager和_base_manager

Meta 中有介绍。

末、参考文章

django官方文档:https://docs.djangoproject.com/en/3.2/

django权限管理和用户组https://blog.csdn.net/weixin_42134789/article/details/84567337

posted @ 2021-12-24 15:42  小默同学  阅读(424)  评论(0编辑  收藏  举报