Preventing duplicate signals for post_save in Django 1.4.3 - python

I'm trying to write some code that sends an email every time one of the users modifies a model object. Currently, I'm working on having the one of the methods in models.py receive a post_save signal. I realize it's a well known fact that the post_save signal is usually sent twice, thus, the workaround is to utilize the dispatch_uid parameter. I have done this, but for some strange reason, I continue to receive two signals. Here's the code in my app's model.py file.
from django.db import models
from django.db.models.signals import post_save
def send_email(sender, **kwargs):
print "Signal sent." #just a placeholder
post_save.connect(send_email, dispatch_uid="unique_identifier")
class Library_Associates (models.Model):
first_name = models.CharField(max_length = 200)
last_name = models.CharField(max_length = 200)
department_choices = (
('ENG', 'Engineering'),
('ART', 'Arts and Sciences'),
('AFM', 'Accounting and Financial Managment'),
('MAT', 'Mathematics'),
)
department = models.CharField(max_length = 3, choices = department_choices, default = 'ENG')
pub_date = models.DateTimeField ('date published')
def __unicode__(self):
return self.first_name
class Meta:
verbose_name_plural = 'Library Associates'
class Info_Desk_Staff (models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
salary = models.IntegerField()
hours_worked = models.IntegerField()
def __unicode__(self):
return self.first_name
class Meta:
verbose_name_plural = 'Info Desk Staff'
I already restarted the server several times, reset/deleted all the data for the app and I continue to still receive two signals. Is there something inherently wrong with my code? Any suggestions or insight would be greatly appreciated! Thanks!

Your problem comes from the fact that each time you modify an object via the admin interface, admin app creates the django.contrib.admin.models.LogEntry instance that represents changes made.
Because you are listening to post_save on all objects, your listener is called twice - once for your model, and the second time for the LogEntry model.
List of possible solutions includes:
Registering your listener separately for each of your models (e.g. select your models somehow and do it in a loop) using the sender argument in the post_save method.
for model in get_models():
post_save.connect(send_email, sender = model, dispatch_uid='unique_identifier')
Check if the sender sent to the listener is not an instance of django.contrib.admin.models.LogEntry
from django.contrib.admin.models import LogEntry
...
def send_email(sender, **kwargs):
if isinstance(sender, LogEntry):
return
Give your models a common super class and use that for testing in the listener
class MyModel(models.Model):
pass
class Library_Associates (MyModel):
...
class Info_Desk_Staff (MyModel):
...
def send_email(sender, **kwargs):
if not isinstance(sender, MyModel):
return

Related

Django signal reciever not called after connecting to pre_delete signal

Goal
I'm trying to implement a voting system that tracks when a user makes a vote on a specific object in the database. I'm tracking this using an intermediate UserVotemodel with a foreign key to the object that actually contains all the votes. Whenever the UserVote gets deleted I want to remove the vote on the related object.
What I've tried
Since bulk deletes do not call the model.delete() method, I want to accomplish the above by listening to the pre_delete signal.
When I test the below receiver function using the Django test runner, everything behaves as expected
from myapp.models.users import UserVote
from django.db.models.signals import pre_delete
from django.dispatch import receiver
#receiver(pre_delete)
def user_vote_handler(sender, instance, **kwargs):
if sender in UserVote.__subclasses__():
# remove the vote from the related model
Problem
When I call delete() on a model instance in views.py, the function is not called. If I add print statements to the receiver function as below, none of the statements are printed.
from myapp.models.users import UserVote
from django.db.models.signals import pre_delete
from django.dispatch import receiver
#receiver(pre_delete)
def user_vote_handler(sender, instance, **kwargs):
print('function called')
if sender in UserVote.__subclasses__():
print('condition met')
# remove the vote from the related model
I've read through the Signal Documention and I haven't found the source of the problem. I've put the receiver function in myapp.signals and in myapp.models.users and the behavior is the same. I have a feeling I've done something stupid and haven't realized it. Can anyone shed some light on what could be going wrong? I'm using Django 2.2.
Code
# models.records.py
...
class Record(models.Model):
...
# some functions for all Record subclasses
....
class Author(Record):
name = models.CharField(max_length=255)
comments = models.ManyToManyField(
Comment,
through='ForumComment',
through_fields=('author', 'comment'),
)
...
# some functions specific to this
....
...
# more records
...
# models.comments.py
...
class RecordComment(models.Mode):
text = label = models.CharField(max_length=255)
...
# some methods
...
class AuthorComment(RecordComment):
...
# methods specific to AuthorComment
...
...
# more RecordComment subclass
...
class Comment(models.Model):
class Meta:
abstract = True
up_votes = models.PositiveIntegerField(blank=True, default=0)
down_votes = models.PositiveIntegerField(blank=True, default=0)
class ForumComment(Comment):
author = models.ForeignKey('myapp.Author', on_delete=models.CASCADE, verbose_name='related record')
comment = models.ForeignKey(AuthorComment, on_delete=models.CASCADE)
...
# more types of comments
...
# models.users.py
...
class User(AbstractUser):
pass
class UserVote(models.Model):
"""shared fields and functions for UserVote models"""
class Meta:
abstract = True
DOWN_VOTE = 'DOWN'
UP_VOTE = 'UP'
VOTE_CHOICES = [
(DOWN_VOTE, 'Down vote'),
(UP_VOTE, 'Up vote'),
]
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
vote = models.CharField(choices=VOTE_CHOICES, default=UP_VOTE, max_length=255)
...
# methods to handle voting
...
class ForumCommentUserVote(UserVote):
forum_comment = models.ForeignKey('myapp.ForumComment', on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['forum_comment', 'user'], name='unique_author_tag_assignment_user_vote')
]
...
# some more methods
...
...
# more UserVote subclasses
...
in you app app.py file add import for signals it should look like this
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
and in the settings.py you should add your app like this
'users.apps.UsersConfig',
this should trigger the pre_delete signal, :D i spent a whole day once thinking i did something wrong when i actually i just forgot these 2 lol

