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.
Related
I am building a web app, where each product has its own "Profile". I need to add to the model some kind of field where i can add "Comments", with date and text, for keeping track of info such as change in formula, change of provider, change in price, etc.
Any ideas?
models.py
from django.db import models
# Create your models here.
class Horse(models.Model):
name = models.CharField(max_length=255)
nacimiento = models.DateField(blank=True, null=True)
nro = models.IntegerField()
event = models.TextField()
slug = models.SlugField(unique=True)
def __str__(self):
return '%s-%s' % (self.name, self.nro)
So for every event that happens, i need a new entrance with the description provided in the text field.
class HorseTracker(models.Model):
horse = models.ForeignKey(Horse, on_delete=models.CASCADE, related_name='horse')
comment = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
Each time you change something in your model you can create new instance of HorseTracker with description of changes you've made.
To make it more usefull you can use TabularInline in your HorseAdmin
class HorseTrackerInline(admin.TabularInline):
model = HorseTracker
class HorseAdmin(admin.ModelAdmin):
list_display = ['name', 'nacimiento', 'nro', 'event', 'slug', ]
inlines = [ HorseTrackerInline, ]
If you want to track various models I would suggest to use something like django-simple-history to keep track of the changes in your model.
Adding a history field to the model lets you save all the changes made to the fields and then access the history. If you want to add a custom message you can add fields to the historical model, and in a signal set the message.
from simple_history.models import HistoricalRecords
class MessageHistoricalModel(models.Model):
"""
Abstract model for history models tracking custom message.
"""
message = models.TextField(blank=True, null=True)
class Meta:
abstract = True
class Horse(models.Model):
name = models.CharField(max_length=255)
birthdate = models.DateField(blank=True, null=True)
nro = models.IntegerField()
event = models.TextField()
slug = models.SlugField(unique=True)
history = HistoricalRecords(bases=[MessageHistoricalModel,])
Then using signals you can get changes using diff and then save a custom message stating the changes an who made them.
from django.dispatch import receiver
from simple_history.signals import (post_create_historical_record)
#receiver(post_create_historical_record)
def post_create_historical_record_callback(sender, **kwargs):
history_instance = kwargs['history_instance'] # the historical record created
# <use diff to get the changed fields and create the message>
history_instance.message = "your custom message"
history_instance.save()
You could generate a pretty generic signal that works for all your models tracked with a 'history' field.
Note: I renamed "nacimiento" as "birthdate" to keep consistency in naming all the fields in english.
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()
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
I have a pretty simple model that works:
class Badge(models.Model):
name = models.CharField(max_length=16, help_text="Name for Badge")
category = models.ForeignKey(BadgeCategory, help_text="Category for badge")
description = models.CharField(max_length=32, help_text="A brief description")
file = models.ImageField(upload_to=format_badge_name)
signals.post_save.connect(create_badge, sender=Badge)
I know my create_badge function in signals.py works. If I send it without a value for sender, it says the sender is a LogEntry object. I want/need to reference some of the instance information in the post_save script like below:
def create_badge(sender, instance, created, **kwargs):
from userinfuser.ui_api import UserInfuser
from django.conf import settings
if created:
api_key = settings.API_KEY
api_email = settings.API_EMAIL
ui = UserInfuser(api_email, api_key)
ui.create_badge(instance.category.name, instance.name, instance.description, instance.file.url)
Where can I call my post_save call so it's aware of Badge (I'm assuming this is the fix?
Thanks.
Just connect the signal with sender=Badge after Badge is defined, tested example:
from django.db import models
from django.db.models import signals
def create_badge(sender, instance, created, **kwargs):
print "Post save emited for", instance
class BadgeCategory(models.Model):
name = models.CharField(max_length=100)
class Badge(models.Model):
name = models.CharField(max_length=16, help_text="Name for Badge")
category = models.ForeignKey(BadgeCategory, help_text="Category for badge")
description = models.CharField(max_length=32, help_text="A brief description")
signals.post_save.connect(create_badge, sender=Badge)
Test shell session:
In [1]: category = BadgeCategory(name='foo')
In [2]: category.save()
In [3]: badge = Badge(category=category, name='bar', description='test badge')
In [4]: badge.save()
Post save emited for Badge object
am working on a concept in which I want to capture certain information when a model is saved. To understand the full picture, I have a app core with the following model
core/models.py
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from transmeta import TransMeta
from django.utils.translation import ugettext_lazy as _
import signals
class Audit(models.Model):
## TODO: Document
# Polymorphic model using generic relation through DJANGO content type
operation = models.CharField(_('Operation'), max_length=40)
operation_at = models.DateTimeField(_('Operation At'), auto_now_add=True)
operation_by = models.ForeignKey(User, null=True, blank=True, related_name="%(app_label)s_%(class)s_y+")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Audit model is a generic content type, am currently attaching it with other apps such as in blog
blog/models.py
from django.db import models
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
# Create your models here.
class Meta:
verbose_name_plural = "Categories"
class article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(editable=False, unique_for_year=True)
content = models.TextField()
is_active = models.BooleanField()
published_at = models.DateTimeField('Publish at',auto_now=True)
related_articles = models.ManyToManyField('self', null=True, blank=True)
audit_obj = generic.GenericRelation('core.Audit', editable=False, null=True, blank=True)
My first attempt was, I made a post_save signal in which I was checking if the instance passed containing audit_obj attribute and then saving a record in using article.audit_obj.create().save().
Unfortunately, this did not entirely work out for me since I cannot pass the request nor I can access the request to retrieve the user information.
So, I was thinking to create a custom signal and override the form_save method (if there is such a thing) and then using arguments to pass the request object as well as the model object.
Any advice on how I can do that?
Regards,
EDIT (20th of Jan, 2011):
Thanks #Yuji for your time. Well, what am trying to achieve is to keep my code as DRY as possible. What I want to do ultimately, every time I create new model, I will only create an additional attribute and name it audit_obj and I will create a single piece of code, either a signal or to override the save method inside the django core itself. The peiece of code will always check if an attribute with the following name exists and therefore creates a record in aduti table.
I'd just create a function in my model class or Manager and call it from my form save (wherever yours might be)
class AuditManager(models.Manager):
def save_from_object(self, request, obj):
audit = Audit()
audit.object_id = obj.id
audit.operation_by = request.user
# ...
audit.save()
class Audit(models.Model):
## TODO: Document
# Polymorphic model using generic relation through DJANGO content type
operation = models.CharField(_('Operation'), max_length=40)
operation_at = models.DateTimeField(_('Operation At'), auto_now_add=True)
operation_by = models.ForeignKey(User, null=True, blank=True, related_name="%(app_label)s_%(class)s_y+")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
objects = AuditManager()
class MyBlogForm(forms.ModelForm):
class Meta:
model = article # btw I'd use Capital Letters For Classes
def save(self, *args, **kwargs):
super(MyBlogForm, self).save(*args, **kwargs)
Audit.objects.save_from_object(request, self.instance)