Django model总结(上)

Django model是django框架中处于比较核心的一个部位,准备分三个博客从不同的方面分别进行阐述,本文为《上篇》,主要对【a】Model的基本流程,比如它的创建,迁移等;默认行为,以及用定制的行为来覆盖默认的行为;迁移文件相关的操作,比如不同的创建迁移文件的方法,对迁移文件进行重命名,将多个迁移文件压缩成一个迁移文件,迁移文件中的变量的含义,以及迁移文件的回滚【b】数据类型,不同model之间的关系,以及对model的事务管理【c】一些用于减轻数据库与model之间交互作业的工具,比如用fixture文件对model数据进行备份和加载【d】支持软件observer pattern的signal【e】在默认位置以外的配置以及对多种数据库的配置及其在model中的应用。

Django model与迁移流程

Django的主要存储技术是利用关系型数据库,所以Django的model对应的是数据库中的table,该表的表名默认是<app_name>_<model_name>,而model的实例则是该表的一个记录。因为跟数据打交道,所以model经常需要不断的更改,而管理这样的变动是通过迁移文件实现的。

创建Django model

Django model是在每个app中的models.py中,这个models.py文件是当app创建时候就自动创建的。

#demo
from django.db import models
from datetime import date,timedelta
from django.utils import timezone 
# Create your models here.
class Stores(models.Model):
    name=models.CharField(max_length=30,unique_for_date='date_lastupdate',db_column='my_custom_name')
    #id=models.AutoField(primary_key=True) 
    #objects=models.Manage()
    address=models.CharField(max_length=30,unique=True)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    email=models.EmailField(help_text='valide email please')
    date=models.DateField(default=date.today)
    datetime=models.DateTimeField(default=timezone.now)
    date_lastupdate=models.DateField(auto_now=True)
    date_added=models.DateField(auto_now_add=True)
    timestamp_lastupdated=models.DateTimeField(auto_now=True)
    timestamp_added=models.DateTimeField(auto_now_add=True)
    testeditable=models.CharField(max_length=3,editable=False)
    def __str__(self):
        return self.name 

以上代码就创建了一个model,其中被注释掉的id的AutoField被默认自动添加;被注释掉的objects是model的Manager,负责该Model的增删查改;还包含了很多字段如CharField,DateField等;__str__方法方便model实例的显示;以上具体内容将在下面各个位置进行总结。

最后应注意该app必须被包含在project的settings.py的INSTALLED_APPS列表中。

迁移及基本流程

Django model 数据类型

Django model中的数据类型与在两个层面有关:底层数据库层面Django/Python层面

当创建model后,并进行首次迁移,Django产生并执行DDL(Data definition language)来创建数据库表,这个DDL包含了model中各字段的定义,比如Django model的IntegerField被转换为底层数据库的INTEGER,这就意味着如果后面要修改该字段的类型,就必须再次进行迁移操作。

model的数据类型也有在Django/Python层面进行操作的,比如

ITEM_SIZES=(
    ('S','Small'),
    ('M','Medium'),
    ('L','Large'),
    ('P','Portion')
)
class Menu(models.Model):
    name=models.CharField(max_length=30)
    def __str__(self):
        return self.name 
class Item(models.Model):
    menu=models.ForeignKey(Menu,on_delete=models.CASCADE,related_name='menus')
    name=models.CharField(max_length=30)
    description=models.CharField(max_length=100)
    size=models.CharField(choices=ITEM_SIZES,max_length=1)
    colories=models.IntegerField(validators=[calorie_watcher])
    def __str__(self):
        return self.name 

这里,Item的size中有choices,这个限制就是在python层面进行限制的,我们可以通过python manage.py banners 0001来查看(banner 是app名字,0001是首次迁移的前缀):

(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0001
BEGIN;
--
-- Create model Item
--
CREATE TABLE "banners_item" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "description" varchar(100) NOT NULL, "size" varchar(1) NOT NULL, "menu_id" integer NOT NULL REFERENCES "banners_menu" ("id") DEFERRABLE INITIALLY DEFERRED);
COMMIT;

可以发现底层SQL中关于size并没有choice的限制,也就是该限制是在python层面中实施的,即如果后续更改choices,可以不再进行数据迁移。

Data type Django model Data type Django model
Binary BinaryField Boolean BooleanField
Date/time DateField Date/time TimeField
Date/time DurationField Number AutoField
Number BigInteger Number DecimalField
Number FloatField Number IntegerField
Number PositiveIntegerField Number PositiveSmallIntegerField
Text CharField Text TextField
Text CommaSeparatedIntegerField Text EmailField
Text FileField Text FilePathField
Text(specialized) ImageField Text(specialized) GenericIPAddress
Text(specialized) SlugField Text(specialized) URLField
限值:max_length,min_value,max_value,max_digits,decimal_places

对于text-based的字段,max_length限定了最大字符数量;对于IntegerField数据类型,Django提供了min_value来限制其最小值,max_value限制其最大值;对于DecimalField,需要标明max_digitsdecimal_places来限制数值的最多的位数和有效值。

Empty,Null,Not Null: Blank 和Null

所有字段默认都是数据库级别的NOT NULL限制(即默认null=False),即如果创建/更新一个NOT NULL字段,必须为该字段提供值,否则数据库拒绝接受该字段;而有时候,允许一个空字段是必要的,因为数据库中可以识别NULL,因此为了允许在数据库层面支持NULL值,可通过null=True进行设置。

除了 null,Django还支持blank选项,该选项默认为False,被用在python/Django层面的校验通过在model上的Form。如果一个字段被声明为blank=True,Django允许在form中留空,否则Django拒绝接受该值。

例如:

class Person(models.Model):
    first_name=models.CharField(max_length=30)
    middle_name=models.CharField(max_length=30)
    last_name=models.CharField(max_length=30)
blank和null的组合 默认(blank=False,null=False) null=True blank=True null=True,blank=True,
Person.objects.create(first_name='john',middle_name=None,last_name='Batch') None被处理为NULL,数据库拒绝 数据库层面可以为NULL None被处理为NULL,数据库拒绝 数据库层面可以为NULL,OK
Person.objects.create(first_name='john',last_name='Batch') 未标明的被当作空字符,数据库层面不拒绝,空字符不是NULL 数据库层面OK 数据库层面OK 数据库层面OK
Person.objects.create(first_name='john',middle_name=‘ ’,last_name='Batch') 数据库层面OK 数据库层面OK 数据库层面OK 数据库层面OK
Form Validation validation error,因为blank=False validation error,因为blank=False 校验成功 校验成功

对于blank,null的参数有四种组合,分别是default(blank=False,null=False),blank=True(null=False),null=True(blank=False),blank=True& null=True,而'middle_name'有三种选择,分别是赋值为None,'',以及不赋值(使用默认值,等同于赋值为''),所有一共12种情况(如上表所示),但不管哪种情况,只要null=False,赋值为None在数据库层面不通过,只要blank=False,在form校验时就不通过。

预设定值(predetermined values):default,auto_now,auto_now_add,choices

可使用default对model字段进行默认值设定,该defaut参数既可以是一个值,也可以是方法的引用(method reference),该选项是完全由python/Django层面控制的。

尽管default选项对于text 字段,number字段以及boolean字段都是一样的操作,但对于DateFieldDatetimeField来讲,它们有自己独特的默认值设定选项,该选项为auto_nowauto_now_add

在#demo中,datedatetime字段均用default使用了method reference(注意到datetime的default为timezone.now,date的default用了date.today,这里都使用method reference,因为如果带上(),其实也没有错,但是将在编译阶段就调用了函数,从而得到一个值,不能动态的返回当前计算值)。

对于auto_nowauto_now_add,这两个选项与default类似的地方是都自动为字段提供了默认值,,但也有不同,不同之处在于:(1)仅对字段DateField,DateTimeField有效,对于DateFieldauto_nowauto_now_add通过date.today来产生值,对于DateTimeField,auto_nowauto_now_add是通过django.utils.timezone.now来产生值(2)auto_nowauto_now_add不能被覆盖,auto_now选项每当一个record被改变,这个值就会改变,而auto_now_add选项在record存在期间保持不变,这就意味着我们可以用auto_now来追踪record的最近的一次修改,用auto_now_add来追踪record的创建时间(3)需要注意的是,在admin中,auto_nowauto_now_add不显示,因为它们会被自动取值,且不能被覆盖,所有就不显示出来。

另一个预设值是choices,它是在python/Django层面控制的。

Unique:unique,unique_for_date,unique_for_month,unique_for_year

比如name=models.CharField(max_length=30,unique=True),它告诉了Django来确保所有记录都有一个唯一的name

