Джанго сигнализирует об изменении «многие ко многим» - PullRequest
0 голосов
/ 01 мая 2018

У меня есть 3 модели:

class Product(TimeStampedModel):
    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-Category отношение M2M после того, как я изменю Product.category.
Я пытаюсь подключить сигнал m2m_changed, как описано в документах :

@receiver(m2m_changed, sender=Brand.categories.through)
def category_changed(sender, **kwargs):
    print("Signal connected!")

Также я зарегистрировал signal в apps.py в project_folder:

  def ready(self):
        from my_app.signals import category_changed

Но проблема в том, что этот код не имеет никакого эффекта. Я меняю Product.category - и не вижу никаких отпечатков. Как мне исправить это, чтобы оно заработало?

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Это правильный способ обработки изменений поля Many2Many. Django Docs

from django.db.models.signals import m2m_changed

m2m_changed.connect(category_changed,sender=Brand.categories.through)

РЕДАКТИРОВАТЬ: Я не понял, что Chiefir запрашивает сигнал для QuerySet update метод, для которого мы не можем использовать встроенный сигнал m2m_changed. Как указывает Ральф в предыдущем ответе, запуск сигнала об обновлении может вызвать проблемы с производительностью, например, в случае прослушивания сохраненного сквозного файла. Тем не менее, есть решение, которое imho не несет проблем с производительностью.

По сути, вы можете переопределить метод обновления набора запросов, чтобы запускать пользовательский сигнал при каждом обновлении всего набора запросов. Это будет срабатывать только один раз для каждого обновления набора запросов, и вы можете указать, что оно будет запускаться только для одного конкретного обновления поля, проверив аргументы обновления.

Теперь сигнал отправляет обновляемый набор запросов и значение в качестве полезной нагрузки. Помните, что в этом коде сигнал отправляется до обновления категории. Чтобы изменить это, сохраните супер выход как переменную, отправьте сигнал и верните переменную.

from django.dispatch import Signal, receiver

product_category_updated = Signal(providing_args=["queryset", "value"])


class ProductQuerySet(models.QuerySet):

    def update(self, *args, **kwargs):
        if 'category' in kwargs:
            product_category_updated.send(sender=self.__class__, queryset=self, value=kwargs.get('category'))
        return super(ProductQuerySet, self).update(*args, **kwargs)

class ProductManager(models.Manager):

    def get_queryset(self, show_hidden=False):
        return ProductQuerySet(self.model, using=self._db, hints=self._hints)

class Product(TimeStampedModel):
    objects = ProductManager()

    category = models.ForeignKey('Category', related_name='products', to_field='category_name', on_delete=models.deletion.CASCADE)
    brand = models.ForeignKey('Brand', related_name='products', to_field='brand_name', on_delete=models.deletion.CASCADE)

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

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

@receiver(product_category_updated, sender=ProductQuerySet)
def category_changed(sender, **kwargs):
    print("Signal connected!")

Мне пришлось настроить код для моей версии Django 2.0.7, добавив атрибуты on_delete в поля ForeignKey и сделав уникальные brand_name и category_name.

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

В комментарии вы сказали, что хотите получить сигнал для следующего оператора обновления:

Product.objects.filter(category = 'cat_AA').update(category = 'cat_BB')

Согласно этому вопросу , документам django для сигналов и документам django для запросов , метод update() вызывает NOT сигнал post_save.

Таким образом, вам нужно будет найти другой способ достичь своей цели, например, выполнить итерацию по каждому элементу и вызвать метод save (но он будет медленнее).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...