In Django, if you have models that use multi-table inheritance, and you define a receiver for a post_save signal on the parent class, does that receiver function get called when an instance of the child class is saved?
Borrowing an example from another question:
class Animal(models.Model):
category = models.CharField(max_length=20)
class Dog(Animal):
color = models.CharField(max_length=10)
def echo_category(sender, **kwargs):
print "category: '%s'" % kwargs['instance'].category
post_save.connect(echo_category, sender=Animal)
If I do:
>>> dog = Dog.objects.get(...)
>>> dog.category = "canine"
>>> dog.save()
Will the echo_category receiver function be called?
post_save.connect(my_handler, ParentClass)
# connect all subclasses of base content item too
for subclass in ParentClass.__subclasses__():
post_save.connect(my_handler, subclass)
have a nice day!
Check out:
https://code.djangoproject.com/ticket/9318
It appears that most propagate the signal to the super in the subclass.
No, it will not be called. See #9318 in Django trac.
I managed to get inherited signal receivers working with the #receiver decorator. See relevant Django documentation
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class Animal(models.Model):
category = models.CharField(max_length=20)
#receiver(post_save)
def echo_category(sender, **kwargs):
print ("category: '%s'" % kwargs['instance'].category)
class Dog(Animal):
color = models.CharField(max_length=10)
This solution is valid in Python 3.6.8 Django 2.2
When I do this
>>> from myapp.models import Dog
>>> dog = Dog()
>>> dog.category = "canine"
>>> dog.save()
category: 'canine'
>>>
No problems. Everything seems to work from the shell.
Slightly unrelated, but when I edited models through the admin panel There was an issue with it getting called twice so I filtered them by checking the 'created' kwarg. In one call it was false, the other it was true so I just put in a simple if block.
Credit for that workaround goes to Pratik Mandrekar and his answer:
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class Animal(models.Model):
category = models.CharField(max_length=20)
#receiver(post_save)
def echo_category(sender, **kwargs):
if not kwargs.get('created'):
print ("category: '%s'" % kwargs['instance'].category)
class Dog(Animal):
color = models.CharField(max_length=10)
Related
I have a proxy model.
class ProxyModel(ParentModel):
objects = ProxyModelManager()
class Meta:
proxy = True
I'm trying to define my proxy model as a sender.
#receiver(post_save, sender=core_models.ProxyModel)
def test_receiver(sender, instance, **kwargs):
print("test receiver")
But the function is not called after the model is saved.
Is it possible to define proxy model as sender? If so how can I do ?
Thanks.
Edit: I noticed that, If I save the object like so,
>>> from core.models Import ProxyModel
>>> p = ProxyModel.objects.get(name="Object Name")
>>> p.save()
>>> test receiver
The function is being called and prints "test receiver".
But, If I save same object via admin panel through parent model, It doesn't.
from django.contrib.auth.models import User
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
# Create your models here.
class FollowersModel(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1, related_name='usr')
follow = models.ManyToManyField(User, related_name="whom")
my_followers = models.ManyToManyField(User, related_name='my_followers')
update = models.DateTimeField(auto_now=True)
timestamp = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return str(self.user)
#receiver(m2m_changed, sender=FollowersModel.follow.through)
def follow_by_follow_add_or_remove(sender, **kwargs):
if kwargs.get('action') == 'post_add':
print kwargs.get('instance').follow.all()
**solved part**
print list(kwargs.get('pk_set'))[0]
# then it returns the id of the added object :)))
In this example when I print kwargs.get('instance').follow.all() then I can get the whole lists of follow label but I just want to get only the added one, I mean I am searching kind of print kwargs.get('instance').follow.this(), just like jquery we do 'this.append()' only the added one
Okey Bros It is Solved
solved part is in the codes :))
From the documentation of m2m_changed signal:
pk_set
For the pre_add, post_add, pre_remove and post_remove actions, this is a set of primary key values that have been added to or removed from the relation
You can access pk_set from the kwargs to check which primary keys values have been added/removed.
You can use this pk_set to fetch the corresponding objects from database.
I have two models:
class A(models.Model):
title = models.CharField(max_length=100)
a_bg_img = models.ImageField(upload_to='./bg/')
class A_B(models.Model):
title = models.CharField(max_length=100)
b_bg_img = # this should just refer to A's a_bg_img field
b_bg_img should just refer to a_bg_img Field so that I dont save one Image twice. can I just say:
b_bg_img = models.TextField()
and then save there only the link to Image in a_bg_img ?
If you have an image associated with a class A object, then you can easily retrieve the path of that image or url of that image using .path( or get_path()) or .url.
For example:
a = A.objects.get(id=1)
print(a.a_bg_img.path)
'/path/to/image/in/your/local/drive/image.jpg'
print(a.a_bg_img.get_path())
'/path/to/image/in/your/local/drive/image.jpg'
print(a.a_bg_img.url)
'/url/to/your/image'
You can use the django signals post_save to perform actions after the models save()
How ?
create a signals.py file in your app
import it in __init__.py of your main app folder
Inside the signals.py app create function like below:
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=A)
def my_action(sender, instance, **kwargs):
# Your function magic
I have an app that I'm calling Progress. I want to have several different apps throughout my project write to it whenever something happens. But all the different apps are not the same in how they broadcast progress. So I thought that a ContentType solution would work.
The only trick that I'm having a hard time figuring out is that I need to write to the Progress app when an event occurs. Such as when a view renders. I've been trying get_or_create but I'm having trouble getting the right configuration in the queryset. Any suggestions for how to correct this?
I want the get_or_create to sit in the view of an app so that the action I want is what writes to the progress.
My Progress Model.py
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from datetime import datetime
class Progress(models.Model):
"""
The page log. Records the user and the object.
"""
user = models.ForeignKey(User, related_name='user_pagelog')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
stamp = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (('user', 'content_type', 'stamp'),)
verbose_name = 'View Log'
verbose_name_plural = 'View Logs'
get_latest_by = 'stamp'
def __str__(self):
return "%s got to %s on %s" % (self.user, self.content_type, self.stamp)
#classmethod
def get_latest_view(cls, user):
"""
Get most recent view log value for a given user.
"""
try:
view_log = cls.objects.filter(user=user).order_by('-stamp')[0]
return view_log.value
except IndexError:
return None
An example of the queryset that I want to write to the Progress app:
Progress.objects.get_or_create(user=request.user, content_type=f.id)
Where f = get_object_or_404(Page, publish=True)
Finally, the error I'm getting:
Cannot assign "1": "Progress.content_type" must be a "ContentType" instance.
Which I think it means the instance isn't getting found? But it exists, so I'm confused.
Thanks.
No, it doesn't mean that at all. It means what it says, that the parameter has to be an instance of the ContentType model, whereas you're passing the ID of the object itself.
You might be able to use content_type along with the actual instance:
Progress.objects.get_or_create(user=request.user, content_object=f)
Otherwise you'll need to get the right ContentType using the get_for_model() method, and pass that along with the object id.
Is posible to define an attribute in a data model like an object of other data model in Django?
This is the scenary:
models.py
class Inmueble(models.Model):
calle = models.CharField(max_length=20, verbose_name="Calle")
numero = models.CharField(max_length=6, verbose_name="Numero")
piso = models.IntegerField(verbose_name="Piso", blank=True, null=True)
galeria_id = models.OneToOneField(Galeria, verbose_name="Galería del Inmueble")
class Galeria(Gallery):
nombre = models.CharField(max_length=30, verbose_name="Nombre")
The point is: I need to create a new Galeria object automatically every time an Inmueble object is created. Thanks in advance!
Analía.
There are two ways to handle this:
Override the save() method for the Inmueble model.
Create a signal handler on Galeria that receives signals emitted by Inmueble
Both methods would work and are acceptable, however I recommend using a signal for a couple reasons:
It's a bit more de-coupled. If later you change or remove Galeria, your code doesn't break
The signal handler for postsave includes a boolean value to indicate whether the model is being created or not. You could technically implement the same functionality in model save() by checking if the model has a .id set or not, but IMO the signal is a cleaner solution.
Here's an idea of the code for both of these...
Using a Signal (recommended)
from django.db.models.signals import post_save
from wherever.models import Inmueble
class Galeria(Gallery):
# ...
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance.galeria_id = Galeria.objects.create()
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')
Overriding Model save() Method
from wherever.models import Galeria
class Inmueble(models.Model):
# ...
def save(self, force_insert=False, force_update=False):
# No Id = newly created model
if not self.id:
self.galeria_id = Galeria.objects.create()
super(Inmueble, self).save()
Maybe
AutoOneToOneField is the answer.
Finally, I did:
from django.db.models.signals import post_save
class Galeria(Gallery):
inmueble_id = models.ForeignKey(Inmueble)
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance = Galeria.objects.create(inmueble_id=instance, title=instance.calle+' '+instance.numero, title_slug=instance.calle+' '+instance.numero)
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')