I encountered a strange behavior while applying a signal to a new model, I'm not sure to understand what is wrong but it seems related with the fact that I used abstract classes.
Basically, I have Article, Photo (inheriting from Post)
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()
and their respective Categories
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())
A straighforward incremental counter. Every time an article/photo is created, it's corresponding category count is updated and the entire model is saved in the cache (for templating purposes)
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())
What you saw above works like a charm for @receiver(post_save, sender=Photo)
but when I add @receiver(post_save, sender=Article)
, DB initialization with fixture fails and I only get emptyset tables (mariaDB). This very line is the only one changing fail to success and I can't figure why. Since count is defined in the abstract class, I wondered whether it had something to do with it, for I did not have any issue applying a similar logic to categories:
# 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())
Thanks for any enlightenment
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.
You can't filter on name=instance.category
in your query, because that's not a str
. You need to filter on name=instance.category.name
but first you also need to make sure instance.category
isn't None
(since it can be).
The thing I don't understand is why you perform a query in the first place, just to fetch the same object: instance.category
is the same as ArticleCategory.objects.get(name=instance.category.name)
assuming the name is unique, except you do an extra query to the db.
Also the query will raise an exception if you have two categories with the same name
(which you don't exclude in your model). So your code should be:
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())