unique是在数据库层执行的(通过DDL的UNIQUESQL常量),还有Pyhton/Django层面,另外除了ManyToMany,OneToOne,FileField,对于其他的字段都适用。

对于伴随着唯一时间的字段,比如name=models.CharField(max_length=30,unique_for_date='date_lastupdated'),不允许同一个name有相同的date_lasupdated字段,而这个validation由于其复杂性,在python/Django层面进行控制。

表单值:Editable,help_text,verbose_name,error_messages

当form的字段由models字段支撑时,form字段可能被models字段的选项所影响。

默认地,所有models字段都是可编辑的,但是可通过editable=False来告诉Django来忽略它,然后任何与它相关的validation也会被忽略。

例如:将#demo中的city改为city=models.CharField(max_length=30,editable=False),经过makemigrations后,再sqlmigrate,查看

BEGIN;
--
-- Alter field city on store
--
CREATE TABLE "new__customized store" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "city" varchar(30) NOT NULL, "name" varchar(30) NOT NULL, "address" varchar(30) NOT NULL UNIQUE, "state" varchar(2) NOT NULL, "email" varchar(254) NOT NULL, "date" date NOT NULL, "datetime" datetime NOT NULL, "date_lastupdated" date NOT NULL, "date_added" date NOT NULL, "timestamp_lastupdated" datetime NOT NULL, "timestamp_added" datetime NOT NULL);
INSERT INTO "new__customized store" ("id", "name", "address", "state", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "city") SELECT "id", "name", "address", "state", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "city" FROM "customized store";
DROP TABLE "customized store";
ALTER TABLE "new__customized store" RENAME TO "customized store";
COMMIT;

可以发现(1)修改字段是通过新建一个表,将record移植后,删除旧表,再重新更改新表为旧表名字(2)由于设置了city为editable=False,DDL中未出现city,也说明该操作在数据库层面进行的(3)migrate后,查看admin,发现city确实不见了。

之前:

之后:

help_text在旁边给出了关于该字段的解释信息,verbose_name选项使得在表单中输出为该字段的label,error_messages可接受字典,该字典的keys代表错误代码,values是错误信息。

DDL值:db_column,db_index,db_tablespace,primary_key

默认地,Django用model的字段名作为表的column的名字,如果要改变,可以在字段里使用db_column选项,另一个就是db_index选项,但应注意以下两种情况不需要再设定db_index:

  • unique=True
  • ForeignKey

比如:修改#demo为

city=models.CharField(max_length=30,db_index=True)
state=models.CharField(max_length=2,db_column='customized state JOHNYANG')

经过sqlmigrate:

CREATE TABLE "new__customized store" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "address" varchar(30) NOT NULL UNIQUE, "city" varchar(30) NOT NULL, "email" varchar(254) NOT NULL, "date" date NOT NULL, "datetime" datetime NOT NULL, "date_lastupdated" date NOT NULL, "date_added" date NOT NULL, "timestamp_lastupdated" datetime NOT NULL, "timestamp_added" datetime NOT NULL, "customized state JOHNYANG" varchar(2) NOT NULL);
INSERT INTO "new__customized store" ("id", "name", "address", "city", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "customized state JOHNYANG") SELECT "id", "name", "address", "city", "email", "date", "datetime", "date_lastupdated", "date_added", "timestamp_lastupdated", "timestamp_added", "state" FROM "customized store";  
DROP TABLE "customized store";
ALTER TABLE "new__customized store" RENAME TO "customized store";
CREATE INDEX "customized store_city_5ed81ba0" ON "customized store" ("city");
COMMIT;

可以发现state字段消失,增加了customized state JOHNYSNG,和最后CREATE INDEX "customized store_city_5ed81ba0" ON "customized store" ("city")

最后,primary_key选项允许对Model定义一个主键,如果没有一个字段定义primary_key=True,则自动创建一个名为idAutoField字段来充当主键(系统自动执行id=models.AutoField(primary_key=True).

内置和自定义校核器:validators

除了数据类型,长度限制,预设值等之前的选项的约束限制,Django还提供了validator选项(赋值为列表),来允许进行更复杂,高级的逻辑校核。

django.core.validators包中提供了一系列内置的校核方法。比如models.EmailField数据类型依赖django.core.validators.EmailValidator来校核email值,而models.IntegerField使用MinValueValidatorMaxValueValidator来对min_valuemax_value来进行校核。

除了内置validator方法,也可以自定义validator方法,定义该方法很简单,只需要接受一个字段为输入参数,然后如果不满足某个条件就抛出django.core.exceptions.ValidatorError就可以。

ITEM_SIZE=(
    ('S','Small'),
    ('M','Medium'),
    ('L','Large'),
    ('P','Portion')
)
from django.core.exceptions import ValidationError
def calorie_watcher(value):
    if value>5000:
        raise ValidationError(('calories are %(value)s? try something less than 5000'),params={'value':value})
    if value<0:
        raise ValidationError(('Strange calories are %(value)s'),params={'value':value})

class Menu(models.Model):
    name=models.CharField(max_length=30)
    def __str__(self):
        return self.name 

class Item(models.Model):
    menu=models.ForeignKey(Menu,on_delete=models.CASCADE)
    name=models.CharField(max_length=30)
    description=models.CharField(max_length=100)
    price=models.FloatField(blank=True,null=True)
    size=models.CharField(choices=ITEM_SIZE,max_length=1)
    calories=models.IntegerField(validators=[calorie_watcher])
    def __str__(self):
        return 'Menu %s name %s' %(self.menu,self.name )
        

在admin中(需要注意的是如果要在admin中管理数据库,需要在admin.py对需要管理的表进行注册,如

from django.contrib import admin

# Register your models here.
from .models import Question,Choice,Store,Menu,Item
admin.site.register((Question,Choice,Store,Menu,Item))

)对Item进行添加,如果calorie小于0,可以看到报错信息:

Django model默认和定制的行为

Django对Model提供了很多函数,比如基本操作save(),delete(),还有命名惯例以及查询行为等。

Model方法

所有model都继承了一系列方法,如保存,删除,校核,加载,还有对model 数据施加特定的逻辑等。

save()

save()方法是最常见的一个操作,对一个记录进行保存。一旦创建或者获取了model instance的reference,则可以调用save()来进行创建/更新 该instance。

那么django是如何判定一个save()到底是该创建还是更新一个instance?

它是通过id来进行判断的,如果reference的id在数据库中还没有,那么创建,如果已经有了则是更新。

save()方法可接受的参数如下:

参数 默认值 描述
force_insert False 显式的告诉Django对一个记录进行创建(force_insert=True)。极少这样用,但是对于不能依靠Django来决定是否创建的情况是有用的。
force_update False 显式的告诉Django对一个记录进行更新(force_update=True)。极少这样用,但是对于不能依靠Django来决定是否更新的情况是有用的。
using DEFAULT_DB_ALLAS 允许save()对settings.py中不是默认值的数据库进行保存/更新操作(比如,using='oracle',这里'oracle'必须是settings.py的'DATABASES'变量的一个key),详细用法见本文多数据库部分总结
update_fields None 接受包含多个字段的list(比如save(update_fields)=['name']),仅更新list中的字段。当有比较大的model,像进行更有效率更精细的更新,可以使用该选项。
commit True 确保记录保存到了数据库。在特定情况下(比如,models forms或者关系型操作),commit被设定为False,来创建一个instance,而不将其保存到数据库。这允许基于它们的输出来进行额外的操作 。
class Store(models.Model):
    name=models.CharField(max_length=30)
    address=models.CharField(max_length=30)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    def save(self,*args,**kwargs):
        #Do custom logic here(e.g. validation,logging,call third party service)
        #Run default save() method
        super().save(*args,**kwargs)
        

也可以对save()method进行重写,可以在它之前或者之后做一些事情。

delete()

该方法是通过一个reference来从数据库中消除一条记录。delete()实际上是依靠id主键来移除一个记录,所以对于一个reference应该有id值来进行delete()操作。

delete()被一个reference调用时,它的id主键被移除,但它剩余的值还保存在内存中。另外它用删除记录的数量作为回应。比如(1, {'testapp.Item': 1}).

save()类似,delete()也接受两个参数:using=DEFAULT_DB_ALIAS,keep_parents=False。using允许对其他数据库进行delete操作,当delete()发生在关系型model,希望保持parent model完整或者移除,keep_parents是很有用的。

最后,也可以对delete()方法进行重写。

校验方法:clean_fields(),clean(),validate_unique,full_clean()

理解校验方法最重要的部分还是从两方面进行:数据库层面和Python/Django层面。

对于数据库层面的校验,在第一次迁移时就自动完成了,而对于Python/Django层面,必须调用校验函数来进行校验。

需要注意的是:对于sqlite3,除primary key必须是整数外,其他的数据类型并没有严格的校核,Char(10)的含义是至少占用10个字节,所以会出现,如果直接创建一个超过10个字节的记录,也是可以保持到数据库的现象。

比如:

image-20201019103900920

>>> s=Stores.objects.create(name='this is a veryvery long name which exceeds 30 characters let us see if it will raise an error',address='cd',city='cd',state='ca',email='sdfkjs@qq.com')
>>> len('this is a veryvery long name which exceeds 30 characters let us see if it will raise an error')
93
  • clean_fields()

尽管可以通过调用save()来让数据库校验字段的合法性(对sqlite3,数据库不对字段进行数据类型检查,除primary key外),但也可以通过clean_fields()在Python/Django层面进行字段数据类型的合法性检验。

>>> s.clean_fields()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1253, in clean_fields
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'name': ['Ensure this value has at most 30 characters (it has 93).']}
  • validate_unique()

