Nonso
Nonso

Reputation: 46

I am trying to get all updated value in a model and send an email to users about the changes in django

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

Answers (2)

rtindru
rtindru

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

Lemayzeur
Lemayzeur

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

Related Questions