How to run function if model instance field is equal to inplay (Django)

I have a model that's being updated by a background task every few seconds.
I would like to execute a function when the instance of the attribute status changes to inplay
I have looked through documentation and examples but can a't find what I'm looking for. Would signals be the best option to call a function after model instance field changes to
inplay'?
from django.db import models
class testModel(models.Model):
player1 = models.CharField(null=True, max_length=50)
player2 = models.CharField(null=True, max_length=50)
Player1_odds = models.FloatField(null=True)
Player2_odds = models.FloatField(null=True)
status = models.CharField(null=True, max_length=10)
complete = models.CharField(null=True, max_length=10)
from django.dispatch import receiver
from django.db.models.signals import pre_save, pre_delete, post_save,
post_delete
from django.dispatch import receiver
#receiver(post_save, sender=testModel)
def post_save(sender, instance, created, **kwargs):
# if status is = inplay call send
#
#
pass
def send()
# run bet
You should choice overriding save method rather than signals because your changes are specific to testModel only. So this is how you would override save method:
class testModel(models.Model):
status = models.CharField(null=True, max_length=10)
# your other model fields
def save(self):
super(testModel, self).save() # this will save model
if self.status == 'inplay':# this will check if the status is "inplay" after change
send()
Yes you can use signals for that. In your case you can get the update status from the instance.
#receiver(post_save, sender=testModel)
def post_save(sender, instance, created, **kwargs):
if instance.status == 'inplay':
send()

The use of ForeingKey on django when I create an object is also creating an object from the foreignkey model

I have a function on the main app of the website i'm working on that creates a User - the model name is "Usuario" (different from the default User model that django provides). The model is:
class Usuario(models.Model):
email = models.CharField(max_length=250)
data_cadastro = models.DateTimeField(default=now)
telefone = models.CharField(validators=[phone_regex], max_length=17, blank=True) # validators deve ser uma lista
renda = models.DecimalField(default=0,max_digits=14,decimal_places=2)
longitude = models.DecimalField(default=0,max_digits=20,decimal_places=12)
latitude = models.DecimalField(default=0,max_digits=20,decimal_places=12)
(the names of the fields are in Portuguese)
This model is connected to a different one in another app by a simple foreing key (usuario). The other model is:
from core.models import Usuario
class Simulacao(Usuario):
usuario = models.ForeignKey(Usuario, related_name="Usuario_contas", on_delete=models.CASCADE)
data_simulacao = models.DateTimeField(default=now, verbose_name="Data simulação")
tem_conta = models.BooleanField(default=False)
class Meta:
verbose_name = 'Simulação'
verbose_name_plural = 'Simulações'
def __str__(self):
return self.nome
def __unicode__(self):
return self.nome
The function where I create the "Simulacao" object is this one:
from .models import Simulacao, Tipo_Pessoa
from core.models import Usuario
from django.utils.timezone import now
def cadastro_simulacao(form, email):
nova_simulacao = Simulacao.objects.create(usuario = Usuario.objects.get(email = email), tem_conta=form['tem_conta'])
usuario.Usuario_contas_set.add(nova_simulacao)
usuario.save()
All i've done is based on the Django documentation: https://docs.djangoproject.com/pt-br/2.1/topics/db/examples/many_to_one/
I have also tried the function like that:
from .models import Simulacao, Tipo_Pessoa
from core.models import Usuario
from django.utils.timezone import now
def cadastro_simulacao(form, email):
nova_simulacao = Simulacao.objects.create(usuario = Usuario.objects.get(email = email), tem_conta=form['tem_conta'])
nova_simulacao.save()
However, when it's done, a new object of "Usuario" is created with no information (no email, etc, all fields are created blank) and the information of the user doesn't appear properly at the "simulacao" model - the information that was supposed to be inherited from the "usuario" shows up blank too.
Why is that happening? What I want is to catch the information of the User that has the same email and create a "simulacao" object that is connected to this user. I don't want a new object of user. How can I do that?
Your Simulacao inherits from Usario. It shouldn't do that; you have a separate ForeignKey. Remove that inheritance so that Simulacao inherits directly from models.Model.

