Сигналы Django: конфликты с моделями, унаследованными от одного класса - PullRequest
0 голосов
/ 07 июня 2019

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

Модели (упрощенные)

В основном у меня есть Артикул , Фото (наследование от Пост )

class Post(models.Model): 
    class Meta:
        abstract        = True

    some_field    = models.Something()

class Article(Post):
    category = models.ForeignKey(Article_category, null=True, on_delete=models.SET_NULL)
    some_field     = models.Something()

class Photo(Post):
    category = models.ForeignKey(Photo_category, null=True, on_delete=models.SET_NULL)
    some_field     = models.Something()

и их соответствующие категории

class Category(models.Model):
    class Meta: 
        abstract = True 

    parent = models.ForeignKey('self', null=True, blank=True, related_name='nested_category', on_delete=models.SET_NULL)
    name   = models.CharField(max_length=50)
    count  = models.PositiveSmallIntegerField(default=0, editable=False)

class Article_category(Category):

    @classmethod
    def load(cls):
        cache.set('{}'.format(cls.__name__), cls.objects.all()) 

class Photo_category(Category):

    @classmethod
    def load(cls):
        cache.set('{}'.format(cls.__name__), cls.objects.all())

сигнал

Простой инкрементный счетчик. Каждый раз, когда создается статья / фотография, обновляется соответствующий ей счетчик категорий, и вся модель сохраняется в кеше (для шаблонов)

from django.db.models import F

@receiver(post_save, sender=Article) ----> here comes trouble
@receiver(post_save, sender=Photo)
def add_one_to_count(sender, instance, **kwargs):
    cat = type(instance.category).objects.get(name=instance.category)
    cat.count = F('count')+1
    cat.save()
    cache.set('{}_category'.format(sender.__name__), type(instance.category).objects.all())

Проблема

То, что вы видели выше, работает как талисман для @receiver(post_save, sender=Photo), но когда я добавляю @receiver(post_save, sender=Article), инициализация БД с помощью fixture завершается неудачно, и я получаю только пустые таблицы ( mariaDB ). Эта самая линия - единственное, что меняет успех, и я не могу понять, почему. Поскольку в абстрактном классе определено count , я подумал, не связано ли это с ним, поскольку у меня не было проблем с применением подобной логики к категориям:

# this works perfectly
@receiver(post_save, sender=Photo_category)
@receiver(post_delete, sender=Photo_category)
@receiver(post_save, sender=Article_category)
@receiver(post_delete, sender=Article_category)
def refresh_cached_category(sender, instance, using, **kwargs):
    cache.set('{}'.format(type(instance).__name__), type(instance).objects.all())

Спасибо за любое просвещение

Полная трассировка

 Traceback (most recent call last):
   File "manage.py", line 21, in <module>
     main()
   File "manage.py", line 17, in main
     execute_from_command_line(sys.argv)
   File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
     utility.execute()
  File "/usr/local/lib/python3.7/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 72, in handle
    self.loaddata(fixture_labels)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 114, in loaddata
    self.load_label(fixture_label)
  File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/loaddata.py", line 181, in load_label
    obj.save(using=self.using)
  File "/usr/local/lib/python3.7/site-packages/django/core/serializers/base.py", line 223, in save
    models.Model.save_base(self.object, using=using, raw=True, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/db/models/base.py", line 790, in save_base
    update_fields=update_fields, raw=raw, using=using,
  File "/usr/local/lib/python3.7/site-packages/django/dispatch/dispatcher.py", line 175, in send
    for receiver in self._live_receivers(sender)
  File "/usr/local/lib/python3.7/site-packages/django/dispatch/dispatcher.py", line 175, in <listcomp>
    for receiver in self._live_receivers(sender)
  File "/usr/src/cms/website/observers.py", line 26, in add_one_to_count
    cat = type(instance.category).objects.get(name=instance.category)
  File "/usr/local/lib/python3.7/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py", line 408, in get
    self.model._meta.object_name
 website.models.DoesNotExist: Problem installing fixture '/usr/src/cms/../test/data_dev.yaml': Article_category matching query does not exist.

1 Ответ

1 голос
/ 07 июня 2019

Вы не можете фильтровать по name=instance.category в своем запросе, потому что это не str. Вам нужно отфильтровать по name=instance.category.name, но сначала нужно убедиться, что instance.category не None (так как это может быть).

Я не понимаю, зачем вам сначала выполнять запрос, просто чтобы получить тот же объект: instance.category - это то же самое, что и ArticleCategory.objects.get(name=instance.category.name), если предположить, что имя уникально, за исключением того, что вы делаете дополнительный запрос в БД.

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

def add_one_to_count(sender, instance, **kwargs):
    if instance.category:
        instance.category.count = F('count')+1
        instance.category.save()
        cache.set('{}_category'.format(sender.__name__), type(instance.category).objects.all())
...