Django post_save() signal implementation - python

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.

Related

Custom django signal not updating my models

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.

Admin side modification django

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 [...]

Delete object from database automatically if one field is TRUE

Automatically delete an object from the database if one attribute of the object is TRUE.
I've tried Django Signals, but it didn't help.
class Question(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField(max_length=50)
question = models.TextField(max_length=200)
answered = models.BooleanField(default=False)
def __str__(self):
return self.name
If I change the "answered" field to TRUE in Admin Panel, then this object must be automatically deleted from the database.
You will need post_save signals by using something like:
from .models import Question
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Question)
def save_profile(sender, instance, **kwargs):
if instance.answered:
instance.delete()

How to add custom function to admin forms?

I would like to implement a function that updates quantity in LibraryBook each time the admin adds a book in SingleBook on the admin site. I have been searching for means to do so but to no avail. Any pointers including links to documentation would be very much appreciated.
Here is my code:
#models.py
class LibraryBook(models.Model):
book_title = models.CharField(max_length=100, blank=False)
book_author_id = models.ForeignKey(BookAuthors, on_delete=models.CASCADE)
category = models.ForeignKey(BookCategory, on_delete=models.CASCADE)
quantity = models.IntegerField(blank=False, default=0)
number_borrowed = models.IntegerField(default=0)
def __unicode__(self):
return unicode(self.book_title)
class SingleBook(models.Model):
serial_number = models.CharField(primary_key=True , max_length=150, blank=False)
book_id = models.ForeignKey(LibraryBook, on_delete=models.CASCADE)
is_available_returned = models.BooleanField(default=True)
is_borrowed = models.BooleanField(default=False)
def __unicode__(self):
return unicode(self.book_id)
#admin.py
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
class LibraryBookAdmin(admin.ModelAdmin):
list_display = ('book_title', 'book_author_id', 'quantity')
search_fields = ('book_title', 'book_author_id')
fields = ('book_title', 'book_author_id', 'quantity')
PS: I have omitted the import and admin.site.register code
Django==1.9.8
django-material==0.8.0
django-model-utils==2.5.1
psycopg2==2.6.2
wheel==0.24.0
override save_model
If you only want to make the changes when an admin updates a record, the best way is to override the save_model method in ModelAdmin
The save_model method is given the HttpRequest, a model instance, a
ModelForm instance and a boolean value based on whether it is adding
or changing the object. Here you can do any pre- or post-save
operations.
class SingleBookAdmin(admin.ModelAdmin):
list_display = ('book_id', 'serial_number')
def save_model(self, request, obj, form, change):
admin.ModelAdmin.save_model(self, request, obj, form, change)
if obj.is_borrowed:
do something to obj.book_id.quantity
else:
do something to obj.book_id.quantity
post_save signal
from django.dispatch.dispatcher import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=SingleBook)
def user_updated(sender,instance, **kwargs):
''' Fired when a SingleBook is updated or saved
we will use the opporunity to change quantity'''
# your logic here
Other pointers
If on the other hand, you wanted to make changes based on all user actions, catching the post_save signal is the way to go. In either case, you might want to override the from_db method in the model to keep track of which fields have changed.
You might also want to change quantity and number_borrowed to IntegerFields (unless you are only using sqlite in which case it doesn't matter)
Also book_author_id should probably be book_author and book_id should probably be book (this is not a rule, just a convention to avoid the ugly book_id_id reference)
Use signals. Just attach post_save signal to SingleBook model and update according LibraryBook in it. post_save signal takes created argument, so you can determine if book is newly created or edited and apply your action based on that.
Also attach post_delete signal to decrease counter when SingleBook is removed.
To avoid race conditions (when 2 admins are adding books at the same time), I'm suggesting use of queryset update method together with F on changing LibraryBook counter, example:
LibraryBook.objects.filter(id=single_book.book_id_id).update(quantity=F('quantity') + 1)
Doing it that way will ensure that actual math operation will be performed on database level.

Populating django field with pre_save()?

class TodoList(models.Model):
title = models.CharField(maxlength=100)
slug = models.SlugField(maxlength=100)
def save(self):
self.slug = title
super(TodoList, self).save()
I'm assuming the above is how to create and store a slug when a title is inserted into the table TodoList, if not, please correct me!
Anyhow, I've been looking into pre_save() as another way to do this, but can't figure out how it works. How do you do it with pre_save()?
Is it like the below code snippet?
def pre_save(self):
self.slug = title
I'm guessing not. What is the code to do this?
Thanks!
Most likely you are referring to django's pre_save signal. You could setup something like this:
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.template.defaultfilters import slugify
#receiver(pre_save)
def my_callback(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
If you dont include the sender argument in the decorator, like #receiver(pre_save, sender=MyModel), the callback will be called for all models.
You can put the code in any file that is parsed during the execution of your app, models.py is a good place for that.
#receiver(pre_save, sender=TodoList)
def my_callback(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
you can use django signals.pre_save:
from django.db.models.signals import post_save, post_delete, pre_save
class TodoList(models.Model):
#staticmethod
def pre_save(sender, instance, **kwargs):
#do anything you want
pre_save.connect(TodoList.pre_save, TodoList, dispatch_uid="sightera.yourpackage.models.TodoList")
The pre_save() signal hook is indeed a great place to handle slugification for a large number of models. The trick is to know what models need slugs generated, what field should be the basis for the slug value.
I use a class decorator for this, one that lets me mark models for auto-slug-generation, and what field to base it on:
from django.db import models
from django.dispatch import receiver
from django.utils.text import slugify
def autoslug(fieldname):
def decorator(model):
# some sanity checks first
assert hasattr(model, fieldname), f"Model has no field {fieldname!r}"
assert hasattr(model, "slug"), "Model is missing a slug field"
#receiver(models.signals.pre_save, sender=model, weak=False)
def generate_slug(sender, instance, *args, raw=False, **kwargs):
if not raw and not instance.slug:
source = getattr(instance, fieldname)
slug = slugify(source)
if slug: # not all strings result in a slug value
instance.slug = slug
return model
return decorator
This registers a signal handler for specific models only, and lets you vary the source field with each model decorated:
#autoslug("name")
class NamedModel(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField()
#autoslug("title")
class TitledModel(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField()
Note that no attempt is made to generate a unique slug value. That would require checking for integrity exceptions in a transaction or using a randomised value in the slug from a large enough pool as to make collisions unlikely. Integrity exception checking can only be done in the save() method, not in signal hooks.
Receiver functions must be like this:
def my_callback(sender, **kwargs):
print("Request finished!")
Notice that the function takes a sender argument, along with wildcard keyword arguments (**kwargs); all signal handlers must take these arguments.
All signals send keyword arguments, and may change those keyword arguments at any time.
Reference here.

Categories