对于有unique选项的字段,可通过记录实例的validate_unique()方法进行unique检验。

>>> p=Stores(name='testname',address='科华北路',city='cd',state='ca',email='sjkfd@qq.com')
>>> p.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'address': ['Stores with this Address already exists.']}

除了对字段的unique选项进行unique检验,validate_unique()还对model的Meta class的unique_together属性(赋值为元组)进行检验,即多个字段组成的唯一性。

例如:

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    class Meta:
        unique_together=('test0','test1')
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0007
CREATE TABLE "banners_testall" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test0" varchar(3) NOT NULL, "test1" varchar(3) NOT NULL);
CREATE UNIQUE INDEX "banners_testall_test0_test1_2f0f2688_uniq" ON "banners_testall" ("test0", "test1");
>>> from banners.models import *
>>> s=TestAll(test0='a',test1='b')
>>> ss=TestAll(test0='a',test1='b')
>>> ss.validate_unique()
>>> s.validate_unique()
>>> s.save()
>>> ss.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Test all with this Test0 and Test1 already exists.']}
>>> s.validate_unique()
>>> ss.save()
Traceback (most recent call last):
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 413, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: UNIQUE constraint failed: banners_testall.test0, banners_testall.test1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 751, in save
    force_update=force_update, update_fields=update_fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 789, in save_base
    force_update, using, update_fields,
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 892, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 932, in _do_insert
    using=using, raw=raw,
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 1249, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\sql\compiler.py", line 1395, in execute_sql
    cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 98, in execute
    return super().execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\backends\sqlite3\base.py", line 413, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: UNIQUE constraint failed: banners_testall.test0, banners_testall.test1
>>> sss=TestAll(test0='a',test1='b')
>>> sss.validate_unique()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 1013, in validate_unique
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Test all with this Test0 and Test1 already exists.']}
>>>

可见,当s保存前,sss并不冲突,但是当s保存后,再调用ss.validate_unique()报错。

  • clean()

除了clean_fields(),还有clean(),用来提供更灵活的校验方法(比如特定的关系或者值)。

例如:

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
>>> from banners.models import *
>>> ss=TestAll(test0='c',test1='c')
>>> ss.save()
>>> ss.clean()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\testBegin\banners\models.py", line 99, in clean
    raise ValidationError('test0 shall not equil to test1')
django.core.exceptions.ValidationError: ['test0 shall not equil to test1']
  • full_clean()

最后是full_clean(),它是依次执行clean_fields(),clean(),validate_unique().

数据加载方法:refresh_from_db()

当数据库被另一个进程更新,或者不小心改变了model instance的值,也就是想用数据库中的数据来更新model instance的值,refresh_from_db()方法就是用来做这件事的方法.

尽管一般都是不带参数的使用refresh_from_db(),但它可以接受两个参数:(1)using,该参数跟save(),delete()方法中的含义一样(2)fields,用来挑选需要refresh的字段,如果没有指定,则refresh model的所有字段.

from_db(),get_deferred_fields()用来定制加载数据过程,用的情况相对比较少,详见https://docs.djangoproject.com/en/3.1/ref/models/instances/

定制方法

例:

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
>>> from banners.models import *
>>> s=TestAll.objects.all().first()
>>> s.test0
'a'
>>> s.special()
('a', 'b')

Model 管理字段:Objects

objects字段是所有Django models的默认管理字段,用来管理各种query操作(增删查改).

它是直接用在class上,而不是instance上,比如,要读取Store的id=1的记录,可以Store.objects.get(id=1),而删除所有记录则可以Store.objects.all().delete().

可以定制管理字段,比如重命名为mgr,则只需要添加类属性mgr=models.Manager().

Model Meta class及选项

Django中的Meta类是用来把model的行为作为一个整体进行定义,与数据类型不同,后者在字段上定义了更精细的行为.

比如为了避免不断的显式的声明一个model的搜索顺序,可以直接在Meta 类中定义ordering.

比如:

class Stores(models.Model):
    name=models.CharField(max_length=30,unique_for_date='date_lastupdate',db_column='my_custom_name')
    address=models.CharField(max_length=30,unique=True)
    city=models.CharField(max_length=30)
    state=models.CharField(max_length=2)
    email=models.EmailField(help_text='valide email please')
    date=models.DateField(default=date.today)
    datetime=models.DateTimeField(default=timezone.now)
    date_lastupdate=models.DateField(auto_now=True)
    date_added=models.DateField(auto_now_add=True)
    timestamp_lastupdated=models.DateTimeField(auto_now=True)
    timestamp_added=models.DateTimeField(auto_now_add=True)
    testeditable=models.CharField(max_length=3,editable=False)
    def __str__(self):
        return self.name 
    class Meta:
        ordering=['-timestamp_added']
>>>a=Stores.objects.all()
>>> for i in a:
...     print(i.timestamp_added)
...
2020-10-19 02:38:22.767910+00:00
2020-09-28 07:45:12.708000+00:00
2020-09-28 05:05:08.066000+00:00

可以看到是按照timestamp_added降序排列的.

DDL table options: db_table,db_tablespace,managed,unique_together
  • db_table

默认地,创建表的名字为<app_name>_<model_name>,比如,app名字为'banners',model名字为'testall',那么创建的表名为'banners_testall'.

sqlite> .tables
auth_group                  banners_entry_authors
auth_group_permissions      banners_item
auth_permission             banners_menu
auth_user                   banners_question
auth_user_groups            banners_stores
auth_user_user_permissions  banners_testall
banners_author              django_admin_log
banners_blog                django_content_type
banners_choice              django_migrations
banners_entry               django_session

现在添加meta option:

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
    class Meta:
        unique_together=('test0','test1')
        db_table='specialTestAll' #添加db_table
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0008
BEGIN;
--
-- Rename table for testall to specialTestAll
--
ALTER TABLE "banners_testall" RENAME TO "specialTestAll";
COMMIT;

可见,已经将表名进行了更改。

  • db_tablespace

默认地,如果Django的数据库支持(比如Oracle)tablespace的概念,Django 就用settings.py中的DEFAULT_TABLESPACE变量作为默认的tablespace.也可以通过meta的db_tablespace选项来对model设置其他的tablespace.

Django管理创建/销毁数据表,如果不需要这样的管理,可以设置meta的managed=False

DDL index options: indexes,index_together

Index在关系型数据库记录的高效查询中是非常重要的,简单的说,它是包含了用于确保查询速度更快的包含了特定记录的column values。

Django meta类提供了2个选项来对model字段的index创建相应的DDL (如CREATE INDEX...):indexes,index_together.

对于primary keyunique的字段,不需要再进行标注为index

index参数可接受包含多个models.Index实例的列表,models.Index实例接受fields列表,其包含了需要被index的字段名,和name,用于命名Index的名字,缺省情况下,自动为其进行命名。

from django.core.exceptions import ValidationError
class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    test2=models.CharField(max_length=3)
    test3=models.CharField(max_length=3)
    def clean(self):
        if self.test0==self.test1:
            raise ValidationError('test0 shall not equil to test1')
    def special(self):
        return self.test0,self.test1
    class Meta:
        unique_together=('test0','test1')
        db_table='specialTestAll'
        indexes=[
            models.Index(fields=['test0','test1']),
            models.Index(fields=['test0'],name='test0_idx')
        ]
        index_together=['test2','test3']
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0009
BEGIN;
CREATE UNIQUE INDEX "specialTestAll_test0_test1_f3575fe2_uniq" ON "specialTestAll" ("test0", "test1");
--
-- Alter index_together for testall (1 constraint(s))
--
CREATE INDEX "specialTestAll_test2_test3_20a904e7_idx" ON "specialTestAll" ("test2", "test3");
--
-- Create index specialTest_test0_eb859d_idx on field(s) test0, test1 of model testall
--
CREATE INDEX "specialTest_test0_eb859d_idx" ON "specialTestAll" ("test0", "test1");
--
-- Create index test0_idx on field(s) test0 of model testall
--
CREATE INDEX "test0_idx" ON "specialTestAll" ("test0");
COMMIT;