Django - signals. Simple examples to start

I am new to Django and I'm not able to understand how to work with Django signals. Can anyone please explain "Django signals" with simple examples?
Thanks in advance.
You can find very good content about django signals over Internet by doing very small research.
Here i will explain you very brief about Django signals.
What are Django signals?
Signals allow certain senders to notify a set of receivers that some action has taken place
Actions :
model's save() method is called.
django.db.models.signals.pre_save | post_save
model's delete() method is called.
django.db.models.signals.pre_delete | post_delete
ManyToManyField on a model is changed.
django.db.models.signals.m2m_changed
Django starts or finishes an HTTP request.
django.core.signals.request_started | request_finished
All signals are django.dispatch.Signal instances.
very basic example :
models.py
from django.db import models
from django.db.models import signals
def create_customer(sender, instance, created, **kwargs):
print "Save is called"
class Customer(models.Model):
name = models.CharField(max_length=16)
description = models.CharField(max_length=32)
signals.post_save.connect(receiver=create_customer, sender=Customer)
Shell
In [1]: obj = Customer(name='foo', description='foo in detail')
In [2]: obj.save()
Save is called
Apart from the explanation given by Prashant, you can also use receiver decorator present in django.dispatch module.
e.g.
from django.db import models
from django.db.models import signals
from django.dispatch import receiver
class Customer(models.Model):
name = models.CharField(max_length=16)
description = models.CharField(max_length=32)
#receiver(signals.pre_save, sender=Customer)
def create_customer(sender, instance, created, **kwargs):
print "customer created"
For more information, refer to this link.
In the signals.post_save.connect(receiver=create_customer, sender=Customer)... sender will always be the model which we are defining... or we can use the User as well in the sender.
Signals are used to perform any action on modification of a model instance. The signals are utilities that help us to connect events with actions. We can develop a function that will run when a signal calls it. In other words, Signals are used to perform some action on modification/creation of a particular entry in Database. For example, One would want to create a profile instance, as soon as a new user instance is created in Database
There are 3 types of signal.
pre_save/post_save: This signal works before/after the method save().
pre_delete/post_delete: This signal works before after delete a model’s instance (method delete()) this signal is thrown.
pre_init/post_init: This signal is thrown before/after instantiating a model (init() method).
One of the example, if we want to create a profile of a user as soon as the user is created using post_save signal.
For code example, I found the Geeks for Geeks, which explains is very simple way, and easy to understand.
https://www.geeksforgeeks.org/how-to-create-and-use-signals-in-django/
You can add signals to your models.py file
here is an example for adding an auto slug, if you use a SlugField:
this is the stuff you need to import
from django.utils.text import slugify
from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save
Add the #receiver to the bottom of your class, included the def
If you add the def __str__(self): under the receiver, you will get an error
class Store(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=False, blank=True, null=True)
def __str__(self):
return self.name
#receiver(pre_save, sender=Store)
def store_pre_save(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.name)
or you can use post_save
class Store(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=False, blank=True, null=True)
def __str__(self):
return self.name
#receiver(post_save, sender=Store)
def store_post_save(sender, instance, created, *args, **kwargs):
if not instance.slug:
instance.slug = slugify(instance.name)
instance.save()
I found this example from this tutorial

Django: How to update the field of another model during object creation

I have a simple chat app.
class Thread(models.Model):
participants = models.ManyToManyField(User)
last_message_time = models.DateTimeField(null=True, blank=True)
class NewMessage(models.Model):
message = models.CharField(max_length=500)
sender = models.ForeignKey(User)
thread = models.ForeignKey(Thread, related_name = 'thread_to_message')
datetime = models.DateTimeField(auto_now_add=True)
Every time a a NewMessage object is created, I would like to update the last_message_time in the Thread model with the datetime from the NewMessage object that was just created. How can I go about doing this?
The simplest way is probably with a post_save signal handler for NewMessage.
from django.db.models.signals import post_save
def update_thread(sender, **kwargs):
instance = kwargs['instance']
created = kwargs['created']
raw = kwargs['raw']
if created and not raw:
instance.thread.last_message_time = instance.datetime
instance.thread.save()
post_save.connect(update_thread, sender=NewMessage)
You could also use a custom save method on NewMessage.

Categories

Resources