0

I want to send notification emails to a list of users defined in a 'observers' ManyToMany field when a new post is created.

The post is created without errors and the list of observer users are added to it successfully (they appear in the post_detail.html template), but the notification email is never sent to the observer users.

I think I'm doing something wrong in the new_post function below, which I adapted from this code for sending email when a user comments on a post, which does work. Any help much appreciated.

models.py (relevant parts):

from django.db import models
from django.contrib.auth.models import User

from django.contrib.sites.models import Site
from django.db.models import signals
from notification import models as notification

class Post(models.Model):
    author = models.ForeignKey(User, related_name="added_posts")
    observers = models.ManyToManyField(User, verbose_name=_("Observers"), related_name='observers+', blank=True, null=True)

# send notification to Post observers
def create_notice_types(app, created_models, verbosity, **kwargs):
    notification.create_notice_type("new_post", "New post created", "A new post has been created")
signals.post_syncdb.connect(create_notice_types, sender=notification)

def new_post(sender, instance, created, **kwargs):   

    context = {
        'observer': instance,
        'site': Site.objects.get_current(),
    }

    recipients = []
    pk=instance._get_pk_val()

    for observer in instance.observers.all().distinct():
        if observer.user not in recipients:
            recipients.append(observer.user)

    notification.send(recipients, 'new_post', context)

signals.post_save.connect(
    new_post, sender=models.get_model(
    'blog', 'Post'), dispatch_uid="pkobservers")

views.py (relevant parts):

@login_required
def add(request, form_class=PostForm, template_name="blog/post_add.html"):
    post_form = form_class(request)
    if request.method == "POST" and post_form.is_valid():
        post = post_form.save(commit=False)
        post.author = request.user
        post_form.save()
        post_form.save_m2m()
        return redirect("blog_user_post_detail", 
            username=request.user.username, slug=post.slug)
    return render_to_response(template_name, 
        {"post_form": post_form}, context_instance=RequestContext(request))

EDIT:

Also tried this (after dropping the blog_post and blog_post_observers tables, and running manage.py syncdb again, but still doesn't work):

models.py

class Post(models.Model):
    # ....
    observers = models.ManyToManyField(User, verbose_name=_("Observers"), related_name='observers+')

def new_post(sender, instance, created, **kwargs):   

    context = {
        'observer': instance,
        'site': Site.objects.get_current(),
    }

    recipients = instance.observers.all()
    pk=instance._get_pk_val()

    notification.send(recipients, 'new_post', context)

signals.post_save.connect(new_post, sender=models.get_model('blog', 'Post'), dispatch_uid="pkobservers")

EDIT 2 Thursday, 27 June 2013: 11:48:49 Italy Time:

When I edit/update a post, using the following view, the notification email does work:

views.py

@login_required
def edit(request, id, form_class=PostForm, template_name="blog/post_edit.html"):
    post = get_object_or_404(Post, id=id)
    if post.author != request.user:
        request.user.message_set.create(message="You can't edit items that aren't yours")
        return redirect("desk")
    post_form = form_class(request, instance=post)
    if request.method == "POST" and post_form.is_valid():
        post = post_form.save(commit=False)
        post.updated_at = datetime.now()
        post_form.save()
        post_form.save_m2m()
        messages.add_message(request, messages.SUCCESS, message=_("Successfully updated post '%s'") % post.title)
        return redirect("blog_user_post_detail", username=request.user.username, slug=post.slug)
    return render_to_response(template_name, {"post_form": post_form, "post": post}, context_instance=RequestContext(request))
Community
  • 1
  • 1
sgriffee
  • 381
  • 5
  • 18
  • have you tested that emails work outside of the signal ? – karthikr Jun 26 '13 at 15:11
  • yes, it works outside the signal. – sgriffee Jun 26 '13 at 15:19
  • You are using messages and not notification; I think your edit changes the question a bit... – Jorge Leitao Jun 27 '13 at 11:40
  • The code in Edit 2 above is the views.py code (I've now added views.py heading to make this clearer), and the models.py code is unchanged. The messages code in Edit 2 is for messages that appear on the page after the form has been submitted, and unrelated to email notifications as far as I can see. – sgriffee Jun 27 '13 at 12:34

2 Answers2

2

First of all, I think your many to many relationship should not have blank or null, since each observer would ideally be a user and not a None. This avoids trying to send emails to None users, which probably gives error.

Second, I think you can just use

recipients = instance.observers.all().distinct()

instead of looping on the list (distinct() already considers only unique users)

Third, I don't see why you really need a "distinct()": can a user be observer more than once?

recipients = instance.observers.all()

Fourth, In your code, you are looping trough instance.observers.all() with an "observer", which is already a User. Why do you append observer.user in recipients? I think appending observer should be enough.

Finally, confirm that recipients is not empty. If you have tested notification.send(), your code seems correct.

Jorge Leitao
  • 19,085
  • 19
  • 85
  • 121
  • I tried your suggestions, i.e. the edited code above, but the email notification still doesn't work :/ – sgriffee Jun 26 '13 at 15:34
  • Thanks for the help, I'll keep trying (back tomorrow) – sgriffee Jun 26 '13 at 15:49
  • When I edit/update a post, using the following edit view, the notification email **does work**: (please see **EDIT 2 Thursday, 27 June 2013: 11:48:49 Italy Time** above). Maybe the notification app needs the ID of the post, and it is not being passed in the add view? – sgriffee Jun 27 '13 at 09:55
0

Figured it out — the notification email is now sent to observers when creating a new post.

Basically I needed to call save a second time after setting the post.id:

views.py:

@login_required
def add(request, form_class=PostForm, template_name="blog/post_add.html"):
    post_form = form_class(request)
    if request.method == "POST" and post_form.is_valid():
        post = post_form.save(commit=False)
        post.author = request.user
        post_form.save()
        post.id = post.id # set post id
        post_form.save() # save a second time for notifications to be sent to observers 
        return redirect("blog_user_post_detail", 
            username=request.user.username, slug=post.slug)
    return render_to_response(template_name, 
        {"post_form": post_form}, context_instance=RequestContext(request))

The models.py is unchanged from the first EDIT: in the question (Thanks to J. C. Leitão for the help).

Here it is to be sure:

models.py

class Post(models.Model):
    observers = models.ManyToManyField(User, verbose_name=_("Observers"), related_name='observers+')

def new_post(sender, instance, created, **kwargs):   

    context = {
        'observer': instance,
        'site': Site.objects.get_current(),
    }

    recipients = instance.observers.all()
    pk=instance._get_pk_val()

    notification.send(recipients, 'new_post', context)

signals.post_save.connect(new_post, sender=models.get_model('blog', 'Post'), dispatch_uid="pkobservers")
Community
  • 1
  • 1
sgriffee
  • 381
  • 5
  • 18