Reputation: 46
i have a model A which has a many to one relationship with another model B,i want to be able to get send an email notification to emails which will be queried from model B any time values change in model A how can i do that in django?
class ModelA(models.Model):
"""
model to store informations about the ModelA
"""
name = models.CharField(max_length=15, blank=True, unique=True)
creator = models.ForeignKey(AppUser, blank=True, null=True, on_delete=models.CASCADE)
periodicity = models.CharField(max_length=10, blank=False, choices=PERIODICITY_CHOICES)
begin_date = models.DateField(blank=False)
end_date = models.DateField(blank=True, null=True)
subscription_fee = models.DecimalField(max_digits=5, decimal_places=2, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
public = models.BooleanField(default=False)
maximum_sub = models.PositiveIntegerField(blank=True, null=True, validators=[MinValueValidator(2)])
def save(self):
emails = []
body = """
Hello <user first name or nothing>,
This is to notify you that ModelA you're subscribed to has been updated. Here is what has changed:
<field_name_1>: <current_value>
<field_name_2>: <current_value>
etc...
Thank you
"""
if self.pk is not None:
modelA_id = ModelA.objects.get(pk=self.pk)
modelA_members = ModelB.objects.filter(pk=modelA_ID)
emails = [i for i in modelA_members.email]
send_mail('ModelA change Notification', body, "[email protected]", [emails])
super(ModelB, self).save() #
def __unicode__(self):
return u'{0}'.format(self.name)
class ModelB(models.Model):
"""
defineModelB
"""
modelA = models.ForeignKey(ModelA, on_delete=models.CASCADE, blank=False)
user = models.ForeignKey(AppUser, on_delete=models.CASCADE, blank=False, )
cash_out_date = models.DateField(blank=True, null=True)
Upvotes: 2
Views: 243
Reputation: 5347
Okay, so this is one way to solve your problem of detecting the "changed fields" and autosending the email:
Here's how it works. The mixin overrides the class init and saves the existing values in an attribute called self.__initial
. Your new values are in self._dict
and the difference between the two are the changed values, this is captured by the property diff
. We can leverage diff
to get a dictionary of {key: (old_value, new_value)}
, which you can then use in your templates
class ModelA(ModelDiffMixin, models.Model):
pass # Your code goes here
def save(self):
changed_fields = self.diff
for key, old, new in changed_fields.items():
# Do something here with the values
emails = []
body = """
Hello <user first name or nothing>,
This is to notify you that ModelA you're subscribed to has been updated. Here is what has changed:
<field_name_1>: <current_value>
<field_name_2>: <current_value>
etc...
Thank you
"""
if self.pk is not None:
modelA_id = ModelA.objects.get(pk=self.pk)
modelA_members = ModelB.objects.filter(pk=modelA_ID)
emails = [i for i in modelA_members.email]
send_mail('ModelA change Notification', body, "[email protected]", [emails])
super(ModelB, self).save() #
class ModelDiffMixin(object): # Credit to ivanperelivskiy here: https://stackoverflow.com/questions/1355150/django-when-saving-how-can-you-check-if-a-field-has-changed
"""
A model mixin that tracks model fields' values and provide some useful api
to know what fields have been changed.
"""
def __init__(self, *args, **kwargs):
super(ModelDiffMixin, self).__init__(*args, **kwargs)
self.__initial = self._dict
@property
def diff(self):
d1 = self.__initial
d2 = self._dict
diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
return dict(diffs)
@property
def has_changed(self):
return bool(self.diff)
@property
def changed_fields(self):
return self.diff.keys()
def get_field_diff(self, field_name):
"""
Returns a diff for field if it's changed and None otherwise.
"""
return self.diff.get(field_name, None)
def save(self, *args, **kwargs):
"""
Saves model and set initial state.
"""
super(ModelDiffMixin, self).save(*args, **kwargs)
self.__initial = self._dict
@property
def _dict(self):
return model_to_dict(self, fields=[field.name for field in
self._meta.fields])
Upvotes: 1
Reputation: 8525
You have done well by overriding the save method, to access all modelB linked to modelA instance, you have to use the reverse query related_name
Class ModelA(models.Model):
def save(self,*args,**kwargs):
if self.pk is not None:
body = """
Hello <user first name or nothing>,
This is to notify you that ModelA you're subscribed to has been updated. Here is what has changed:
<field_name_1>: <current_value>
<field_name_2>: <current_value>
etc...
Thank you
"""
modelB_members = self.modelb_set.all()
emails = [i.user.email for i in modelB_members]
send_mail('ModelA Change Notification', body, "[email protected]", [emails])
super(ModelA, self).save(*args,**kwargs) #
class ModelB(models.Model):
modelA = models.ForeignKey(ModelA, on_delete=models.CASCADE, blank=False)
user = models.ForeignKey(AppUser, on_delete=models.CASCADE, blank=False, )
cash_out_date = models.DateField(blank=True, null=True)
Upvotes: 0