更新多对多关系

时间:2018-05-09 13:56:48

标签: python django django-models many-to-many django-orm

我有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)

我想将管理员中的类别更改为一堆产品,我有一个为此编写的自定义管理功能。之后,我需要更新Brand-Categories多对多关系,以检查Category是否仍可用于特定Brand。我写过这个函数:

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)

对我来说这个monstro工作正常,但循环所有周期需要2个小时(我有~220k产品,4k +品牌和~500个类别)。 我有没有更好的方法来更新M2M关系?我认为.prefetch_related()应该有帮助,但我现在看来没有效果。

1 个答案:

答案 0 :(得分: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)