Django_mosh
Django mosh
shell django-admin
django-admin startproject <proj name> .
run server
python manage.py runserver
app 可以通过 proj 初始 settings.py 集成
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'new proj name',
]
创建新 app
python manage.py startapp <appname>
添加进路由
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('__debug__/', include('debug_toolbar.urls')),
path('playground', include('playground.urls')),
]
views: 类似 Controller req->res, request handler
debug: django-debug-toolbar 配置方法文档
urlconf
# --- app added
from django.urls import path
from . import views
# URLconf
urlpatterns = [
path('hello/', views.say_hello) # '/' is important
]
# --- main proj
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
path('__debug__/', include('debug_toolbar.urls')),
path('playground/', include('playground.urls')),
]
其中 urlpatterns 是一个内置路由变量
model 类似 entity
from django.db import models
# Create your models here.
class Product(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
# 9999.99
price = models.DecimalField(max_digits=6, decimal_places=2)
inventory = models.IntegerField()
last_update = models.DateTimeField(auto_now=True)
class Customer(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20)
birth_date = models.DateField(null=True) # nullable
注意:
- 没有 id field django 自动创建(如果没有指定主键)
类似枚举和@JsonProperty
MEMBERSHIP_BRONZE = 'B'
MEMBERSHIP_SILVER = 'S'
MEMBERSHIP_GOLD = 'G'
MEMBERSHIP_CHOICES = [
(MEMBERSHIP_BRONZE, 'Bronze'),
(MEMBERSHIP_SILVER, 'Silver'),
(MEMBERSHIP_GOLD, 'Gold'),
]
membership = models.CharField(max_length=1, choices=MEMBERSHIP_CHOICES,
default=MEMBERSHIP_BRONZE)
其中'B'是实际存在数据库之中的值,'Bronze'是实际对上呈现的值
OneToOne Mapping
class Address(models.Model):
city = models.CharField(max_length=255)
customer = models.OneToOneField(Customer, on_delete=models.CASCADE, primary_key=True)
on_delete 可以设置多种级联,例如 SETNULL,SETDEFAULT,PROTECT,CASCADE
django 里面 OneToOne 默认是双向的,会自动在 Customer 之中生成 address 属性
OneToMany: db view,直接使用 ForeignKey
e.g
class Cart(models.Model):
customer = models.OneToOneField(Customer, on_delete=models.CASCADE, primary_key=True)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
number = models.IntegerField()
added_at = models.DateTimeField(auto_now_add=True)
ManyToMany
class Promotion(models.Model):
# 促销, 和产品是多对多关系
description = models.CharField(max_length=255)
discount = models.FloatField()
start_at = models.DateTimeField(null=True)
end_at = models.DateField(null=True)
class Product(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
# 9999.99
price = models.DecimalField(max_digits=6, decimal_places=2)
inventory = models.IntegerField()
last_update = models.DateTimeField(auto_now=True)
collection = models.ForeignKey('Collection', on_delete=models.PROTECT)
promotion = models.ManyToManyField('Promotion') # get by default 'product_set' in promotion
处理循环依赖:
Collection 类之中有一个 feature product,同时 product 被 Collection 包含,解决方法为加上''
相当于留了解析符号,但会影响修改时的改动,所以一般不要用
解决 django 自动创建反向依赖导致的重名问题,一种是指定 related_name,另一种 直接指定为'+'意思是不生成反向依赖
from django.db import models
class Collection(models.Model):
feature_product = models.ForeignKey('Product', on_delete=models.SET_NULL,null=True, related_name='+')
class Product(models.Model):
collection = models.ForeignKey(Collection, on_delete=models.PROTECT)
泛型关系
如果我们想要有一个 tag, 用于对任意物体打标签(因而需要与具体的 store 解耦),怎么做?
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
# Create your models here.
class Tag(models.Model):
title = models.CharField(max_length=255)
class TagItem(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
# Generic
# Type, ID -> content
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
也就是说 content_type 入库的实际上是一个序列化后的类型信息变量(例如 string),而 object_id 是 int,运行时可以计算的结果 content_obj?
答: 是的,
content_type 字段存储的是一个 ContentType 对象的 ID,这个对象代表了关联对象的类型。ContentType 是 Django 的一个内置模型,它存储了所有已注册模型的信息,包括模型的名字和它所在的应用的名字。
object_id 字段存储的是关联对象的 ID。这个 ID 是关联对象在它所在的数据库表中的主键。
content_object 是一个 GenericForeignKey 字段,它不会在数据库中创建对应的列,而是在运行时通过 content_type 和 object_id 的值去获取关联对象。访问 content_object 属性时,Django 会根据 content_type 和 object_id 的值去查询对应的对象,并返回这个对象。
练习:LikeItem
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.fields import GenericForeignKey
# Create your models here.
class LikeItem(models.Model):
# who likes what
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
migrations
应该让 Django 完成建库建表等操作
python manage.py makemigrations
migration 是 django 自动生成的以库为单位的 db 修改操作的抽象,包含 log 等功能
修改 entity 之后也简单的重新运行 migration 就行(可能要改改文件名(生成的文件名类似 git commit 的 hashID,是会出现在后续 migration 的 depandency 里面的))
migration 只是创建了操作,之后使用如下入库
python manage.py migrate
slug
slug = models.SlugField(default='-')
slug 是一个可以匹配字母数字下划线和连字符的动态路由, 用于使一个东西对搜索引擎检索更加友好
例如,如果想检索一篇博客some-blog,一个 url 为myweb/blogs/some-blog的子网页肯定比myweb/blogs/1容易检索
可以用path('.../<slug:slug>')来动态捕获 slug 路由参数
meta data
可以在 model.Model 类的派生类之中定义一个内部类 Meta,其中就可以修改表名字,增加索引等
由于 django 一般是“约定大于配置”的逻辑,如果你在一个地方改了,就要全改来保持统一性,所以改得比较少
有了这些 migration 文件之后,可以像 git checkout 一样回到 db 的某个状态
python manage.py migrate store <migrate_no>
集成 postgresql,看官方 tutorial 改 settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mosh_course',
'USER': 'ayanami',
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'mosh_course'),
'HOST': os.environ.get('POSTGRES_HOST', 'localhost'),
'PORT': 5432,
}
}
mock 一些数据 mockaroo.com 很好用
查询(DAO)
django 使用 queryset 进行查询,可以理解成 java 的 stream
query_set = Customer.objects.all()
query_set 可以 filter, order_by 等链式调用得到新的 query_set,并在最后一个“求值”的 api 上(例如切片,get 等操作)通过实际 query 返回数据
也就是说它是 lazy query 的,注意 query_set 不保证非 null,如果使用 get 没找到的话会抛出错误 ObjectNotFoundException,一个简单的避免抛错的方法是使用filter().first()的形式,在 filter 返回空的情况下返回None
filter或者get可以传入一个特殊参数pk来指定主键是啥
filter
只能传键值对,问题:
- 表示小于?
使用特殊的附加约定形式(类似 JPA)filter(<something>__st=5 ,其中__st的后缀作为特殊谓词标志
可以组合filter(collection__id__range=(5, 10))collection 表中 id 为 5 ~ 10 的
常见的还有__contains, ___startwith
条件组合 Q object 和|,~,&
Poll.objects.get(
Q(question__startswith="Who"),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
)
基本等同于
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
数据引用 F Object,表示引用本表或者外键关联表里面的字段
Poll.objects.get(
Q(question__startswith="Who"),
who=F(question), # 本表的'who'字段 == 本表的 'question'字段
who=F(anothertable__question) # 本表的'who'字段 == 本表的'anothertable'外键关联表的 'question'字段
)
Sort
order_by
query_set = Customer.objects.order_by('name') #按照name字段升序
query_set = Customer.objects.order_by('-name') #按照name字段降序
得到 table 的字段对象
有不同的方法
-
values得到 entity(object) -
values_list得到 dict of tuple -
only和defer都是 entity,只查询和延迟查询,但是有个注意点:不会动态检查是否存在某个属性,如果不存在会得到非常多的 query -> 性能大幅度降低
超级牛魔的一个设计
django 默认 不 加载关联表
同时,他的更新也是 不 关联的——需要手动更新关联表内信息
如果表 A 有 B 的外键,然后加载了表 A 的 all(),就会每一个表 A 的 row 都 join 查询一次表 B
加载关联表:
all_A = A.objects.select_related('B').all()
相关的一个 api 是 prefetch,和 select 的区别在于 select 是一对一的时候用,prefetch 是一对多的时候(一个 A 可能对应多个 B 的时候)用
all_A = A.objects.prefetch_related('B').all()
aggregate
传入一个键值对,值是某种 Django 定义的回调函数(如Count('id'),对 id 列计数)
(严格来说是 django database function 或者它的包装(使用Funcapi))
返回的是{键-回调计算结果} 的 dict
ExpressionWrapper & Custom Manager
用到再说
django 会缓存
CRUD
create:
# 第一种方法,有intellisense,可以refactor
new_customer = Customer()
new_customer.birth_date = "1990-01-01"
new_customer.email = "n7sZf@example.com"
new_customer.save()
# 第二种,更短但没有
new_customer = Customer.objects.create(birth_date="1990-01-01", email="n7sZf@example.com")
update:
new_customer = Customer.objects.get(pk=1)
new_customer.birth_date = "1990-01-01"
new_customer.email = "n7sZf@example.com"
new_customer.save()
# 当然还有第二种update object.filter(pk=1).update()
注意需要用 get,也就是需要显式读
customer = Customer(pk=1)
customer.delete()
# 当然还有第二种delete object.filter(pk=1).delete()
Transaction
现在有一个原子性问题,django 是不关联更新的,所以我们要先更新 order,再更新 orderItem,这就带来了原子性问题
解决方法,使用 with transaction.atomic():包裹多次 save()且需要保证原子性的地方
The Ultimate Solution
queryset = Customer.object.raw('SELECT * FROM store_product')
注意这个 queryset 没有 filter 之类的方法了,退化了(悲,差@Query 远甚)
admin
- custom 可以改改默认控制面板的列表名字,加上筛选和排序等等(ModelAdmin Options)