Django notification on comment submission - django

I am making use of Django's contrib.comments and want to know the following.
Are there any utils or app out there that can be plugged into an app that sends you a notification when a comment is posted on an item?
I haven't really worked with signals that much, so please be a little bit descriptive.
This is what I came up with.
from django.contrib.comments.signals import comment_was_posted
from django.core.mail import send_mail
if "notification" in settings.INSTALLED_APPS:
from notification import models as notification
def comment_notification(request):
user = request.user
message = "123"
notification.send([user], "new comment", {'message': message,})
comment_was_posted.connect(comment_notification)

Connect django.contrib.comments.signals.comment_was_posted to notification.models.send() as appropriate.

You have to register your comment_notification function with comment_was_posted signal.
from django.contrib.comments.signals import comment_was_posted
if "notification" in settings.INSTALLED_APPS:
from notification import models as notification
def comment_notification(sender, comment, request):
user = request.user
message = "123"
notification.send([user], "new comment", {'message': message,})
comment_was_posted.connect(comment_notification)

I don't know of an app (pretty sure there'll be something out there) but it is fairly straightforward to roll your own. You can tap the Comment model's comment_was_posted signal to call a function that will send you an email.

Related

Sending email notifications to subscribers when new blog post is published in Wagtail

I'm in the process of converting an existing Django project to Wagtail. One issue I'm having is email notifications. In the Django project, I have the ability for people to subscribe to a blog, and whenever a new post is published, the author can manually send out a notification to all subscribers in the admin. However, I'm not sure how to accomplish this in Wagtail.
I've read the docs about the page_published signal (https://docs.wagtail.io/en/stable/reference/signals.html#page-published), however, I'm not sure how I could integrate my current code into it. In addition, I would prefer for the author to manually send out the notification, as the author doesn't want to email their subscribers every time a blog post is edited and subsequently published.
For reference, the current code I have for the Django app is as follows (it only works if the blog app is in normal Django; because the blog models are now in the Wagtail app, the current code no longer works).
models.py
class Post(models.Model):
"""Fields removed here for brevity."""
...
def send(self, request):
subscribers = Subscriber.objects.filter(confirmed=True)
sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
for sub in subscribers:
message = Mail(
from_email=settings.FROM_EMAIL,
to_emails=sub.email,
subject="New blog post!",
html_content=( #Abbreviated content here for brevity
'Click the following link to read the new post:' \
'{}'\
'Or, you can copy and paste the following url into your browser:' \
'{}/{}'\
'<hr>If you no longer wish to receive our blog updates, you can ' \
'unsubscribe.').format(
request.build_absolute_uri('/post'),
self.slug,
self.title,
request.build_absolute_uri('/post'),
self.slug,
request.build_absolute_uri('/delete'),
sub.email,
sub.conf_num
)
)
sg.send(message)
admin.py
def send_notification(modeladmin, request, queryset):
for post in queryset:
post.send(request)
send_notification.short_description = "Send selected Post(s) to all subscribers"
#admin.register(Post)
class PostAdmin(SummernoteModelAdmin):
...
actions = [send_notification]
Any suggestions or feedback would be greatly appreciated! Thanks in advance!
EDIT 1
I got a suggestion from the Wagtail Slack to use the register_page_action_menu_item hook (https://docs.wagtail.io/en/stable/reference/hooks.html?highlight=hooks#register-page-action-menu-item). I successfully implemented the action menu item on Wagtail's page editor, however I cannot get my email method to execute (likely due to my not knowing how to properly use the hook). Below is the code from my wagtail_hooks.py file.
from wagtail.admin.action_menu import ActionMenuItem
from wagtail.core import hooks
from .models import Subscriber
from django.conf import settings
from django.core.mail import send_mail
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (Mail, Attachment, FileContent, FileName, FileType, Disposition)
class NotificationMenuItem(ActionMenuItem):
name = 'email-notification'
label = "Notify Subscribers of New Post"
def send(self, request):
"""Used the same def send() method here from my models.py above"""
#hooks.register('register_page_action_menu_item')
def register_notification_menu_item():
return NotificationMenuItem(order=100)
If anyone has advice on how to fix it so it executes, please let me know!
EDIT 2
More problems! (Though I think I'm getting closer.)
Modifying the wagtail_hooks.py to the following, I am able to send an email, but it happens on pageload. So every time I load a blog post in the editor, it sends an email. Clicking the action menu item I created triggers a page reload, which then sends another email (so I don't think my action menu item is actually working when clicked).
Another problem: Because I moved the send() method into the NotificationMenuItem class, I am unable to dynamically generate a blog post's slug and title in the urls of the email.
wagtail_hooks.py
class NotificationMenuItem(ActionMenuItem):
name = 'email-notification'
label = "Notify Subscribers of New Post"
def send(self, request):
"""Used the same def send() method here from my models.py above"""
def get_url(self, request, context):
self.send(request)
EDIT 3
I managed to get the notification system to work in the regular Django admin despite the models being Wagtail models. While this moves the current website's functionality over to the new wagtail site, I still have been unable to solve the most recent issues raised under Edit 2.
Here's the new code in the admin:
def send_notification(modeladmin, request, queryset):
for post in queryset:
post.send(request)
send_notification.short_description = "Send selected Post(s) to all subscribers"
class BlogPageAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'body')
search_fields = ['title', 'body']
actions = [send_notification]
admin.site.register(BlogPage, BlogPageAdmin)
Better you need to use a post_save signal.
#receiver(post_save, sender=BlogPost)
def send_mail_to_subs(sender, instance, created, **kwargs):
current_site = Site.objects.get_current()
domain = current_site.domain
if created:
for subs in instance.author.subscribed.all():
send_mail(
f'New Post from {instance.author}',
f'Title: {instance.post_title}\nContent: {instance.post_content}\nDate Created: {instance.created}\nUrl: {domain}',
'youremail',
[subs.email],
)
Good coding.

Can't hear the Django user_logged_out signal

OK, using the new-ish 'user_logged_in' signal in Django 1.3, and it works great. But, the corresponding 'user_logged_out' signal appears to be getting ignored. Let's assume I'm just trying to log the logins & logouts using a module called transaction. Again, the login bit works great, but the logout looks like it's being ignored... No error messages anywhere, and nothing in my resulting logs. OK, here's the code:
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
from myapp.utils import transaction
#receiver(user_logged_out)
def log_logout(sender, **kwargs):
u = kwargs['user'].username
data={ 'Successful Logout': u }
transaction.add(data)
#receiver(user_logged_in)
def log_login(sender, **kwargs):
u = kwargs['user'].username
data={ 'Successful Login': u }
transaction.add(data)
...I've tested this a couple different ways, and I can't seem to get the user_logged_out signal to fire (or, be heard). Any ideas? Thanks in advance.

How can I send an email to user on new user addition to django admin site?

I want to send an email with login details to user emailaddress whenever Admin adds new user to admin site.
I know Django provides send_mail module for that,but I don't know where should I put this code
and override some view to send automatic mails on new user addition.
from django.core.mail import send_mail
send_mail('Subject here', 'Here is the message.', 'from#example.com',
['to#example.com'], fail_silently=False)
How can i do it?
I tried putting this code in my models.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
def email_new_user(sender, **kwargs):
if kwargs["created"]: # only for new users
new_user = kwargs["instance"]
print new_user.email
#send_mail('Subject here', 'Here is the message.', 'from#example.com',['to#example.com'], fail_silently=False)
post_save.connect(email_new_user, sender=User)
But its not printing anything on command line. I think the function is not getting called whenever i create a new user.Don't know why?
There are multiple problems I can see in the code as I can see it, above.
First, the def email_new_user() is wrongly indented. If that is not a formatting error here, correct it.
new_user = kwargs.["instance"] is wrong syntax. It really should be kwargs["instance"]
And then, do you have an SMTP server running? Can you send email from the shell? If not, configure that and then try it again. It should work.
You want to hook the post_save signal for the User model.
One possible problem was, Django user cretion consists of two steps. First django asks for username, password and password confirmation, and when you press save (or save and continue edit) your save method fired without e-mail information. Since django will accept that request as a newly created record, your signal will fire for a record with no e-mail information...
If django handles admin save diffrently and do not mark a newly created record instance as created you shall not have any problem, but probably django will not do that.
UPDATE: Some possible reasons are:
You need django 1.3, so check your django version with python manage.py version and update it if required...
Best place for your your models.py files
You can put signal handling and registration code anywhere you like. However, you'll need to make sure that the module it's in gets imported early on so that the signal handling gets registered before any signals need to be sent. This makes your app's models.py a good place to put registration of signal handlers.
These are basic problems...

Proper technique for a contact form?

I have a contact form at shantiyoga.ca/contact
With the view
def contact(request):
template = 'contact.html'
form = ContactForm(request.POST or None)
if form.is_valid(): # All validation rules pass
subject = form.cleaned_data['subject']
message = "%s\n\n%s" % (form.cleaned_data['message'], form.cleaned_data['sender'])
cc_myself = form.cleaned_data['cc_myself']
recipients = ['contact#shantiyoga.ca']
sender = form.cleaned_data['sender']
if cc_myself:
recipients.append(sender)
headers = {'Reply-To': form.cleaned_data['sender']}
from django.core.mail import send_mail
send_mail(subject,message,sender,recipients,headers)
return redirect('/thanks/')
return render_to_response(template, {'form': form, 'current':'contact'}, context_instance=RequestContext(request))
Which works fairly well. I'm not terribly sophisticated with Django and my Python skills are not quite up to snuff, so please bear with me if I step through this in a basic fashion.
I would like to clarify that there is no way for the form recipient (contact#shantiyoga.ca) to receive the contact form from the value of the email field (user entered). It will always be sent by the authenticated email in my settings.py, which at this point is my personal email?
A user fills out the contact form and hits submit, an email is sent to contact#shantiyoga.ca from my personal email, and if the user decides to cc themself, a copy of the email is sent to them, also from my personal email.
This is not ideal, should I create an email like contactform#shantiyoga.ca for my settings.py to send the email from?
Also, the headers = {'Reply-To': form.cleaned_data['sender']} does not appear to be doing anything and I can't seem to find documentation describing its proper usage, has anyone had success using this technique?
Thank you for your time,
Noah
I would like to clarify that there is no way for the form recipient (contact#shantiyoga.ca) to receive the contact form from the value of the email field (user entered). It will always be sent by the authenticated email in my settings.py, which at this point is my personal email?
You're setting the sender of the email to sender but using EMAIL_HOST, correct? If this is the case, be careful that your SMTP account will let you send email from users other than your domain. Normally, providing you have an authenticated account on that server, you'll be able to set the From: field to whatever you like.
So in short, this email will hit your inbox appearing to be from the sender variable. So when you hit reply, you'll be able to email them back, which is I think what you want. However, it will be sent using the SMTP server whose authentication details you provide. There is a disconnect between being able to send email (SMTP) and being able to receive it, which I think has got you confused.
I've never tried it, but to add extra headers I believe you need to use the EmailMessage object. According to the docs, you would:
e = EmailMessage(headers={'Reply-To':...
e.send()
Be careful doing this; specifically, be careful to strip out newlines in the reply to field. I do not know if the default clean methods would do this.
Finally, there isn't much wrong with your django/python at all. The only thing I'd say, by way of awareness, is not to use this:
return redirect('/thanks/')
but instead:
return HttpResponseRedirect(reverse('myapp.views.method',args=tuple,kwargs=dict))
This gives you the ability to not hardcode urls into your application. The reverse method will look them up from urls.py for you, so you can move your application around/change urls and it will still work.
Here is signature of send_mail from django documentation:
send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)
As you can see it does not have headers argument.
You will need to use EmailMessage object directly.
It would also be nice to remove email-formatting from view. I would write everything something more like this:
from django.core.mail.message import EmailMessage
from django.conf import settings
class ContactsEmailMessage(EmailMessage):
def __init__(self, sender, subject, message, cc_myself):
super(ContactEmailMessage, self).__init__(
subject=subject,
body=self._get_message(self, sender, message),
from=settings.DEFAULT_FROM_EMAIL,
to=settings.CONTACT_RECIPIENTS,
headers=self._get_headers(sender),
cc=(sender, ) if cc_myself else None
)
def _format_message(self, sender, message):
return "%s\n\n%s" % (sender, message)
def _get_headers(self, sender):
return {
'reply-to': sender
}
Now you can write clean and easy to read view:
from myproject.mail.message import ContactsEmailMessage, BadHeaderError
from django.core.exceptions import SuspiciousOperation
def contact(request):
...
form = ContactForm(request.POST or None)
if form.is_valid(): # All validation rules pass
try:
message = ContactsEmailMessage(**form.cleaned_data)
message.send()
except BadHeaderError:
# django will raise this error if user will try to pass suspicious new-line
# characters to "sender" or other fields. This is safe-guard from
# header injections
raise SuspiciousOperation()
return redirect('/thanks/')
...

Django - extending LogEntry

I have the requirement that whenever there is a model get's added/changed/deleted, it should send a mail notification. The content will be more like the django_admin_log entries. I just need to extend this functionality in my model to send the mail. Any suggestions?
Django_log_admin will only track changes made in the admin interface. If the model is changed anywhere else, it will not update the log. However, if you are OK with just admin changes, then you can use a combination of django_log_admin and the post_save signal to do the trick. Put this in your management.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.admin.models import LogEntry
from django.core.mail import mail_admins
from django.template.loader import render_to_string
#will be triggered every time a LogEntry is saved i.e. every time an action is made.
#receiver(post_save, sender=LogEntry)
def send_notification_email(change, **kwargs):
mail_admins(subject="model %(model) has been changed by %(user)" %
{'model':change.content_type, 'user': change.user},
message = render_to_string('change_email.html', { 'change': change }) )
note to self: wow, django really includes all the batteries :D
You should look at Django's signals. In your case, you'll connect your handlers to the post_save and post_delete signals, for starters. Look through the built-in signal documentation for others you may want to tap. No need to hack into admin.