index_together允许定义多字段index,index_together=['test0','test1']等效于indexes=[models.Index(fields=['test0','test1']) ].

命名选项:verbose_name,verbose_name_plural,label,label_lower,app_label

默认地,Django models是通过类名来refer model的,绝大多数都是可以的。但是对于有些情况,比如,做开发的时候,类名使用了缩写,但如果想要在UI上,或者admin中不这样显示,该怎么办呢?

这就是verbose_name出现的原因了,verbose_name_plural是对英语的复数,比如class Choice的复数显示是choices。

class TestAll(models.Model):
    test0=models.CharField(max_length=3)
    test1=models.CharField(max_length=3)
    test2=models.CharField(max_length=3)
    test3=models.CharField(max_length=3)
    class Meta:
        verbose_name='verboseTestAll'
        verbose_name_plural='VerboseTestAllPlural'

在admin中:

image-20201020203055640
>>> from testapp.models import *
>>> TestAll._meta.verbose_name
'verboseTestAll'
>>> TestAll._meta.verbose_name_plural
'VerboseTestAllPlural'
>>> TestAll._meta.label
'testapp.TestAll'
>>> TestAll._meta.label_lower
'testapp.testall'

继承 Meta option: Abstract and proxy

类似于OOP的继承,这里model 的Meta类也有类似的概念,分别是abstractproxy

  • Abstract

对于abstract,它的用法是在model的Meta class中声明abstract=True,这样该model就变成了一个类似于base class的model,它不创建表,而继承它的class自动获取它定义的所有字段。

  • Proxy

abstract相反,它的用法是base class就是正常的model,在其子类的Meta class中声明proxy=True,子类不会创建新的表,子类所定义的行为选项都好像是直接作用在基类上的,但真正的基类的行为选项并没有实质被改变,可以理解为子类对基类的修修补补,但还是作用在基类的表上的。

简单总结下就是:在abstract和proxy情况下,带有meta的没有表,abstract出现在基类meta中,proxy 出现在子类的meta中。

class TestAbs(models.Model):
    test=models.CharField(max_length=3)
    class Meta:
        abstract=True 
class subAbs(TestAbs):
    pass 

class TestProxy(models.Model):
    test=models.CharField(max_length=3)

class subPro(TestProxy):
    class Meta:
        proxy=True 
(env) E:\programming\DjangoProject\TangleWithTheDjango\testDebug>python manage.py sqlmigrate testapp 0014
BEGIN;
--
-- Create model subAbs
--
CREATE TABLE "testapp_subabs" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test" varchar(3) NOT NULL);
--
-- Create model TestProxy
--
CREATE TABLE "testapp_testproxy" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "test" varchar(3) NOT NULL);
--
-- Create proxy model subPro
--
COMMIT;

当进行django model query时,有很多默认的行为,如果要用非默认的query行为,则需要显式的参数显式的调用query的各种方法。然而,一次又一次的这样做,无聊且易错,所以我们可以依靠本节的query meta option来改变model query的各种默认行为。

  • ordering

ordering是用来定义默认的排序的,比如Store.objects.all().sort_by('-name')是按照name降序排列,等效为ordering=['-name'],需要注意的是赋值为列表类型。

  • order_with_respect_to

    按照某字段进行排序,通常是外键,这样就可以使相互关联的object允许它的parent object(parent object的id就是子object 的外键)来通过get_RELATED_order,set_RELATED_ORDER来获得及设定子object的顺序。比如:

    class Question(models.Model):
        question_text=models.CharField(max_length=200)
        pub_date=models.DateTimeField('date published')
        def __str__(self):
            return self.question_text
        def was_published_recently(self):
            return self.pub_date>=timezone.now()-timedelta(days=1)
    class Answer(models.Model):
        question=models.ForeignKey(Question,on_delete=models.CASCADE)
    
    ;sqlmigrate来查看其底层SQL语句
    CREATE TABLE "banners_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
    CREATE TABLE "banners_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_id" integer NOT NULL REFERENCES "banners_question" ("id") DEFERRABLE INITIALLY DEFERRED, "_order" integer NOT NULL);
    CREATE INDEX "banners_answer_question_id_b2a3fa7a" ON "banners_answer" ("question_id");
    
    >>> from banners.models import *
    >>> import datetime
    >>> Question.objects.create(question_text='what?',pub_date=datetime.date.today())
    F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2020-10-21 00:00:00) while time zone support
    is active.
      RuntimeWarning)
    <Question: what?>
     >>> s=Question.objects.get(id=1)
    >>> s
    <Question: what?>
    >>> s.pub_date
    datetime.datetime(2020, 10, 20, 16, 0, tzinfo=<UTC>)
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (1)>
    >>> Answer.objects.cretae(question=s)
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
    AttributeError: 'Manager' object has no attribute 'cretae'
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (2)>
    >>> Answer.objects.create(question=s)
    <Answer: Answer object (3)>
    >>> s.get_answer_order()
    <QuerySet [1, 2, 3]>
    >>> s.set_answer_order([3,1,2])
    >>> a=Answer.objects.get(id=2)
    >>> a.get_next_in_order()
    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\base.py", line 981, in _get_next_or_previous_in_order
        }).order_by(order)[:1].get()
      File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 431, in get
        self.model._meta.object_name
    banners.models.Answer.DoesNotExist: Answer matching query does not exist.
    >>> a.get_previous_in_order()
    <Answer: Answer object (1)>
    
  • get_latest_by

在Meta中,get_latest_byDateField,DateTimeField,IntegerField的字段的名字,或包含这种字段名字的列表。这样就可以用Managerlatest()earliest()方法。

比如:

class Question(models.Model):
    question_text=models.CharField(max_length=200)
    pub_date=models.DateTimeField('date published')
    def __str__(self):
        return self.question_text
    def was_published_recently(self):
        return self.pub_date>=timezone.now()-timedelta(days=1)
    class Meta:
        get_latest_by='pub_date'
 
;查看sqlmigrate
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py sqlmigrate banners 0011
BEGIN;
--
-- Change Meta options on question
--
--
-- Set order_with_respect_to on answer to None
--
CREATE TABLE "new__banners_answer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_id" integer NOT NULL REFERENCES "banners_question" ("id") DEFERRABLE INITIALLY DEFERRED);
INSERT INTO "new__banners_answer" ("id", "question_id") SELECT "id", "question_id" FROM "banners_answer";
DROP TABLE "banners_answer";
ALTER TABLE "new__banners_answer" RENAME TO "banners_answer";
CREATE INDEX "banners_answer_question_id_b2a3fa7a" ON "banners_answer" ("question_id");
COMMIT;
>>> from banners.models import *
>>> import datetime
>>> datetime.date(2019,8,19)
datetime.date(2019, 8, 19)
>>> Question.objects.create(question_text='how?',pub_date=datetime.date(2019,3,4))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2019-03-04 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: how?>
>>> Question.objects.create(question_text='when?',pub_date=datetime.date(2017,2,2))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2017-02-02 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: when?>
>>> Question.objects.create(question_text='where?',pub_date=datetime.day(2018,3,3))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: module 'datetime' has no attribute 'day'
>>> Question.objects.create(question_text='where?',pub_date=datetime.date(2018,3,3))
F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\fields\__init__.py:1312: RuntimeWarning: DateTimeField Question.pub_date received a naive datetime (2018-03-03 00:00:00) while time zone support
is active.
  RuntimeWarning)
<Question: where?>
>>> Question.objects.latest()
<Question: what?>
>>> Question.objects.earliest()
<Question: when?>
>>> s=Question.objects.earliest()
>>> s.pub_date
datetime.datetime(2017, 2, 1, 16, 0, tzinfo=<UTC>)
>>> Answer.objects.latest()  #可以看到,没有设定get_latest_by,就不能用manager的latest方法
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 674, in latest
    return self.reverse()._earliest(*fields)
  File "F:\PycharmProject\webDev\Django project\env\lib\site-packages\django\db\models\query.py", line 658, in _earliest
    "earliest() and latest() require either fields as positional "
ValueError: earliest() and latest() require either fields as positional arguments or 'get_latest_by' in the model's Meta.
  • default_manager_name

所有的models都将objects作为默认的model manager,但是当存在多个manager时,就必须指定哪个才是默认的manger,这时候,就需要在meta中对default_manager_name进行指定。

  • default_related_name

对于相互有关系的object,可以通过parent object 来反向得到子object:

>>> from banners.models import *
>>> q=Question.objects.get(id=1)
>>> q.answer_set.get(id=1)
<Answer: Answer object (1)>

