I am trying to implement a custom signal but I have never done this before, so I seem to have messed this up.
Here is my code from models.py:
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save, post_delete
from django.dispatch import Signal
from django.db.models import Max,Count
from django.apps import apps
# Create your models here.
class Player(models.Model):
player_name = models.CharField(max_length=150)
current_level_no = models.IntegerField(null=True)
no_of_moves = models.IntegerField(null=True)
class Meta:
verbose_name_plural = 'Players'
def __str__(self):
return self.player_name
class PlayerStats(models.Model):
player_name = models.ForeignKey(to=Player, on_delete=models.CASCADE)
level_no = models.IntegerField(null=True)
moves = models.IntegerField(null=True)
class Meta:
verbose_name_plural = 'Players Stats'
# It works!
TotalLevels = 25
MaxCurrentLevel = PlayerStats.objects.aggregate(max_levels=Max('level_no'))['max_levels']
PlayerCount = Player.objects.aggregate(count_players=Count('player_name', distinct=True))['count_players']
def create_player(sender, instance, created, **kwargs):
if created:
new_username=instance.username
Player.objects.create(player_name=new_username, current_level_no=None, no_of_moves=None)
def delete_player(sender, instance, **kwargs):
deleted_username=instance.username
Player.objects.filter(player_name=deleted_username).delete()
def create_player_stat(sender, instance, **kwargs):
for x in range(1, TotalLevels+1):
PlayerStats.objects.create(player_name=instance, level_no=x, moves=None)
UpdateLevelsSignal = Signal(providing_args=['Update'])
if MaxCurrentLevel != TotalLevels and PlayerCount != 0:
UpdateLevelsSignal.send(UpdateLevelsSignal,Update=True)
def UpdateLevels(sender, Update,**kwargs):
if Update:
if MaxCurrentLevel < TotalLevels:
for x in Player.objects.all().values_list('player_name', flat=True):
instance = Player.objects.get(player_name=x)
for y in range(TotalLevels-MaxCurrentLevel, TotalLevels+1):
PlayerStats.objects.create(player_name=instance, level_no=y, moves=None)
else:
for x in Player.objects.all().values_list('player_name', flat=True):
instance = Player.objects.get(player_name=x)
for y in range(MaxCurrentLevel-TotalLevels, MaxCurrentLevel+1):
PlayerStats.objects.filter(player_name=instance, level_no=y, moves=None).delete()
post_save.connect(create_player, sender=User)
post_delete.connect(delete_player, sender=User)
post_save.connect(create_player_stat, sender=Player)
UpdateLevelsSignal.connect(UpdateLevels, sender=UpdateLevelsSignal)
Basically a signal to create or delete some model objects based on certain conditions, nothing too fancy. But, when I check on the model objects, there are no changes occurring on triggering the conditions.
If someone could point out what I am doing wrong here or suggest a solution, that would be helpful!
As always, I greatly appreciate your answers!
This isn't going to solve your question, but I don't want to include it on a comment. The reason you should move your signals and receivers into their own files is limit how much business logic is in your models file. Plus it makes it more extensible when dealing with multiple apps.
project/
app1/
apps.py
models.py
signals.py
receivers.py
app2/
apps.py
models.py
receivers.py
In each of the apps above, the apps.py file would be something like:
class App1Config(AppConfig):
def ready(self):
# This makes Django load up the register the connected receivers.
from project.app1 import receivers, signals
The reason you want to move the receivers into their own files is partly due to the case of wanting to trigger a function that only impacts models/objects in app2, but is triggered by some event on a model in app1. For example:
#project/app2/receivers.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from project.app1.models import Foo
from .models import Bar
#receiver(post_save, sender=Foo)
def create_bar_for_foo(instance, created, *, raw=False, **kwargs):
if created:
Bar.objects.create(foo=foo)
You could do that receiver in app2/models.py, but it would feel out of place. It would feel especially alien if it were a custom signal that has nothing to do with model-based events.
I wish the Django Documentation attempted to explain how to organize the signals and receivers, but the book Two Scoops of Django does a great job of explaining it.
Related
Good evening,
I know, I asked a similar Question just a while ago, but I still got problems in understanding the more detailled functions of a signal. So I'm hoping someone can help me out with this one!? As an example I have a class in my "models.py" like this one:
class ExampleModel(models.Model):
name = models.CharField(max_length=255, null=False, blank=False)
value1 = models.IntegerField()
value2 = models.IntegerField()
value_of_1_and_2 = models.IntegerField()
def __str__(self):
return self.name
I would like to have my field "value_of_1_and_2" filled automatically with the sum of the other two fields before saving...
Is this possible? I tried some things and got stuck with a "pre_save" like this:
#receiver(pre_save, sender=ExameplModel)
def save(self, *args, **kwargs):
...
Thanks for all of your help and a nice evening to all of you!
Although the signals do logically similar things, they are slightly different from the save method in the model class. To be more specific, they answer the following questions in your head:
What do I want to do before the model is saved/deleted?
What do I want to do after the model is saved/deleted?
Others ..
If we come to what you want; If I understood correctly, you want to leave value_of_1_and_2 field blank while saving your model, and you want django to save it in the background with a method you set.
First of all, I prefer to keep the signals and models apart for a clean look. So next to the models.py file, create the signals.py file.
signals.py :
from .models import ExampleModel
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save, sender=ExampleModel)
def pre_save_example_model(sender, instance, *args, **kwargs):
if not instance.value_of_1_and_2:
instance.value_of_1_and_2 = instance.value1 + instance.value2
Then make the following definition in apps.py for the signals to work (Let's consider the name of the application as Summing. You can replace it with your own.):
apps.py :
from django.apps import AppConfig
class SummingConfig(AppConfig):
name = 'summing'
def ready(self):
from . import signals
And the most important thing here is: since you will leave the value_of_1_and_2 field empty, you should update your field as follows. Otherwise Django will raise the error.
value_of_1_and_2 = models.IntegerField(blank=True, default=0)
That's all. Save the model by entering value1 and value2, then you will see the result.
I'm trying to print some text after Django model in an app has been saved.
I have created a signal for that in a signals.py file in the same application.
However, it's not working as expected (i.e., the function is not being called and text is not being printed)
But if I place the receiver function in the models.py file just below the model that I created, it's working fine (i.e., the function is being called and text has been printed)
I have gone through the documentation to check if there is any need to place the signals in a specific file or location. It looks like there isn't any such restriction.
https://docs.djangoproject.com/en/3.0/topics/signals/#django.dispatch.receiver
Why is this behaving differently if there is no such restriction?
signals.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from aws_envs.models import UserStack
#receiver(post_save, sender=UserStack)
def create_user_stack(sender, **kwargs):
print("creating user stack now")
models.py:
class UserStack(BaseModel):
name = models.CharField(max_length=50)
email = models.EmailField(unique=True, max_length=50, blank=False)
enabled = models.BooleanField(default=True)
def save(self, *args, **kwargs):
print(f"Creating stack with data: {self.name, self.email}")
super(UserStack, self).save(*args, **kwargs)
def __str__(self):
return self.name, self.email
in INSTALLED_APPS you should register like this:
'post.apps.PostConfig'
I.e. in settings.py replace
INSTALLED_APPS = (
'...',
'post,
)
with
INSTALLED_APPS = (
'...',
'post.apps.PostConfig',
)
in apps.py you shoud add these:
from django.apps import AppConfig
class postConfig(AppConfig):
name = 'post'
def ready(self):
# signals are imported, so that they are defined and can be used
import post.signals
created a file in app's folder
# post/signals.py
from django.dispatch import receiver
from django.db.models.signals import post_save
from post.models import Post
def send():
print("send email!")
#receiver(post_save, sender=Post, dispatch_uid='Post_post_save')
def send_email(instance, **kwargs):
send()
I have this class In which when I submit a name it goes to admin and only admin can approve this. I want that when admin approve a email automatically should be sent to user.
class myab(models.Model):
generic_name = models.CharField(max_length=50, null=False)
timestamp = models.DateTimeField(auto_now_add=True)
is_approved = models.BooleanField(null=False, default=False)
I only wanted to know how to trigger email code . I have everything else just wanted to understand how to trigger that function when Admin will approve this post.
You could make a listener function to the post-save signal and use a if to check if the instance was approved; read the signal docs for a better understanding.
The signal receiver could look similar to this:
from django.core import mail
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(post_save, sender=MyModel)
def my_handler(sender, instance, created, *args, **kwargs):
...
if instance.is_approved:
mail.send_mail(...)
Highlighting this section of the docs:
Where should this code live?
[...] signal handling [...] code can live
anywhere you like, although it’s recommended to avoid the
application’s root module and its models module [...]
In practice, signal handlers are usually defined in a signals
submodule of the application they relate to [...]
Lets suppose we have the following classes:
class Cart(models.Model):
total = models.DecimalField(default=0.00, max_digits=100, decimal_places=2)
and
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE)
I'd like to update the total everytime a Cart-Item is added to the Cart. When/ How can I call my addItUp() method here?
Thought about using a signal like this, but this doesn't trigger:
#receiver(post_save, sender=CartItem, weak=False)
def post_save_cartItem_receiver(sender, instance, *args, **kwargs):
print("inside signal")
Here's my very simplified addToCart() method:
def add(self, request, product_id, cart_obj):
product = get_object_or_404(Product, id=product_id)
CartItem.objects.create(cart=cart_obj, ...)
cart_obj.save()
return True
This is my first question here, if i can better my explanation please tell me.
Welcome to StackOverflow.
A common problem that people have when creating triggers is that they forget (or don't know) to import signals in their apps.py file. That's the only reason that I can imagine your signal wouldn't be triggering.
I imagine your apps.py file should look something like (if the name of your app is Shop):
from django.apps import AppConfig
class ShopConfig(AppConfig):
name = 'Shop'
def ready(self):
import Shop.signals
You can then use the post_save signal for a CartItem to get its related Cart, add up the total value, and save the Cart with a new total.
I have a question about django.
I have ManyToMany Models here
class Product(models.Model):
name = models.CharField(max_length=255)
price = models.DecimalField(default=0.0, max_digits=9, decimal_places=2)
stock = models.IntegerField(default=0)
def __unicode__(self):
return self.name
class Cart(models.Model):
customer = models.ForeignKey(Customer)
products = models.ManyToManyField(Product, through='TransactionDetail')
t_date = models.DateField(default=datetime.now())
t_sum = models.FloatField(default=0.0)
def __unicode__(self):
return str(self.id)
class TransactionDetail(models.Model):
product = models.ForeignKey(Product)
cart = models.ForeignKey(Cart)
amount = models.IntegerField(default=0)
For 1 cart object created, I can insert as many as new TransactionDetail object (the product and amount). My question is. How can I implement the trigger? What I want is whenever a Transaction detail is created, I want the amount of the product's stock is substracted by the amount in the transactiondetail.
I've read about post_save() but I'm not sure how to implement it.
maybe something like this
when:
post_save(TransactionDetail,
Cart) #Cart object where TransactionDetail.cart= Cart.id
Cart.stock -= TransactionDetail.amount
If you really want to use signals to achieve this, here's briefly how,
from django.db.models.signals import post_save
from django.dispatch import receiver
class TransactionDetail(models.Model):
product = models.ForeignKey(Product)
# method for updating
#receiver(post_save, sender=TransactionDetail, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
instance.product.stock -= instance.amount
instance.product.save()
Personally I would override the TransactionDetail's save() method and in there save the new TransactionDetail and then run
self.product.stock -= self.amount
self.product.save()
If you want to avoid getting maximum recursion depth exceeded, then you should disconnect signals, before saving within the signal handler. The example above (Kenny Shen's answer), would then be:
from django.db.models.signals import post_save
from django.dispatch import receiver
class TransactionDetail(models.Model):
# ... fields here
# method for updating
#receiver(post_save, sender=TransactionDetail, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
instance.product.stock -= instance.amount
post_save.disconnect(update_stock, sender=TransactionDetail)
instance.product.save()
post_save.connect(update_stock, sender=TransactionDetail)
This is described thoroughly in Disconnect signals for models and reconnect in django, with a more abstract and useful example.
Also see: https://docs.djangoproject.com/en/2.0/topics/signals/#disconnecting-signals in the django docs.
If you really want to use signals in django please try this:
#import inbuilt user model
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def create_profile(sender, **kwargs):
# write you functionality
pass
then add default_app_config in the init file
default_app_config = "give your AppConfig path"
In fact, the docstrtring explains the Signals is in django.dispatch.Signal.connect:
def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
Connect receiver to sender for signal.
Arguments:
receiver
A function or an instance method which is to receive signals.
Receivers must be hashable objects.
If weak is True, then receiver must be weak referenceable.
Receivers must be able to accept keyword arguments.
If a receiver is connected with a dispatch_uid argument, it
will not be added if another receiver was already connected
with that dispatch_uid.
sender
The sender to which the receiver should respond. Must either be
a Python object, or None to receive events from any sender.
weak
Whether to use weak references to the receiver. By default, the
module will attempt to use weak references to the receiver
objects. If this parameter is false, then strong references will
be used.
dispatch_uid
An identifier used to uniquely identify a particular instance of
a receiver. This will usually be a string, though it may be
anything hashable.