更新多对多关系

3

我有3个模型(简化版):

class Product(models.Model):
    category = models.ForeignKey('Category', related_name='products', to_field='category_name')
    brand = models.ForeignKey('Brand', related_name='products', to_field='brand_name')

class Brand(models.Model):    
    brand_name = models.CharField(max_length=50)
    categories = models.ManyToManyField('Category', related_name='categories')

class Category(models.Model):
    category_name = models.CharField(max_length=128)

我希望在管理员界面中将一个分类更改为一组产品,我已经编写了一个自定义的管理员函数来完成这个操作。之后,我需要更新品牌-分类多对多关系,以检查特定的品牌是否仍然可以使用该分类。我已经编写了以下函数:

def brand_refresh():
    brands = Brand.objects.all().prefetch_related('shops', 'categories')
    products = Product.objects.select_related('shop', 'brand', 'category')

    for brand in list(brands):
        for category in brand.categories.all():
            if not products.filter(category=category).exists():
                brand.categories.remove(category)

               for product in list(products.filter(brand=brand).distinct('category')):
                    if product.category not in [None, category]:
                        brand.categories.add(product.category)

看起来这个怪物正在工作,但是遍历所有循环需要2个小时(我有约220k个产品、4k+个品牌和约500个类别)。在这里更新M2M关系是否有更好的方法?我认为.prefetch_related()应该对此有所帮助,但是现在似乎没有效果。


也许这会有所帮助:https://stackoverflow.com/questions/26839115/django-removing-item-from-many-to-many-relation-more-efficiently - Ashish Acharya
1个回答

1

以下是关于循环的第一部分的解决方案:

在生产环境中运行之前,您应该在可丢弃的本地数据库副本上尝试此操作,并检查一切是否正常:

from django.db.models import Count

# get a list of all categories which have no products
empty_categories = Category.objects.annotate(product_count=Count('products')).filter(product_count=0).values_list('id', flat=True)

# delete association of empty categories in all brands
Brand.categories.through.objects.filter(category_id__in=list(empty_categories)).delete()

对于第二部分,也许你可以尝试像这样做,虽然我不确定它是否更快(或者甚至是否正确):

for brand in Brand.objects.all():
    # get a list of categories of all products in the brand
    brand_product_categories = brand.products.all().value_list('category__id', flat=True).distinct()

    # get the brand's categories
    brand_categories = Category.objects.filter(category__brand=brand).value_list('id', flat=True)

    # get elements from a not in b
    categories_to_add = set(brand_product_categories) - set(brand_categories)

    for category_id in categories_to_add:
        brand.categories.add(category_id)

不确定,我已经想出了一些东西,但需要测试才能确定它是否更快。 - Ashish Acharya
是的,我明白了。那么应该是 category_id__in=list(empty_categories) 吧?据我所知,它应该要被转换一下吧? - Chiefir
1
我已经测试过类似的查询,没有使用 list(),但是它没有起作用。empty_categories 不是一个列表,而是某种 QuerySet 变体,在那个时刻不确定确切的情况。谢谢你的回答,我会测试一下并告诉你是否更快 :) - Chiefir
当然,请告诉我。我想这将是一个有趣的实验,尤其是第二部分。 - Ashish Acharya
1
如果您正在查询Category模型本身,则无需指定category_id,只需使用id即可。 - Ashish Acharya
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接