这里,默认是<model_name>_set,所以在本例中为answer_set.

因为对于字段的反向名字应该是唯一的.所以在abstract subclass情况下需要注意不能冲突,可以用包含'%(app_label)s'和'%(model_name)s'来规避这种冲突.

# common/models.py
from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass
# rare/models.py
from common.models import Base

class ChildB(Base):
    pass
  • Permisssion Meta operation: default_permissions, permission

默认地,default_permissions是('add','change','delete',‘view'),这就允许了对model instance的增删改的操作.

比如:

class testPermission(models.Model):
    test0=models.TextField()
    class Meta:
        default_permissions=('add',)
        permissions=(('can_do_someting','we can do something'),)
(env) F:\PycharmProject\webDev\Django project\testBegin>python manage.py shell
Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:05:16) [MSC v.1915 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user=User.objects.create_user('xiaoming','xiaoming@qq.com','3152')
>>> user.last_name
''
>>> user.save()
>>> op=User.objects.get(id=1)
>>> op.first_name
''
>>> op.name
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'name'
>>> op.get_all_permissions()
{'banners.delete_menu', 'banners.view_menu', 'banners.add_blog', 'sessions.view_session', 'banners.delete_entry', 'auth.change_user', 'banners.view_testall', 'banners.change_menu', 'sessions.delete_session', 'banners.view_choice', 'banners.view_item', 'banners.change_stores', 'banners.add_item', 'auth.view_user', 'banners.delete_item', 'admin.add_logentry', 'auth.add_permission', 'banners.add_testall', 'banners.view_stores', 'admin.delete_logentry', 'contenttypes.change_contenttype', 'banners.delete_answer', 'banners.change_author', 'banners.view_blog', 'admin.view_logentry', 'banners.change_answer', 'banners.change_blog', 'auth.delete_user', 'auth.add_user', 'banners.add_testpermission', 'banners.view_author', 'sessions.add_session', 'banners.add_menu', 'contenttypes.view_contenttype', 'contenttypes.delete_contenttype', 'banners.view_entry', 'banners.delete_author', 'banners.add_entry', 'auth.view_permission', 'banners.change_testall', 'banners.add_answer', 'auth.change_group', 'auth.view_group', 'banners.change_question', 'banners.add_question', 'banners.add_author', 'banners.view_answer', 'sessions.change_session', 'auth.delete_group', 'contenttypes.add_contenttype', 'banners.delete_blog', 'banners.delete_stores', 'banners.delete_choice', 'banners.change_entry', 'banners.can_do_someting', 'auth.delete_permission', 'auth.change_permission', 'banners.add_stores', 'banners.delete_question', 'banners.change_item', 'banners.add_choice', 'admin.change_logentry', 'banners.view_question', 'banners.delete_testall', 'auth.add_group', 'banners.change_choice'}
>>> op.has_perm('banners.delete_testpermission')
True

更详细的解答可参考:https://www.cnblogs.com/37Y37/p/11658651.html

Django models 的关系型数据

简而言之,关系型数据就是用不同数据库的记录的id或者pk来连接在一起,从而达到易维护,提高query性能,减少冗杂数据的目的.

Django支持以下三种关系型数据:一对多,多对多,一对一.

  • 一对多

一对多是指,一个model的记录可联结另一个model的多个记录.比如一个Menu的记录可包含多个Item记录,而一个Item记录只能对应一个Menu记录,为了表达这样的关系,可在Itemmodel中加入ForeignKey字段,该字段指明为Menu

  • 多对多

多对多关系指,一个model的记录可对应另一个model的多个记录,而另一个model的一个记录也可以对应前者的多个记录.比如Book的一个记录可对应多个Author的记录,而Author的一个记录也可以对应多个Book.为了表达这种关系,可将ManyToManyField用在任意一个Model上.

  • 一对一

一对一有点像面向对象继承的意思,比如Item如果和Drink进行一对一的联结,那么Item之用定义通用的字段,而Drink定义详细的,特定的字段.

关系型数据类型的选型
数据整体性选型(Data integrity options):on_delete

关系型数据将不同的model绑定了起来,当一端被删除,怎么处理另一端是非常重要的.on_delete就是用于这个目的.

on_delete适合以上三种关系型数据类型,它的选型为:

  • models.CASCADE(default). 自动删除相关的记录.(比如Menubreakfast实例被删除,所有相关的Item的记录将被删除)
Reference option: Self,literal strings
  • 'self'

model 关系有时候有递归关系。在一对多的父-子关系中比较常见。比如Categorymodel可以有一个parent,而这个parent就是另一个Category。为了定义这种关系,必须用self来指代相同的model。

但是在实际操作中,往往需要加上null=True,blank=True(在admin操作时,需要),因为如果要定义它,它的parent就必须存在,如果不允许null值,那么就会报错。

比如:

class Category(models.Model):
    menumenu=models.ForeignKey('self',on_delete=models.CASCADE,null=True,blank=True)
image-20201022214155077
  • literal string

尽管model关系型数据通常都用reference,但是使用model名字的字符也是合法的,比如models.ForeignKey('menu')。当model定义顺序不允许索引还没有创建的model时,该方法很有用,该方法也被称为'laze loading'。如果跨app,可以将app_label加在前面,如models.ForeignKey(production.Manufacturer').

反向关系:related_name,related_query_name,symmetrical

对于关系型model,Django自动的用_set在数据间建立了反向关系。

例:

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE)
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> a=parent.objects.create(name='johnyang')
>>> b=sub.objects.create(par=a,name='johnsub')
>>> c=sub.objects.create(par=a,name='johnsub2')
>>> d=parent.objects.create(name='tom')
>>> e=sub.objects.create(par=d,name='tomsub')
>>> a.sub_set.all()
<QuerySet [<sub: johnsub>, <sub: johnsub2>]>
>>> f=a.sub_set.all()
>>> f[0]
<sub: johnsub>
>>> print(f[0])
johnsub
>>> len(f)
2
>>> parent.objects.filter(sub__name='johnsub')
<QuerySet [<parent: johnyang>]>

从上面可以可看出,反向关系就是通过parent来反向索引/querysub

反向获取所有其子model,默认通过instance的<submodelname>_set属性,可通过related_name来进行定制;

通过model class manager的<submodelname>__...来query符合条件的parent instance,该<submodelname>可通过related_query_name来进行定制。

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE,related_name='subRelated',related_query_name='subQuery')
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> parent.objects.filter(subQuery__id=1)
<QuerySet [<parent: johnyang>]>
>>> a=parent.objects.filter(subQuery__id=1)
>>> a[0]
<parent: johnyang>
>>> a[0].id
1
>>> a[0].subRelated.all()[0].id
1

related_name='+',表示拒绝进行反向索引:

class parent(models.Model):
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name

class sub(models.Model):
    par=models.ForeignKey(parent,on_delete=models.CASCADE,related_name='+',related_query_name='subQuery')
    name=models.CharField(max_length=5)
    def __str__(self):
        return self.name 
>>> from testapp.models import *
>>> a=parent.objects.get(id=1)
>>> a.sub_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'parent' object has no attribute 'sub_set'
  • symmetrical

ManyToMany='self',那么Django认为这种多对多关系是对称的,所以就没有_set的反向索引,多对多的添加只能通过manytomany键的addcreate来进行添加。

class person(models.Model):
    friends=models.ManyToManyField('self')
>>> from testapp.models import *
>>> a=person.objects.create()
>>> b=person.objects.create()
>>> a.friends.create()
<person: person object (7)>
>>> a.friends.add(b)
>>> a.friends.all()
<QuerySet [<person: person object (6)>, <person: person object (7)>]>
>>> e=b.friends.create()
>>> e.friends.all()
<QuerySet [<person: person object (6)>]>
>>> b.friends.all()
<QuerySet [<person: person object (5)>, <person: person object (8)>]>
>>> e.friends_set #无_set
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person' object has no attribute 'friends_set'
>>> e.person_set
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person' object has no attribute 'person_set'

如果要查询b的所有朋友,只需要b.friends.all()就可以了,而不能b.friends_set.all()

>>> from testapp.models import *
>>> amenity.objects.get(id=1).storee_set.all()
<QuerySet [<storee: storee object (1)>, <storee: storee object (2)>]>
>>> person.objects.get(id=1).friends.all()
<QuerySet []>
>>> person.objects.get(id=1).friends.create()
<person: person object (10)>
>>> amenity.objects.get(id=1).storee_set.create(name='now1024')
<storee: storee object (4)>

值得注意的是,对于多对多,不能像一对多那样分别对多个model进行分别赋值,它可以进行反向索引进行createadd操作,对于selfsymmetrical=True,反向索引不能用_set

如果要取消这种对称,只需要symmetrical=False

