New notifications
- Added request.notifications
- Email configuration fixes
- Set config_spec default SMTP port to `0` and switch to SSL/non-SSL
default if `port == 0`
- Added email_smtp_use_ssl configuration setting
- Added migrations for notification tables
- Added __repr__ to MediaComment(Mixin)
- Added MediaComment.get_entry => MediaEntry
- Added CommentSubscription, CommentNotification, Notification,
ProcessingNotification tables
- Added notifications.task to celery init
- Fixed a bug in the video transcoder where pygst would hijack the
--help argument.
- Added notifications
- views
- silence
- subscribe
- routes
- utility methods
- celery task
- Added half-hearted .active comment CSS style
- Added quick JS to show header_dropdown
- Added fragment template to show notifications in header_dropdown
- Added fragment template to show subscribe/unsubscribe buttons on
media/comment pages
- Updated celery setup tests with notifications.task
- Tried to fix test_misc tests that I broke
- Added notification tests
- Added and extended tests.tools fixtures
- Integrated new notifications into media_home, media_post_comment views
- Bumped SQLAlchemy dependency to >= 0.8.0 since we need polymorphic for
the notifications to work
This commit is contained in:
141
mediagoblin/notifications/__init__.py
Normal file
141
mediagoblin/notifications/__init__.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from mediagoblin.db.models import Notification, \
|
||||
CommentNotification, CommentSubscription
|
||||
from mediagoblin.notifications.task import email_notification_task
|
||||
from mediagoblin.notifications.tools import generate_comment_message
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
def trigger_notification(comment, media_entry, request):
|
||||
'''
|
||||
Send out notifications about a new comment.
|
||||
'''
|
||||
subscriptions = CommentSubscription.query.filter_by(
|
||||
media_entry_id=media_entry.id).all()
|
||||
|
||||
for subscription in subscriptions:
|
||||
if not subscription.notify:
|
||||
continue
|
||||
|
||||
if comment.get_author == subscription.user:
|
||||
continue
|
||||
|
||||
cn = CommentNotification(
|
||||
user_id=subscription.user_id,
|
||||
subject_id=comment.id)
|
||||
|
||||
cn.save()
|
||||
|
||||
if subscription.send_email:
|
||||
message = generate_comment_message(
|
||||
subscription.user,
|
||||
comment,
|
||||
media_entry,
|
||||
request)
|
||||
|
||||
email_notification_task.apply_async([cn.id, message])
|
||||
|
||||
|
||||
def mark_notification_seen(notification):
|
||||
if notification:
|
||||
notification.seen = True
|
||||
notification.save()
|
||||
|
||||
|
||||
def mark_comment_notification_seen(comment_id, user):
|
||||
notification = CommentNotification.query.filter_by(
|
||||
user_id=user.id,
|
||||
subject_id=comment_id).first()
|
||||
|
||||
_log.debug('Marking {0} as seen.'.format(notification))
|
||||
|
||||
mark_notification_seen(notification)
|
||||
|
||||
|
||||
def get_comment_subscription(user_id, media_entry_id):
|
||||
return CommentSubscription.query.filter_by(
|
||||
user_id=user_id,
|
||||
media_entry_id=media_entry_id).first()
|
||||
|
||||
def add_comment_subscription(user, media_entry):
|
||||
'''
|
||||
Create a comment subscription for a User on a MediaEntry.
|
||||
|
||||
Uses the User's wants_comment_notification to set email notifications for
|
||||
the subscription to enabled/disabled.
|
||||
'''
|
||||
cn = get_comment_subscription(user.id, media_entry.id)
|
||||
|
||||
if not cn:
|
||||
cn = CommentSubscription(
|
||||
user_id=user.id,
|
||||
media_entry_id=media_entry.id)
|
||||
|
||||
cn.notify = True
|
||||
|
||||
if not user.wants_comment_notification:
|
||||
cn.send_email = False
|
||||
|
||||
cn.save()
|
||||
|
||||
|
||||
def silence_comment_subscription(user, media_entry):
|
||||
'''
|
||||
Silence a subscription so that the user is never notified in any way about
|
||||
new comments on an entry
|
||||
'''
|
||||
cn = get_comment_subscription(user.id, media_entry.id)
|
||||
|
||||
if cn:
|
||||
cn.notify = False
|
||||
cn.send_email = False
|
||||
cn.save()
|
||||
|
||||
|
||||
def remove_comment_subscription(user, media_entry):
|
||||
cn = get_comment_subscription(user.id, media_entry.id)
|
||||
|
||||
if cn:
|
||||
cn.delete()
|
||||
|
||||
|
||||
NOTIFICATION_FETCH_LIMIT = 100
|
||||
|
||||
|
||||
def get_notifications(user_id, only_unseen=True):
|
||||
query = Notification.query.filter_by(user_id=user_id)
|
||||
|
||||
if only_unseen:
|
||||
query = query.filter_by(seen=False)
|
||||
|
||||
notifications = query.limit(
|
||||
NOTIFICATION_FETCH_LIMIT).all()
|
||||
|
||||
return notifications
|
||||
|
||||
def get_notification_count(user_id, only_unseen=True):
|
||||
query = Notification.query.filter_by(user_id=user_id)
|
||||
|
||||
if only_unseen:
|
||||
query = query.filter_by(seen=False)
|
||||
|
||||
count = query.count()
|
||||
|
||||
return count
|
||||
25
mediagoblin/notifications/routing.py
Normal file
25
mediagoblin/notifications/routing.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mediagoblin.tools.routing import add_route
|
||||
|
||||
add_route('mediagoblin.notifications.subscribe_comments',
|
||||
'/u/<string:user>/m/<string:media>/notifications/subscribe/comments/',
|
||||
'mediagoblin.notifications.views:subscribe_comments')
|
||||
|
||||
add_route('mediagoblin.notifications.silence_comments',
|
||||
'/u/<string:user>/m/<string:media>/notifications/silence/',
|
||||
'mediagoblin.notifications.views:silence_comments')
|
||||
46
mediagoblin/notifications/task.py
Normal file
46
mediagoblin/notifications/task.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from celery import registry
|
||||
from celery.task import Task
|
||||
|
||||
from mediagoblin.tools.mail import send_email
|
||||
from mediagoblin.db.models import CommentNotification
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmailNotificationTask(Task):
|
||||
'''
|
||||
Celery notification task.
|
||||
|
||||
This task is executed by celeryd to offload long-running operations from
|
||||
the web server.
|
||||
'''
|
||||
def run(self, notification_id, message):
|
||||
cn = CommentNotification.query.filter_by(id=notification_id).first()
|
||||
_log.info('Sending notification email about {0}'.format(cn))
|
||||
|
||||
return send_email(
|
||||
message['from'],
|
||||
[message['to']],
|
||||
message['subject'],
|
||||
message['body'])
|
||||
|
||||
email_notification_task = registry.tasks[EmailNotificationTask.name]
|
||||
55
mediagoblin/notifications/tools.py
Normal file
55
mediagoblin/notifications/tools.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mediagoblin.tools.template import render_template
|
||||
from mediagoblin.tools.translate import pass_to_ugettext as _
|
||||
from mediagoblin import mg_globals
|
||||
|
||||
def generate_comment_message(user, comment, media, request):
|
||||
"""
|
||||
Sends comment email to user when a comment is made on their media.
|
||||
|
||||
Args:
|
||||
- user: the user object to whom the email is sent
|
||||
- comment: the comment object referencing user's media
|
||||
- media: the media object the comment is about
|
||||
- request: the request
|
||||
"""
|
||||
|
||||
comment_url = request.urlgen(
|
||||
'mediagoblin.user_pages.media_home.view_comment',
|
||||
comment=comment.id,
|
||||
user=media.get_uploader.username,
|
||||
media=media.slug_or_id,
|
||||
qualified=True) + '#comment'
|
||||
|
||||
comment_author = comment.get_author.username
|
||||
|
||||
rendered_email = render_template(
|
||||
request, 'mediagoblin/user_pages/comment_email.txt',
|
||||
{'username': user.username,
|
||||
'comment_author': comment_author,
|
||||
'comment_content': comment.content,
|
||||
'comment_url': comment_url})
|
||||
|
||||
return {
|
||||
'from': mg_globals.app_config['email_sender_address'],
|
||||
'to': user.email,
|
||||
'subject': '{instance_title} - {comment_author} '.format(
|
||||
comment_author=comment_author,
|
||||
instance_title=mg_globals.app_config['html_title']) \
|
||||
+ _('commented on your post'),
|
||||
'body': rendered_email}
|
||||
54
mediagoblin/notifications/views.py
Normal file
54
mediagoblin/notifications/views.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# GNU MediaGoblin -- federated, autonomous media hosting
|
||||
# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from mediagoblin.tools.response import render_to_response, render_404, redirect
|
||||
from mediagoblin.tools.translate import pass_to_ugettext as _
|
||||
from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
|
||||
get_media_entry_by_id,
|
||||
require_active_login, user_may_delete_media, user_may_alter_collection,
|
||||
get_user_collection, get_user_collection_item, active_user_from_url)
|
||||
|
||||
from mediagoblin import messages
|
||||
|
||||
from mediagoblin.notifications import add_comment_subscription, \
|
||||
silence_comment_subscription
|
||||
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
@get_user_media_entry
|
||||
@require_active_login
|
||||
def subscribe_comments(request, media):
|
||||
|
||||
add_comment_subscription(request.user, media)
|
||||
|
||||
messages.add_message(request,
|
||||
messages.SUCCESS,
|
||||
_('Subscribed to comments on %s!')
|
||||
% media.title)
|
||||
|
||||
return redirect(request, location=media.url_for_self(request.urlgen))
|
||||
|
||||
@get_user_media_entry
|
||||
@require_active_login
|
||||
def silence_comments(request, media):
|
||||
silence_comment_subscription(request.user, media)
|
||||
|
||||
messages.add_message(request,
|
||||
messages.SUCCESS,
|
||||
_('You will not receive notifications for comments on'
|
||||
' %s.') % media.title)
|
||||
|
||||
return redirect(request, location=media.url_for_self(request.urlgen))
|
||||
Reference in New Issue
Block a user