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 [...]
Related
I have a problem, probably in my design.
In my Django project I have a 'Flow' object with 'event_reason' field in it, Foreign key to 'Event' object, therefore I need to import Event object, (otherwise I get the error: Cannot create form field for 'event_reason' yet, because its related model 'Event' has not been loaded yet). Therefore my code of my models is (in models/flows.py)
from .events import Event
class Flow(models.Model):
amount = models.DecimalField(max_digits=17, decimal_places=2, default=0.0)
flow_type = models.IntegerField(default=-1, choices=FLOW_CHOICES)
...
event_reason = models.ForeignKey('Event', on_delete=models.DO_NOTHING, default=None, related_name='events_flow',blank=True, null=True)
class FlowForm(ModelForm):
class Meta:
model = Flow
fields = '__all__'
In addition I have 'amount' and 'flow_type' fields in the 'Flow' objects and I want that the 'post_save' signal will save a new Flow associated to this Event. Therefore the Event model code is (in models/events.py):
from .flows import Flow
class Event(models.Model):
...
amount = models.IntegerField(default=0)
flow_type = models.IntegerField(default=-1, choices=FLOW_CHOICES)
#receiver(post_save, sender=Event)
def my_handler(sender, **kwargs):
new_flow = Flow.objects.create(...)
And I get the error ImportError: cannot import name 'Flow' from partially initialized module 'firstapp.models.flows' (most likely due to a circular import)
I understand that I cannot my a circular import but I want this functionality to work. How to solve this problem? Should I design the objects otherwise?
Thank you
The models.py should mostly only have model code, sometimes people do put signals (bad practice) but forms should absolutely not be written there.
One should write forms in forms.py so your should move that code to a separate file and import the model there. The forms etc. should not be imported in the models as their should be no need for them there.
So in forms.py:
from .models.flows import Flow
class FlowForm(ModelForm):
class Meta:
model = Flow
fields = '__all__'
# Other forms also go here
Now for signals it is best not to write them in models as I said before. They should be written in some file let's say signals.py and this will be imported in the apps config classes ready method.
So in signals.py:
# imports
#receiver(post_save, sender=Event)
def my_handler(sender, **kwargs):
new_flow = Flow.objects.create(...)
Now your app should have an app config by default in which you would add an import in the ready method. So in your apps apps.py just add an import in the ready method:
class YouAppConfig(AppConfig):
# some attributes here don't change
def ready(self):
from . import signals
Now remove those circular imports in your model files.
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.
My models.py:>
class Aval(models.Model):
cliente = models.ForeignKey(Cliente)
salao = models.ForeignKey(Salao)
rate = models.IntegerField(choices=RATE, default=5)
criacao = models.DateTimeField(blank=True, null=True, auto_now=True)
comentario = models.TextField(max_length=400, blank=True, null=True, default=None)
aprovado = models.BooleanField(default=False)
My signals.py:>
#receiver(post_save, sender=Aval)
def new_rate(sender, instance, created, **kwargs):
aval = instance
print("Aval is saved.")
I'm testing the signal post_save for Aval model, When I'm save some object Aval it not printing "Aval is saved" . What I'm doing wrong ?
Otiginal answer for Django < 1.7:
You should include:
import signals
to __init__.py file of your application.
Edit: Django >= 1.7:
Signals can be registered in django.apps.AppConfig.ready
as described in Signals documentation and AppConfig doc
Where should this code live?
Strictly speaking, signal handling and registration code can live
anywhere you like, although it’s recommended to avoid the
application’s root module and its models module to minimize
side-effects of importing code.
In practice, signal handlers are usually defined in a signals
submodule of the application they relate to. Signal receivers are
connected in the ready() method of your application configuration
class. If you’re using the receiver() decorator, import the signals
submodule inside ready().
I dont know if the paste is wrong, but in that code your model is named Avaliacao and not Aval, The Model and the sender argument has to match
So in any Django project, it is fairly simple to tell if an object has been created by overriding the save method:
def save(self, *args, **kwargs):
created = False
if not self.pk:
created = True
super(ModelName, self).save(*args,**kwargs)
if created:
#do what you want or call a signal
But I need to call a function after a User is created from django.contrib.auth.models.User. I would prefer not to have to actually edit the user model's save method.
How could I go about doing this?
Note:
using pre_save will not work because none of the info from the model will be available, and that is necessary.
You can use post_save signal.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save)
def do_your_thing(sender, **kwargs):
if sender is User:
if kwargs["created"]:
print kwargs["instance"].password
# do your thing
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.