>>> from testapp.models import *
>>> a=person1.objects.create()
>>> a.friends_set.all()
Traceback (most recent call last):     
  File "<console>", line 1, in <module>
AttributeError: 'person1' object has no attribute 'friends_set'
>>> a.friends.all()
<QuerySet []>
>>> b=person1.objects.create()
>>> a.friends.add(b)
>>> a.friends.all()
<QuerySet [<person1: person1 object (2)>]>
>>> b.person_set.all()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'person1' object has no attribute 'person_set'
>>> b.person1_set.all()
<QuerySet [<person1: person1 object (1)>]>
>>> b.friends.all()
<QuerySet []>

这时候,查询b的朋友就需要b.person1_set.all()了。

数据库选项:to_field,db_constraint,swappable,through,through_fields,db_table

  • to_field

一般地,Django的关系型model是通过primary key,(默认是id)来建立的。比如,menu=models.ForeignKey(Menu)储存Menu实例的id来作为索引。但也可以覆盖该行为,用to_field,需要注意的是如果要对to_field进行赋值,必须设定unique=True

  • db_constraint

默认地,Django在数据库层面遵循关系型数据库的各种惯例。db_constrait选项,默认为True,允许通过设定为False来忽略这种约束,而这种设定也仅仅当你事先知道一个数据库是破损的,不需要在数据库层面检查约束。

  • swappable

如果ManyToManyField指向一个swappable model,它控制数据的迁移行为。一般用在Usermodel,较少应用该选项。

  • through,through_fields,db_table

through,through_fields,db_table影响着junction table,如果要改变交叉表的名字,可以使用db_table

交叉表存储了多对多的关系型数据的最少信息:对于关系的id。来指定一个单独的model来作为交叉表,然后存储多对多的额外信息也是可能的(比如,through=MyCustomModel,用MyCustomModel来作为一个多对多的额外表。如果定义了throuth选项,那么用through_fields来告诉Django在新表中,哪些字段用来存储关系索引。

class person2(models.Model):
    name=models.CharField(max_length=128)
    def __str__(self):
        return self.name 
class group2(models.Model):
    name=models.CharField(max_length=128)
    members=models.ManyToManyField(person2,through='membership')
    def __str__(self):
        return self.name 
class membership(models.Model):
    person=models.ForeignKey(person2,on_delete=models.CASCADE)
    group=models.ForeignKey(group2,on_delete=models.CASCADE)
    date_joined=models.DateField(default=timezone.now)
    invite_reason=models.CharField(max_length=64)
    
#直接通过中间表来创建
>>> from testapp.models import *
>>> a=person2.objects.create(name='johnyang')
>>> b=group2.objects.create(name='gp1')
>>> m1=membership.objects.create(person=a,group=b,invite_reason='for test purpose')
>>> a.group2_set.all()
<QuerySet [<group2: gp1>]>
>>> a.group2_set.all().create(name='gp2')
<group2: gp2>
>>> m1.group
<group2: gp1>

如果标明了through_defaults,也可以用addcreate,set来添加关系

>>> c=person2.objects.create(name='tom')
>>> from datetime import date                   
>>> b.members.add(c,through_defaults={'date_joined':date(1991,5,5),'invite_reason':'startup'})
>>> b.members.create(name='hanson',through_defaults={'date_joined':date(2000,2,3),'invite_reason':'for development'})
<person2: hanson>
>>> d=person2.objects.create(name='yohn')
>>> e=person2.objects.create(name='porken')
>>> b.members.set([d,e],through_defaults={'date_joined':date(1991,5,5),'invite_reason':'startup'})
>>> m1 
<membership: membership object (1)>
>>> m1.person
<person2: johnyang>
>>> b.members.all()
<QuerySet [<person2: yohn>, <person2: porken>]>

remove用来移除一个记录,clear用来移除全部的记录。

>>> n=person2.objects.filter(name='yohn')
>>> b.members.remove(n[0])
>>> b.members.al()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'ManyRelatedManager' object has no attribute 'al'
>>> b.members.all()
<QuerySet [<person2: porken>]>
>>> b.members.clear()
>>> b.members.all()
<QuerySet []>

也可以从person2反向查询:

>>> from testapp.models import *
>>> person2.objects.all()
<QuerySet [<person2: johnyang>, <person2: tom>, <person2: hanson>, <person2: yohn>, <person2: porken>]>
>>> group2.objects.all()
<QuerySet [<group2: gp1>, <group2: gp2>]>
>>> gp1=group2.objects.get(id=1)
>>> gp1
<group2: gp1>
>>> gp1.members.all()
<QuerySet []>
>>> gp2=group2.objects.get(id=2)
>>> gp2
<group2: gp2>
>>> gp2.members.all()
<QuerySet []>
>>> johnyang,tom,hanson,yohn,porken=person2.objects.all()
>>> johnyang
<person2: johnyang>
>>> gp1.members.set([johnyang,tom,hanson],through_defaults={'invite_reason':'for test'})
>>> gp2.members.set([hanson,yohn,porken],through_defaults={'invite_reason':'for interest'})
>>> membership.objects.all()
<QuerySet [<membership: membership object (6)>, <membership: membership object (7)>, <membership: membership object (8)>, <membership: membership object (9)>, <membership: membership object (10)>, <membership: membership object (11)>]>
>>> johnyang.membership_set.all()
<QuerySet [<membership: membership object (6)>]>
>>> johnyang.group2_set.all()
<QuerySet [<group2: gp1>]>
>>> hanson.membership_set.get(group=gp1).invite_reason
'for test'
>>> hanson.membership_set.get(group=gp2).invite_reason
'for interest'
  • Form 值:limit_choices_to

当Django model在forms中使用时,限制显示关系型数据的数量是很有必要的。比如,对于有几百乃至几千个记录的Item,其对应的model是Menu,那么全部显示出Item的记录就是不切实际的。

limit_choices_to可用来在form中过滤显示的关系型数据。

Django Model 事务

事务在model数据操作中扮演着非常重要的角色。当在Django中建立了一个数据库,下面的有关事务的设置默认为:

AUTOCOMMIT=True
ATOMIC_REQUESTS=False

AUTOCOMMIT设定为True,保证了改变数据(Create,Update,Delete)的操作都进行自身的事务,然而也有很多种情况,是有必要将成组的操作包装成一个事务(all or nothing)。

每个请求(REQUEST)的事务:ATOMIC_REQUESTS,装饰器

Django支持ATOMIC_REQUESTS,默认是False,该选项是用来对每个对django application的请求进行一个事务操作,通过设定为True,保证了所有的数据操作都在一个请求内完成,如果这个请求成功的话。

当在view method中的逻辑是'all or nothing'的任务,比如,一个view method需要执行5个与数据有关的子任务(验证,发邮件等),来确保所有子任务都成功,数据操作才结束,如果有一个子任务失败,所有的子任务都回回滚,就像什么也没发生一样,是非常重要的。

因为ATOMIC_REQUEST=True对每个request都开了事务,这就意味着可能对“高速”应用的性能有一定的影响。也是基于这样的因素,单独的对特定的request开启/关闭”原子请求“(atomic request)也是被django支持的。

from django.db import transaction
# 当ATOMIC_REQUESTS=True ,可以单独的关闭atomic requests
@transaction.non_atomic_requests
def index(request):
    #事务的commit/rollback
    data_operation_1()
    data_operation_2()
    data_operation_3()
#当ATOMIC_REQUESTS=False ,可以单独的开启atomic requests
@transaction.atomic
def detail(request):
    data_operation_1()
    data_operation_2()
    data_operation_3()

环境管理和回调:atomic(),on_commit()

除了AUTOCOMMIT,ATOMIC_REQUEST配置和view method transaction decorator,也可以将事务运用在一个中等的规模,意思就是,比单独的数据操作(比如save())要粗糙,但是比atomic request(比如view method)要精细.

with可以激发context manager,它也是用`django.db.transaction.atomic,但不是装饰器了,而是用在方法的内部。

from django.db import transaction

def login(request):
    # AUTO_COMMIT=True,ATOMIC_REQUESTS=False
    data_operation_standalone()
    
    with transaction.atomic:;
        # 开始事务
        #失败后,回滚
        data_operation_1()
    	data_operation_2()
    	data_operation_3()
        #如果成功就commit
     data_operation_standalone2()

callback也是可以被支持的,通过on_commit,它的语法如下:

from django.db import transaction
transaction.on_commit(only_after_success_operation)
transaction.on_committ(lambda:only_affter_success_operation_with_args('success'))

Django model 迁移

迁移文件实际上是model到数据库的缓冲,我们可以在创建完迁移文件后(python manage.py makemigrations,先不写入数据库,先提前看下model的变化以及相应的sql语句(使用python manage.py sqlmigrate),当然也可以回滚到models.py的某个时间点。

Migration 文件创建

python manage.py makemigrations命令就是创建迁移文件的第一步。如果不带任何参数的执行该命令,则Django检查INSTALLED_APP中声明的所有app的models.py,然后为每个有改变的models.py创建迁移文件。

下表给出了常用的参数:

Argument Description
<app_name> 专门对某个app的models.py进行创建迁移文件,如python manage.py makemigrations stores
--dry-run 模拟创建迁移文件,但不产生实际的迁移文件,比如:(env) E:\programming\python manage.py makemigrations --dry-run<br/>Migrations for 'testapp':<br/> testapp\migrations\0028_testmig.py<br/> - Create model testmig
--empty 必须提供一个app_name,为其创建一个空迁移文件。
--name 'my_mig_file' 为其创建一个自定义名字的迁移文件,需要注意的是前缀依然自动带有序列号。
--merge 为有冲突的两个迁移文件进行merge操作。

Migration 文件重命名

迁移文件不是放着不动,对它们进行重命名也是可以的,或者说有时候甚至是必要的。需要进行的重命名步骤取决于该迁移文件是否已经迁移至数据库。可以执行python manage.py showmigrations来查看是否已经迁移至数据库,带有'x'的就是已经迁移到数据库了。

比如:

image-20201025214540730

对于那些还没有迁移到数据库的,可以直接进行更改名字,因为这时候迁移文件只是model 改变的一个表示,并没有将这种变化反映到数据库中。

对于已经反映到数据库中的迁移文件,有两种办法:(1)第一种方法是:修改迁移文件名字,更改掌控迁移活动的数据库表来反映这个新名字,更新其他迁移文件的dependencies(如果有的话)也来反映这个新名字。一旦更改了新的名字,进入django_migrations表中,查看旧的记录,更新为新的名字,如果有更为新一点的迁移文件,更新一点的迁移文件会有dependencies声明,这个也需要更新。(2)第二种方法是:回滚到需要更该名字的迁移文件之前,这就相当于还没有将迁移文件迁移至数据库,然后直接更改迁移文件的名字,再执行迁移。

迁移文件压缩(Migration file squashing)

一个经历了很多改变的models.py,它的迁移文件看可能达到数十,甚至数百个,在这种情况下,对它们进行压缩也是有可能的。注意是‘压缩’,而不是‘merge',’merge'往往指解决冲突。

语法是:

python manage.py squashmigrations <app_name> <squash_up_to_migration_file_serial_number>

该命令需要app名字以及需要压缩到的迁移文件的序列号,比如:squashmigrations stores 0004就是将迁移文件0001,0002,0003,0004进行了压缩;;squashmigrations stores 0002 0004对0002,0003,0004进行了压缩。

压缩后的迁移文件遵从如下惯例:

<initial_serial_number>__squashed__<up_to_serial_number>__<date>.py

image-20201025225101091

压缩的文件替代了被压缩的文件的功能,也可以选择保留老的文件,之后的机理是新的压缩文件中用replaces字段来表明哪个旧文件被代替。

image-20201025225609594

迁移文件结构

from django.db import migrations,models
class Migration(migrations.Migration):
    initial=True
    replaces=[
        
    ]
    dependencies=[
        
    ]
    operations=[
        
    ]

initial是布尔值,用来表示是否是initial migration file,replaces是压缩文件中替换了哪个迁移文件的列表,dependenciesoperations是用的最为频繁的两个字段。

dependencies是一个包含了元组的('<app_name>','<migration_file>')列表,它告诉了迁移文件是依赖<app_name>下的<migration_file>的,最常见的是添加跨app间的迁移文件。比如,如果onlineapp依赖stores0002_data_population迁移文件,那么就可以添加一个dependency tuple到onlineapp的第一个迁移文件,以确保它在stores的迁移文件之后运行(比如('stores','0002_data_population'))

operations字段声明了包含迁移操作的列表。对于大部分情况,Django根据models的变化来创建迁移操作,比如,加了一个新的model,下一个迁移文件就包含了django.db.migrations.operations.CreateModel();当重新命名了一个model,则下一个迁移文件就包括了django.db.migrations.operations.RenameModel;当改变了一个model字段就有AlterModel();等等。

最为常见的编辑operation是添加非DDL操作,这个不能被作为是models 的改变。可以通过RunSQL来插入SQL查询的迁移,或者RunPython来进行python的逻辑迁移。

迁移文件回滚

迁移文件回滚非常简单,只需要python manage.py migrate xxxx就可以回滚到xxxx序列号的地方。

比如:

image-20201026000445955

现在所有表为:

image-20201026000546857

回滚到最后一个:

image-20201026000954772

相应的所有表:

image-20201026001024920

通过比较,发现确实是回滚到了任一指定的位置。

这不是说一定就能回滚成功,当遇到不可逆的操作,就抛出django.db.migrations.exceptions.IrreversibleError,当然,这并不意味不能进行回滚,而是需要额外的工作来使得文件变得可回滚。

大多不可逆的操作是在迁移文件中执行了特定的逻辑(RunSQL,RunPython),为了让这些操作可回滚,就必须一样地提供相应的回滚逻辑。下面”Django model初始值设立“部分将详细讲为RunSQLRunPython迁移操作创建可回滚操作。

Django 数据库任务

Django的manage.py提供了很多命令来执行像备份,加载,删除数据等任务。

备份:Fixture文件,dumpdata,loaddata,inspectdb

dumpdataloaddata命令是django的数据备份与加载工具,Django用术语fixture来表示由dumpdata产生的,由loaddata来加载的数据结构。默认使用JSON格式,但是也可以用XML,YAML格式(需要安装pyYAML).

dumpdata的常见用法如下:

  • manage.py dumpdata > all_model_data.json:将所有app的model的数据库的数据输出为json文件,注意如果没有>符号,全部打印在命令框中。
  • manage.py dumpdata <app_name> --format xml:将<app_name>下的model的数据以xml输出。
  • manage.py dumpdata <app_name>.<model_name> --indent 2:对<app_name>下的<model_name>model的数据以缩进2字符的样式输出json格式。

manage.py loaddata来加载dumpdata产生的fixture文件,它的语法就是manage.py loaddata <fixture_file_name>,fixture文件路径可以是相对的也可以是绝对的,除了给定的路径,它还对每个app中的fixtures文件夹进行搜索。

loaddata可以接受多个参数(比如loaddata <fixture_>,<fixture_2>...),这是有必要的,如果fixtures文件有内部相互依赖关系。它还可以限制在特定app中进行搜索/加载fixture文件(比如,manage.py loaddata menu --app item,仅搜索/加载在itemapp 下的fixtue 文件menu`。)

