Django signal reciever not called after connecting to pre_delete signal - python

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

Related

Django related model not updating related object in admin

I have 2 models that look like this:
models.py
class Client(models.Model):
deal = models.ManyToManyField('Deal', related_name="clients")
class Deal(models.Model):
client = models.ManyToManyField(Client, related_name="deals")
Then in the admin, I have inlined the related models to make it easy to make changes regardless of the object type you have open.
admin.py
class ClientInline(admin.TabularInline):
model = Deal.client.through
class DealAdmin(admin.ModelAdmin):
inlines = [ClientInline]
class DealInline(admin.TabularInline):
model = Client.deal.through
class ClientAdmin(admin.ModelAdmin):
inlines = [DealInline]
However, if you add a Client to a Deal and then open the Client detail page, the corresponding deal does not appear. Is there something I'm not connecting?
It is enough to have relation define only in one model. Otherwise you'll have 2 separate tables for separate ManyToMany relation: ClientDeal and DealClient.
What you need to do is to choose which one you need to leave. And probably update Admin inlines according to Django Admin documentation
class Client(models.Model):
deals = models.ManyToManyField('Deal', related_name="clients")
class Deal(models.Model):
pass
Yes, If you're using models.manytoMany() , you have to put it only in one model. no the two
But there's a very good attribute you should use: through
with through attribute you can create a intermediate model. here there's an example:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=200)
groups = models.ManyToManyField('Group', through='GroupMember', related_name='people')
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=200)
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
class GroupMember(models.Model):
person = models.ForeignKey(Person, related_name='membership')
group = models.ForeignKey(Group, related_name='membership')
type = models.CharField(max_length=100)
def __unicode__(self):
return "%s is in group %s (as %s)" % (self.person, self.group, self.type))
later, you can use your inline admin class!
I just tested this an you were actually really close.
First, #wowkin2 said, you don't want to define a ManyToManyField in both models so I would probably just define it in your Deal model.
Second, replace this:
class DealInline(admin.TabularInline):
model = Client.deal.through
with this:
class DealInline(admin.TabularInline):
model = Deal.client.through
And everything should work.
So, this is what your files should now look like:
models.py
class Deal(models.Model):
client = models.ManyToManyField(Client, related_name="deals")
admin.py
class ClientInline(admin.TabularInline):
model = Deal.client.through
class DealAdmin(admin.ModelAdmin):
inlines = [ClientInline]
class DealInline(admin.TabularInline):
model = Deal.client.through
class ClientAdmin(admin.ModelAdmin):
inlines = [DealInline]

django model instance method not being called

I want to update my model upon login (to check the authorizations of a person from an external system).
The code of my model looks as follow:
import json
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.signals import user_logged_in
from django.db import models
class Person(AbstractUser):
is_dean = models.BooleanField(null=False, blank=False, default=False)
is_institute_manager = models.BooleanField(null=False, blank=False, default=False)
managed_institutes = models.TextField(blank=True, null=True, default=None)
def get_managed_institutes(self):
return json.loads(self.managed_institutes)
def set_managed_institutes(self, value):
self.managed_institutes = json.dumps(value)
# Signals processing
def check_authorizations(sender, user, request, **kwargs):
...
# check if the user is dean
is_dean = False
# logic to check if the user is dean...
user.is_dean = is_dean
# Check if the user manages institutes
is_institute_manager = False
managed_institutes = list()
# Logic to check if the user is managing institutes ...
user.is_institute_manager = is_institute_manager
user.set_managed_institutes = managed_institutes
user.save()
user_logged_in.connect(check_authorizations)
Surprisingly, the boolean flags get set correctly, but the method set_managed_institute never gets called...
I am quite convinced this a trivial mistake from my end, but I can't figure it out.
That is not how you call methods in Python. You need to do so explicitly:
user.set_managed_institutes(managed_institutes)
Or did you mean to define a property?
#property
def managed_institutes(self):
return json.loads(self._managed_institutes)
#managed_institutes.setter
def managed_institutes(self, value):
self._managed_institutes = json.dumps(value)
But also note, you probably want to use a JsonField anyway. If you're using PostgreSQL, there is one defined in Django directly; otherwise there are several third-party libraries that take care of serializing and deserializing your data on load/save.

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

Edit attributes of a child model from the form representing parent model

I have a person model with different attributes. A person has an Address, which is a OneToOneField for the Person/parent model. When i go for admin form in adding/editing the person, i also need to edit the address attributes like line1, line2, mobile and so on. Similarly, i want to delete some person from the admin page and it should delete the matching references like address as well. I have already tried lots of things, including Inline and all. But, i think inline could have worked only if the address would be having a foreign key with the person, reverse was not possible. Any kind of help on this would be highly appreciated...
class Person(models.Model):
person_Id = models.CharField(max_length=32L, primary_key=True, db_column='PERSON_ID', editable=False)
business_Address = models.OneToOneField(Address, unique=True, db_column='BUSINESS_ADDRESS_ID')
class Meta:
db_table = 'PD_PERSON'
class Address(models.Model):
# Field names made lowercase.
address_id = models.IntegerField(primary_key=True, db_column='ADDRESS_ID')
address_name = models.CharField(max_length=256L, db_column='NAME', blank=True)
line1 = models.CharField(max_length=128L, db_column='LINE1', blank=True)
class Meta:
db_table = 'PD_ADDRESS'
In admin.py ~
class PersonInline(admin.TabularInline):
model = Person
class PersonAdmin(admin.ModelAdmin):
list_display = ('customer_Id', 'complete_Name', 'company')
search_fields = ('name', 'customer_Id', 'email_Id')
class AddressAdmin(admin.ModelAdmin):
inlines = [ PersonInline, ]
admin.site.register(Address, AddressAdmin)
#admin.site.register(Category)
#admin.site.register(Languages)
admin.site.register(Person, PersonAdmin)
Problem 1: delete adress on person deletion
You can use post_delete signal to automatically delete the adress once your person is deleted. It's quite simple to use
from django.db.models.signals import post_delete
from web.programing.roxx.models import Person, Adress
#receiver(post_delete, sender=Person, dispatch_uid="person_post_delete")
def onPersonDelete(sender, instance, using, **kwargs):
# be carefull, the instance doesn't exist anymore in the database
adress_id = instance.business_Address.id
if id != None:
Adress.objects.filter(id=adress_id).delete()
But this won't display the adress in the "Are you sure ?" message..
Problem 2: edit the adress in the person admin page
For the moment, I don't see any solution simpler than building a custom form to edit Person model, and override both add_view() and change_view() methods to initialize this form withe the O2O key..

Preventing duplicate signals for post_save in Django 1.4.3

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

Categories

Resources