一、一对多
以作者和书为例,一个作者可以写多篇文章,一篇文章只能属于一个作者,这就是一对多的关系。
class Author(models.Model):
name = models.CharField(max_length=100)
class Article(models.Model):
title = models.CharField(max_length=100,null=False)
price = models.FloatField(null=False,default=0)
author = models.ForeignKey('Author',on_delete=models.CASCADE,null=True)
新增
#正向创建,通过外键字段关联。已知作者,创建文章,通过文章正向关联作者
def one_to_many_create_view(request):
author = Author.objects.get(pk=1)
article = Article(title="测试文章1",price=100,author=author)
article.save()
return HttpResponse("success")
#反向创建,通过外键对象反过来关联。已知作者,创建文章,通过作者反向关联文章
def one_to_many_create_view(request):
author = Author.objects.get(pk=1)
article = Article(title="测试文章2", price=100)
author.article_set.add(article)
return HttpResponse("success")
反向创建报错如下:
ValueError: <Article: 测试文章2> instance isn't saved. Use bulk=False or save the object first.
按照提示需要先把article对象保存,然后再通过author对象反向添加或者设置bulk=False
(1)添加save():
#修改反向创建,先save article对象
def one_to_many_create_view(request):
author = Author.objects.get(pk=1)
article = Article(title="测试文章2", price=100) #创建的时候先不通过正向方式设置作者字段
article.save() #保存成功的前提是author字段可以为null或者models里面设置了default值,否则会报错
author.article_set.add(article)
return HttpResponse("success")
(2)设置bulk=False
#避免了使用save()的前提条件,会自动将article对象先保存
def one_to_many_create_view(request):
author = Author.objects.get(pk=1)
article = Article(title="测试文章2", price=100)
author.article_set.add(article,bulk=False)
return HttpResponse("success")
author.article_set.add(article1,article2,article3...,bulk=False)源码如下:
add(self, *objs, **kwargs) (RelatedManager in django.db.models.fiedls.related_descriptors)
def add(self, *objs, **kwargs):
self._remove_prefetched_objects()
bulk = kwargs.pop('bulk', True)
objs = list(objs)
db = router.db_for_write(self.model, instance=self.instance)
def check_and_update_obj(obj):
if not isinstance(obj, self.model):
raise TypeError("'%s' instance expected, got %r" % (
self.model._meta.object_name, obj,
))
setattr(obj, self.field.name, self.instance)
if bulk:
pks = []
for obj in objs:
check_and_update_obj(obj)
if obj._state.adding or obj._state.db != db:
raise ValueError(
"%r instance isn't saved. Use bulk=False or save "
"the object first." % obj
)
pks.append(obj.pk)
self.model._base_manager.using(db).filter(pk__in=pks).update(**{
self.field.name: self.instance,
})
else:
with transaction.atomic(using=db, savepoint=False):
for obj in objs:
check_and_update_obj(obj)
obj.save()
删除
def one_to_many_delete(request):
#正向删除
article = Article.objects.get(id=1)
article.author.delete() #Article中ForeignKey设置的on_delete=models.CASCADE,所以删除作者的同时会将其对应的文章都删除on_delete还可以设置null或者默认值,可自行查阅
#反向删除
author = Author.objects.get(id=1)
author.article_set.all().delete()
return HttpResponse("success")
反向删除要取.all(),得到queryset对象才有delete()方法,否则只取author.article_set得到的是RelatedManager对象,没有delete()方法。
和多对多删除不同,反向获取的article_set是RelatedManager对象,没有remove()方法和clear()方法,所以没法通过remove()删除某个对象,只能通过delete()删除全部或者把article_set.all()换成article_set.filter(xxx)进行过滤到指定对象后删除
查询
#正向获取,包含外键字段。已知文章,查询对应的作者
def one_to_many_view(request):
article= Article.objects.get(id=1)
author = article.author
print(author)
return HttpResponse("success")
#反向获取,通过外键对象反过来获取。已知作者,查询对应的所有文章
def one_to_many_view(request):
author = Author.objects.get(pk=1)
all_articles = author.article_set.all() #article_set 是django自动生成,格式是:外键字段所在的模型类名小写_set
for article in all_articles:
print(article)
return HttpResponse("success")
反向获取时,如果想修改默认的外键字段所在的模型类名小写_set
,可以通过设置models中外键字段的releated_name
属性
author = models.ForeignKey('Author',on_delete=models.CASCADE,null=True,related_name="articles")
author.article_set
就失效了,替换为自定义的author.articles
二、一对一
用户表和用户扩展信息表,一个扩展信息只能对应一个用户,一个用户只能对应一个扩展信息,这就是一对一关系
class User(models.Model):
name = models.CharField(max_length=100)
class UserExtra(models.Model):
hobby = models.CharField(max_length=100)
user = models.OneToOneField("User",on_delete=models.CASCADE)
新增
#正向创建:已知用户,创建扩展信息,通过扩展信息关联用户
def one_to_one_fourth_view(request):
user_obj = User.objects.get(pk=1)
user_extra_obj = UserExtra(hobby="读书",user=user_obj)
user_extra_obj.save()
return HttpResponse("success")
修改代码如下,两个扩展信息指向同一个用户:
def one_to_one_third_view(request):
user_obj = User.objects.get(pk=1)
user_extra_obj = UserExtra(hobby="读书", user=user_obj)
user_extra_obj.save()
user_extra_obj = UserExtra(hobby="旅游",user=user_obj)
user_extra_obj.save()
return HttpResponse("success")
#抛异常
>>django.db.utils.IntegrityError: (1062, "Duplicate entry '1' for key 'user_id'")
右击mysql中UserExtra表,查看对象信息,查看DDL语句如下:
CREATE TABLE `user_userextra` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hobby` varchar(100) NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
一对一是严格规定的,不允许出现一对多的情况,UserExtra表中将user这个一对一字段自动设置了unique防止重复
反向创建:已知用户,创建扩展信息,通过用户关联扩展信息
无法通过user_obj.userextra.add()方式添加扩展对象,因为user_obj.userextra是一个UserExtra类对象,没有add()方法
删除
def one_to_one_delete(request):
#正向删除
user_obj = User.objects.get(pk=1)
user_obj.userextra.delete() # user_obj.userextra得到的是UserExtra类对象
#反向删除
user_extra_obj = UserExtra.objects.get(id=1)
user_extra_obj.user.remove()# user_extra_obj.user得到的是User类对象
return HttpResponse("success")
查询
def one_to_one_view(request):
#正向查询,已知扩展信息,查询对应的用户
user_extra_obj = UserExtra.objects.get(id=1)
user_obj = user_extra_obj.user
print(user_obj)
#反向查询:已知用户,查询对应的扩展信息
user_obj = User.objects.get(pk=1)
user_extra_obj = user_obj.userextra #userextra是django自动生成,格式是:外键字段所在的模型类名小写
print(user_extra_obj)
return HttpResponse("success")
反向获取时,如果想修改默认的外键字段所在的模型类名小写
,可以通过设置models中外键字段的releated_name
属性
user = models.OneToOneField("User",on_delete=models.CASCADE,related_name="extra")
user_obj.userextra
就失效了,替换为自定义的user_obj.extra
三、对多对
商品和订单是多对多关系
#商品表
class Goods(models.Model):
name = models.CharField(max_length=100) #商品名称
price = models.FloatField() #商品价格
order = models.ManyToManyField("Order")
#订单表
class Order(models.Model):
order_num = models.CharField(max_length=100) #订单号
create_time = models.DateTimeField(auto_now_add=True)#创建时间
新增
def many_to_many_create(request):
# 正向添加单个
good = Goods.objects.get(pk=1)
order = Order.objects.get(pk=1)
good.order.add(order)#也可以用add(id)比如add(1)
good.order.set([order])#跟add(order)效果一样
#正向添加多个
good = Goods.objects.get(pk=1)
orders = Order.objects.all()
good.order.add(*orders)#也可以用add([id1,id2,id3])比如add([1,2,3]),不会删除之前的关联,只会新添加关联,如果已存在则覆盖
good.order.set(orders) #注意这里是一个可迭代queryset,效果跟add(*orders)一样,不会删除之前的关联,只会新添加关联,如果已存在则覆盖
#正向添加多个
good = Goods.objects.get(pk=1)
orders = Order.objects.all()
good.order = orders #这里如果只添加单个对象会抛'Order' object is not iterable异常
good.save()
#反向添加单个
good = Goods.objects.get(pk=1)
order = Order.objects.get(pk=1)
order.goods_set.add(good)
order.goods_set.set([good])#跟add(good)效果一样
#反向添加多个
goods = Goods.objects.all()
order = Order.objects.get(pk=1)
order.goods_set.add(*goods)
order.goods_set.set(goods)#跟add(*goods)效果一样
return HttpResponse("success")
删除第三张表关系
def many_to_many_delete(request):
#正向删除单个
good = Goods.objects.get(pk=1)
order = Order.objects.get(pk=1)
good.order.remove(order)
# 正向删除多个
good = Goods.objects.get(pk=1)
orders = Order.objects.all()
good.order.remove(*orders)
# 正向删除多个
good = Goods.objects.get(pk=1)
good.order.clear()
#反向删除单个
good = Goods.objects.get(pk=1)
order = Order.objects.get(pk=1)
order.goods_set.remove(good)
# 反向删除多个
goods = Goods.objects.all()
order = Order.objects.get(pk=1)
order.goods_set.remove(*goods) #虽然goods是全部商品,但是反向删除只会删除订单对象对应的所有商品
#反向删除多个
order = Order.objects.get(pk=1)
order.goods_set.clear()
return HttpResponse("success")
删除第三张表关系和真实数据
#正向删除单个
good = Goods.objects.get(pk=1)
order = Order.objects.get(pk=1)
good.order.all().delete()
# 反向删除多个
goods = Goods.objects.all()
order = Order.objects.get(pk=1)
order.goods_set.all().delete()
需要取到.all(),得到queryset对象才有delete()方法,否则只取good.order或者order.goods_set得到的是ManyRelatedManager对象,没有delete()方法。
修改
先删除,再新增
查询
def many_to_many_view(request):
#正向查询,通过商品查所有订单
good = Goods.objects.get(pk=1)
orders = good.order.all()
print(orders)
#正向查询,通过订单某个字段查所有商品
goods = Goods.objects.filter(order__order_num="2000")
print(goods)
# 正向查询,通过商品表查询,查询订单对应的所有商品
goods = Goods.objects.filter(order=1)
print(goods)
#反向查询,通过订单查所有商品
order = Order.objects.get(pk=1)
goods = order.goods_set.all()
print(goods)
#反向查询,通过商品某个字段查所有订单
orders = Order.objects.filter(goods__name="苹果")
print(orders)
#反向查询,通过订单表查询,查询商品主键对应的所有订单
orders = Order.objects.filter(goods=1)
print(orders)
return HttpResponse("success")
跟一对多类似,反向获取时,如果想修改默认的外键字段所在的模型类名小写_set,可以通过设置models中ManyToManyField类的releated_name属性自定义名称
通过字段查询,不管正向反向都用对方model名称的小写__字段名
#正向查询,通过订单某个字段查所有商品
goods = Goods.objects.filter(order__order_num="2000")
#反向查询,通过商品某个字段查所有订单
orders = Order.objects.filter(goods__name="苹果")