manage.py inspectdb是由现有数据库而创建model的逆向工程。

image-20201026130845698

删除数据:flush,sqlflush,sqlsequencereset

manage.py flush将清除数据库中所有数据,而manage.py sqlflush给出清除全部数据所使用的SQL,而并不实际清除。

image-20201026131912845

与数据直接交互:dbshell

manage.py dbshell将根据默认的数据库而打开相应的命令行shell。

Django model初始数据设立

在很多情况下,对Django model进行预先加载一些数据是有必要啊的,Django允许要么在python中手工添加,或者用sql语句来添加,或者用fixture文件。

第一步加载一个预先的数据就是先创建一个空的迁移文件,来处理数据加载过程(manage.py makemigrations --empty <app_name>).

  • RunPython

例如:

创建model后,migrate,形成000_initial.py,但此时不要migrate,否则等创建空迁移文件后,将不能把数据写入数据库.

python manage.py makemigrations --empty testmodel
# Generated by Django 3.1 on 2020-10-26 12:48

from django.db import migrations,models

def load_stores(apps,schema_editor):
    store=apps.get_model('testmodel','store')#<model_name>,<app_name>
    store_corporate=store(name='Corporate',address='623 borradway',city='San diego',state='CA')
    store_corporate.save()
    store_downtown=store(name='Downtown',address='Horton plazza',city='San diego',state='CA')
    store_downtown.save()
    store_uptown=store(name='Uptown',address='1240 university',city='San diego',state='CA')
    store_uptown.save()
    store_midtown=store(name='Midtown',address='784 washhington',city='San diego',state='CA')
    store_midtown.save()

def delete_stores(apps,schema_editor):
    """
    回滚.
    """
    store=apps.get_model('testmodel','store')
    store.objects.all().delete()

class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(load_stores,delete_stores),
    ]

python manage.py migrate testmodel
image-20201026215649357

可见,已经写入了数据库。

  • RunSQL

删除刚才的RunPython的迁移文件,新建一个空的0002号迁移文件,在app下的sql文件夹下写入以下store.sql文件:

