Обновление отношения «многие ко многим» - PullRequest
0 голосов
/ 09 мая 2018

У меня 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 часа (у меня есть ~ 220 тыс. Продуктов, 4 тыс. Марок + и ~ 500 категорий). У меня есть какой-нибудь лучший способ обновить отношение M2M здесь? Я думаю, что .prefetch_related() должен помочь здесь, но то, что я сейчас имею, кажется, не имеет никакого эффекта.

1 Ответ

0 голосов
/ 09 мая 2018

Вот решение для первой части вашего цикла:

Вам следует попробовать это на одноразовой локальной копии вашей базы данных и проверить, что все работает хорошо, прежде чем запускать их в производство:

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)
...