3

I've just setup inheritance on my models, this makes much better sense for my project - everything inherits from Item class, with common elements. Then additional fields are added to child classes - Video, Podcast, Article etc...

Previously, upon Item.save() I had some pre_save and post_save signals setup which parsed/performed various tasks on my Item model objects.

 @receiver(pre_save, sender=Item)
def some_function(sender, instance, *args, **kwargs):
   print("I am working")

However, now that my objects are all subclassing from Item, those signals don't seem to be firing.

>Article.objects.get_or_create(title='some title')

 @receiver(pre_save, sender=Article)
def some_function(sender, instance, *args, **kwargs):
   print("I am not doing anything")

models.py



class Item(models.Model, AdminVideoMixin):
    title = models.TextField(max_length=2000)
    slug = models.SlugField(max_length=500, default='')
    link = models.URLField(max_length=200)

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(get_random_string(length=5,allowed_chars='1234567890') + '-' + self.title)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('curate:item_detail',args=[self.slug])

    def __str__(self):
        return self.title

class Video(Item):
    video_embed = EmbedVideoField(max_length=300)
    channel = models.TextField(max_length=500, blank=True

    def __str__(self):
        return self.title.upper()

class Article(Item):
    authors = models.CharField(max_length=10000)
    movies = models.TextField(max_length=10000)

    def __str__(self):
        return self.title.upper()

class Podcast(Item):
    authors = models.CharField(max_length=10000)
    itune_image = models.CharField(max_length=10000)
    owner_name = models.CharField(max_length=10000)

    def __str__(self):
        return self.title.upper()


class Episode(Item):
    authors = models.CharField(max_length=10000)
    itune_image = models.CharField(max_length=10000)
    owner_name = models.CharField(max_length=10000)
    enclosure_url = models.URLField(max_length=2000)
    owner = models.ForeignKey(Podcast, on_delete=models.CASCADE,max_length=2000)

    class Meta:
        pass

    def __str__(self):
        return self.title.upper()

I even stacked up the recievers as per this thread in case it's supposed to be triggered by the parent class as well as child class, but still it didn't save.

@receiver(post_save, sender=Video)
@receiver(post_save, sender=Item)
@receiver(post_save, sender=Article)
def some_function(sender, instance, *args, **kwargs):

    print('This still didn't fire')

Also tried to use post_save.connect(some_function, sender=Article) and I couldn't get signals to fire. I'm creating the objects with get_or_create(attributes)

Will I have to manually write something to go in afterwards and perform pre-post/saves or am I doing something wrong? I saw a solution but this suggest sending the signal to all child classes - no matter which - but this isn't what I want. I'd like to set model specific post_save / pre_save signals rather than a blanket rule.

phil0s0pher
  • 525
  • 10
  • 21
  • 1
    `save()` doesn't send the pre-save/post-save signal to the parent classes (because it's not guaranteed the parent tables will actually be saved, since that only needs to happen when fields on the parent model are affected). `delete()` does propagate to parent classes btw, so signal handlers work for e.g. `post_delete`. But, stacking all the classes as receiver should work, so the reason that doesn't work lies elsewhere. Are you sure your signal handlers are processed (in a file that gets read at startup)? And did you restart your server after changing them? – dirkgroten May 15 '19 at 11:48
  • Also the solution you mention doesn't "send the signal to all child classes", it connects the receiver to all child classes, exactly what your want (basically the same as stacking the receivers, but without the hassle of remembering to add one when you create a new subclass later on). – dirkgroten May 15 '19 at 11:51
  • I'm using the exact same signals.py script (inside the apps folder) as before - I import all the signals I might need at the top of the script. from django.contrib.auth.models import User from django.db.models.signals import post_save, pre_save – phil0s0pher May 15 '19 at 12:24
  • And I can confirm the objects are saving - they are in my admin page and I can engage with them in shell. – phil0s0pher May 15 '19 at 12:26
  • Thanks for the help - you're right, the problem was coming from elsewhere. I was using a package called newspaper - but it actually imports as Article. So I had two 'Article' objects in my signals.py, and Python/Django didn't throw an error. I re-imported newspaper under a different name ArticleNewspaper – phil0s0pher May 15 '19 at 12:32

1 Answers1

1

I think had a similar problem when I wanted to add a pre_save signal for all my sub model calsses but I wanted to implement at one place only. The solution was possible with python 3.6+ becuse it require __init_subclass__ magic method.

The problem that the child models sub classes are not automatically registered for the signal. So instead of manually register the signals you could register the signal for the child models in the init_subclass function

The code example below shows the jist of the implementation:

Class BaseModel:
      common_field = models.CharField(...)
      
     @classmethod
        def __init_subclass__(cls, **kwargs):
            super().__init_subclass__(**kwargs)
            # automatically bind the pre_save for the sub model
            pre_save.connect(generate_common_field, cls)
    
    
   def generate_common_field:
     pass

Class SubModel(BaseModel):
    pass
Gabor
  • 352
  • 5
  • 14