INSERT INTO testmodel_store (id,name,address,city,state) VALUES (0,'hongqi','tianrenlu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (1,'haoyouduo','kehuabeilu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (2,'jailefu','shuangdianxilu','chengdu','CD');
INSERT INTO testmodel_store (id,name,address,city,state) VALUES (3,'hongqi','wenwenglu','chengdu','CD');
# Generated by Django 3.1 on 2020-10-26 14:08

from django.db import migrations,models

def load_store_from_sql():
    from testDebug.settings import BASE_DIR
    import os
    sql_statement=open(os.path.join(BASE_DIR,'testmodel/sql/store.sql'),'r').read()
    return sql_statement

def delte_stores_from_sql():
    return 'DELETE from testmodel_store'


class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunSQL(load_store_from_sql(),delte_stores_from_sql())#需要注意的是传入的不是函数索引,而是函数返回值。
    ]

执行迁移命令后(有必要的话,可以删除db.sqltie3文件)

image-20201026222915557

  • fixture 文件

在app下设fixtures文件夹,文件夹下写入store.jsonfixture文件(非常重要),如下:

[
    {
        "fields":{
            "city":"beijing",
            "state":"BJ",
            "name":"beijingkaoya",
            "address":"wenhuadadao"
        },
        "model":"testmodel.store",
        "pk":0
    },
    {
        "fields":{
            "city":"nanjing",
            "name":"tianshuimian",
            "state":"NJ",
            "address":"dongfenglu"
        },
        "model":"testmodel.store",
        "pk":1
    }
]

空迁移文件

# Generated by Django 3.1 on 2020-10-26 14:51

from django.db import migrations,models
def load_stores_from_fixture(apps,schema_editor):
    from django.core.management import call_command
    call_command('loaddata','store.json')
def delete_stores(apps,schema_editor):
    Store=apps.get_model('testmodel','store')
    Store.objects.all().delete()


class Migration(migrations.Migration):

    dependencies = [
        ('testmodel', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(load_stores_from_fixture,delete_stores)
    ]

执行迁移后:

image-20201026232651547

Django model 信号

Django models有很多可以覆盖的方法,如save,__init__等,这样的话,就可以在里面嵌入我们特定的逻辑就可以达到model A的某个方法可以触发model B的另一个方法,但是这样却不是比较好的方法,因为model A中有了其他模型的一些信息,不符合低耦合的原则,所以在项目变得逐渐庞大后,这样会变得很乱,怎么办呢?

这样的问题在软件工程中非常常见,软件工程也提供了一个非常棒的方法,那就是signal-slot,每个控件都往环境里发射信号,然后有专门的监听器,监测到某个信号,就调用与该信号相应的slot函数,类似的,Django也借鉴了这种思路,所有的model都有自己的signal,只需要定义相应的接受函数即可,一般的模式为:

from django.dispatch import receiver
@receiver(<signal_to_listen>,sender=<model_class_to_listen>)
def method_with_logic(sender,**kwargs):
    #logic when signal is emitted
    #Access sender and kwargs to get info on model that emitted signal

需要注意的是sender不一定非得是model索引,也可以是索引的字符,这种情况下是lazy-loading模式。

内置的model signal如下表:

Signal Signal class Description
pre_init,post_init django.db.models.signal.pre_init/post_init 在model的__init__的开始与结束时发射信号
pre_save,post_save django.db.models.signal.pre_save/post_save 在model的save开始与结束时发射信号
pre_delete,post_delete django.db.models.signal.pre_delete/post_delete 在model的delete开始与结束时发射信号
m2m_changed django.db.models.signal.m2m_changed 当一个多对多模型被改变发射信号
class_prepared django.db.models.signals.class_prepared 当一个模型被定义和注册时候发射信号,一般是django内部机制,较少的使用于其他地方

建议专门把一个app的signal全部写入signals.py,便于管理。

# app内的signals.py
from django.dispatch import receiver
from django.db.models.signals import pre_save,post_save
from datetime import datetime 
import logging

# logging=logging.getLogger(__name__)
logging.basicConfig(filename='testP.log',level=logging.DEBUG)
@receiver(pre_save,sender='banners.testP')
def run_before_save(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new pre save'.center(30,'*'))
    logging.info('Start pre_save testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))
@receiver(post_save,sender='banners.testP')
def run_after_save(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new post save'.center(30,'*'))
    logging.info('Post_save testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))

需要注意的是,写完signals.py后,需要在app.py中定义ready方法来引入该signal文件

#app.py
from django.apps import AppConfig
class BannersConfig(AppConfig):
    name = 'banners'
    def ready(self):  #定义ready方法,引入signals.py
        import banners.signals

最后还需要在app的__init__.py中定义如下:

# __init__.py
default_app_config='banners.apps.BannersConfig'
>>> from banners.models import *
>>> testP.objects.create(name='johnyang3')
<testP: johnyang3>
>>> testP.objects.create(name='johnyang4')
<testP: johnyang4>
>>> a=testP(name='johnyang5')
>>> a.save()

log文件如下:

image-20201026155750199

定制signal

#models.py引入Signal类
from django.dispatch import Signal
testPClosed=Signal(providing_args=['employee']) #自定义signal,但必须提供providing_args,该参数为sender,receiver都使用
class testP(models.Model):
    name=models.CharField(max_length=5)
    def closing(self,employee):
        testPClosed.send(sender=self.__class__,employee=employee)
    def __str__(self):
        return self.name 
#在signal.py中加入
import logging
from .models import testPClosed
# logging=logging.getLogger(__name__)
logging.basicConfig(filename='testP.log',level=logging.DEBUG)
@receiver(testPClosed)
def run_when_testP_close(sender,**kwargs):
    now=datetime.now().strftime('%Y-%m-%d %H:%m:%S')
    logging.info('new closing'.center(30,'*'))
    logging.info('closing testP at %s' % now)
    logging.info('Sender is %s' % sender)
    logging.info('kwargs is %s' % str(kwargs))

>>> from banners.models import *
>>> a=testP(name='johnyang')
>>> a.closing(2)

image-20201027102155222

除models.py外的其他model放置方法

默认地,Django 的model 是放置在app里的models.py。然而随着定义的model的数量的增长,储存在一个文件的方法就越来越不便于管理,对此,有3种方法,将model存放在除models.py之外的位置。

Apps内的models文件夹

model可以存放在apps的models的文件夹中的文件中,首先删去models.py,然后在models文件夹中添加__init__使之成为一个包,并在__init__中引入models文件夹下各种.py文件中的model,注意,不建议使用from xxx import *,这样会导致命名空间紊乱。

image-20201027123259552

#__init__.py
from .testModelFolder import storeFolder
#models/testModelFolder.py
from django.db import models
class storeFolder(models.Model):
    name=models.CharField(max_length=3)

image-20201027123604350

Apps内的自定义文件夹

可以在apps内建立多个文件夹,只要每个文件夹下都有一个__init__.py文件,然后就可以在每个文件夹下任意定义.py来储存各种model,只需要在models.py中引入进来就行from .<sub_folder>.<file> import <model_class>

image-20201027131848894

#testfolds/cutomFile.py
from django.db import models
class storeFolder(models.Model):
    name=models.CharField(max_length=3)
#models.py
from .testfolds.customFile import storeFolder

经过makemigration,migrate后,

image-20201027132043399

Apps外及model“赋值”到apps

这种方法一般不推荐。

它是将apps外任意一个.py的model的meta的app_label赋值为给定的app,然后该model就属于该app了。

例如:

#about/models.py
from django.db import models

# Create your models here.
class testAcrossApp(models.Model):
    name=models.CharField(max_length=3)
    class Meta:
        app_label='banners'

image-20201027133913543

Django 多数据库

settings.py中,DATABASES用来定义与app相关的数据库。既然DATABASES是复数,也就意味着该变量可以有多个值:

#settings.py
DATABASES={
    'default':{
        ...
    },
    'devops':{
        ...
    },
    'analytics':{
        ...
    }
}

...表示数据库连接参数(比如ENGINE,NAME)。最重要的一点就是必须要有default键,除非声明了DEFAULT_DB_ALIAS值,那么该值不叫'default'了,但承担的就是默认数据库的任务。

model的多数据库选择:using

using来使用非默认数据库,有两种用法,一种是参数选项,一种是objects的方法。

  • 比如save(using=...),delete(using=...).
  • 比如Item.objects.using('somedb').all().

多数据库工具:--database

对于manage.py的用法中,可以加入--database,用以对特定数据库进行操作(migrate,dumpdata等)。比如:python manage.py migrate --database devops就是只对该数据库执行操作。

posted @ 2020-10-27 16:05  JohnYang819  阅读(956)  评论(0编辑  收藏  举报