models表关系

一、一对多

以作者和书为例,一个作者可以写多篇文章,一篇文章只能属于一个作者,这就是一对多的关系。

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="苹果")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354