Author: brosner
Date: Wed Oct 1 09:35:10 2008
New Revision: 103
Added:
branches/pluggable-backends/notification/admin.py
- copied unchanged from r102, /trunk/notification/admin.py
branches/pluggable-backends/notification/decorators.py
- copied unchanged from r102, /trunk/notification/decorators.py
branches/pluggable-backends/notification/engine.py
- copied unchanged from r102, /trunk/notification/engine.py
branches/pluggable-backends/notification/feeds.py
- copied unchanged from r102, /trunk/notification/feeds.py
branches/pluggable-backends/notification/lockfile.py
- copied unchanged from r102, /trunk/notification/lockfile.py
branches/pluggable-backends/notification/management/ (props changed)
- copied from r102, /trunk/notification/management/
branches/pluggable-backends/notification/management/__init__.py
- copied unchanged from r102,
/trunk/notification/management/__init__.py
branches/pluggable-backends/notification/management/commands/ (props
changed)
- copied from r102, /trunk/notification/management/commands/
branches/pluggable-backends/notification/management/commands/__init__.py
- copied unchanged from r102,
/trunk/notification/management/commands/__init__.py
branches/pluggable-backends/notification/management/commands/emit_notices.py
- copied unchanged from r102,
/trunk/notification/management/commands/emit_notices.py
branches/pluggable-backends/notification/management/commands/upgrade_notices.py
- copied unchanged from r102,
/trunk/notification/management/commands/upgrade_notices.py
branches/pluggable-backends/notification/templates/notification/full.html
- copied unchanged from r102,
/trunk/notification/templates/notification/full.html
branches/pluggable-backends/notification/templatetags/ (props changed)
- copied from r102, /trunk/notification/templatetags/
branches/pluggable-backends/notification/templatetags/__init__.py
- copied unchanged from r102,
/trunk/notification/templatetags/__init__.py
branches/pluggable-backends/notification/templatetags/captureas_tag.py
- copied unchanged from r102,
/trunk/notification/templatetags/captureas_tag.py
Modified:
branches/pluggable-backends/ (props changed)
branches/pluggable-backends/notification/models.py
branches/pluggable-backends/notification/templates/notification/teaser.html
branches/pluggable-backends/notification/urls.py
branches/pluggable-backends/notification/views.py
Log:
pluggable-backends: Merged from trunk revisions 77-89 and 91-102. This is a
very rough merge and has broken code.
Modified: branches/pluggable-backends/notification/models.py
==============================================================================
--- branches/pluggable-backends/notification/models.py (original)
+++ branches/pluggable-backends/notification/models.py Wed Oct 1 09:35:10
2008
@@ -1,12 +1,22 @@
import datetime
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
from django.db import models
+from django.db.models.query import QuerySet
from django.conf import settings
from django.core.urlresolvers import reverse
from django.template import Context
from django.template.loader import render_to_string
-from django.contrib.auth.models import User, SiteProfileNotAvailable
+from django.core.exceptions import ImproperlyConfigured
+
+from django.contrib.sites.models import Site
+from django.contrib.auth.models import User
+from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
@@ -17,9 +27,11 @@
from notification import backends
from notification.message import encode_message
+class LanguageStoreNotAvailable(Exception):
+ pass
class NoticeType(models.Model):
-
+
label = models.CharField(_('label'), max_length=40)
display = models.CharField(_('display'), max_length=50)
description = models.CharField(_('description'), max_length=100)
@@ -29,10 +41,7 @@
def __unicode__(self):
return self.label
-
- class Admin:
- list_display = ('label', 'display', 'description', 'default')
-
+
class Meta:
verbose_name = _("notice type")
verbose_name_plural = _("notice types")
@@ -53,18 +62,16 @@
Indicates, for a given user, whether to send notifications
of a given type to a given medium.
"""
-
+
user = models.ForeignKey(User, verbose_name=_('user'))
notice_type = models.ForeignKey(NoticeType, verbose_name=_('notice
type'))
medium = models.CharField(_('medium'), max_length=1,
choices=NOTICE_MEDIA)
send = models.BooleanField(_('send'))
-
- class Admin:
- list_display = ('id', 'user', 'notice_type', 'medium', 'send')
-
+
class Meta:
verbose_name = _("notice setting")
verbose_name_plural = _("notice settings")
+ unique_together = ("user", "notice_type", "medium")
def get_notification_setting(user, notice_type, medium):
@@ -82,17 +89,26 @@
class NoticeManager(models.Manager):
- def notices_for(self, user, archived=False):
+ def notices_for(self, user, archived=False, unseen=None, on_site=None):
"""
returns Notice objects for the given user.
If archived=False, it only include notices not archived.
If archived=True, it returns all notices for that user.
+
+ If unseen=None, it includes all notices.
+ If unseen=True, return only unseen notices.
+ If unseen=False, return only seen notices.
"""
if archived:
- return self.filter(user=user)
+ qs = self.filter(user=user)
else:
- return self.filter(user=user, archived=archived)
+ qs = self.filter(user=user, archived=archived)
+ if unseen is not None:
+ qs = qs.filter(unseen=unseen)
+ if on_site is not None:
+ qs = qs.filter(on_site=on_site)
+ return qs
def unseen_count_for(self, user):
"""
@@ -102,27 +118,28 @@
return self.filter(user=user, unseen=True).count()
class Notice(models.Model):
-
+
user = models.ForeignKey(User, verbose_name=_('user'))
message = models.TextField(_('message'))
notice_type = models.ForeignKey(NoticeType, verbose_name=_('notice
type'))
added = models.DateTimeField(_('added'), default=datetime.datetime.now)
unseen = models.BooleanField(_('unseen'), default=True)
archived = models.BooleanField(_('archived'), default=False)
-
+ on_site = models.BooleanField(_('on site'))
+
objects = NoticeManager()
-
+
def __unicode__(self):
return self.message
-
+
def archive(self):
self.archived = True
self.save()
-
+
def is_unseen(self):
"""
returns value of self.unseen but also changes it to false.
-
+
Use this in a template to mark an unseen notice differently the
first
time it is shown.
"""
@@ -131,19 +148,27 @@
self.unseen = False
self.save()
return unseen
-
+
class Meta:
ordering = ["-added"]
verbose_name = _("notice")
verbose_name_plural = _("notices")
-
- class Admin:
- list_display =
('message', 'user', 'notice_type', 'added', 'unseen', 'archived')
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ("notification_notice", [str(self.pk)])
+
+class NoticeQueueBatch(models.Model):
+ """
+ A queued notice.
+ Denormalized data for a notice.
+ """
+ pickled_data = models.TextField()
def create_notice_type(label, display, description, default=2):
"""
Creates a new NoticeType.
-
+
This is intended to be used by other apps as a post_syncdb manangement
step.
"""
try:
@@ -165,6 +190,23 @@
NoticeType(label=label, display=display, description=description,
default=default).save()
print "Created %s NoticeType" % label
+def get_notification_language(user):
+ """
+ Returns site-specific notification language for this user. Raises
+ LanguageStoreNotAvailable if this site does not use translated
+ notifications.
+ """
+ if getattr(settings, 'NOTIFICATION_LANGUAGE_MODULE', False):
+ try:
+ app_label, model_name =
settings.NOTIFICATION_LANGUAGE_MODULE.split('.')
+ model = models.get_model(app_label, model_name)
+ language_model =
model._default_manager.get(user__id__exact=user.id)
+ if hasattr(language_model, 'language'):
+ return language_model.language
+ except (ImportError, ImproperlyConfigured, model.DoesNotExist):
+ raise LanguageStoreNotAvailable
+ raise LanguageStoreNotAvailable
+
def get_formatted_messages(formats, label, context):
"""
Returns a dictionary with the format identifier as the key. The values
are
@@ -172,37 +214,42 @@
"""
format_templates = {}
for format in formats:
+ # conditionally turn off autoescaping for .txt extensions in format
+ if format.endswith(".txt"):
+ context.autoescape = False
name = format.split(".")[0]
format_templates[name] = render_to_string((
'notification/%s/%s' % (label, format),
- 'notification/%s' % format), context)
+ 'notification/%s' % format), context_instance=context)
return format_templates
-def send(recipient, label, extra_context={}):
+def send(users, label, extra_context={}, on_site=True):
"""
Creates a new notice.
-
+
This is intended to be how other apps create new notices.
-
+
notification.send(user, 'friends_invite_sent', {
'spam': 'eggs',
'foo': 'bar',
)
+
+ You can pass in on_site=False to prevent the notice emitted from being
+ displayed on the site.
+
+ FIXME: this function needs some serious reworking.
"""
- if not isinstance(recipient, (list, tuple)):
- recipient = (recipient,)
+ notice_type = NoticeType.objects.get(label=label)
notice_type = NoticeType.objects.get(label=label)
backend_recipients = {}
- context = Context({
- "notice": ugettext(notice_type.display),
- "notices_url": notices_url,
- "current_site": current_site,
- })
- context.update(extra_context)
+ current_site = Site.objects.get_current()
+ notices_url = u"http://%s%s" % (
+ unicode(current_site),
+ reverse("notification_notices"),
+ )
- recipients = []
current_language = get_language()
formats = (
@@ -212,35 +259,44 @@
'full.html',
) # TODO make formats configurable
- for user in recipient:
- # get user profiles if available
+ for user in users:
+ recipients = []
+ # get user language for user from language store defined in
+ # NOTIFICATION_LANGUAGE_MODULE setting
try:
- profile = user.get_profile()
- except SiteProfileNotAvailable:
- profile = None
-
- # activate language of user to send message translated
- if profile is not None:
- # get language attribute of user profile
- language = getattr(profile, "language", None)
- if language is not None:
- # activate the user's language
- activate(language)
+ language = get_notification_language(user)
+ except LanguageStoreNotAvailable:
+ language = None
+
+ if language is not None:
+ # activate the user's language
+ activate(language)
+
+ # update context with user specific translations
+ context = Context({
+ "user": user,
+ "notice": ugettext(notice_type.display),
+ "notices_url": notices_url,
+ "current_site": current_site,
+ })
+ context.update(extra_context)
# get prerendered format messages
+ # TODO: figure out how to handle this in the branch.
messages = get_formatted_messages(formats, label, context)
# Strip newlines from subject
+ # TODO: this should move to the email backend
subject
= ''.join(render_to_string('notification/email_subject.txt', {
'message': messages['short'],
}, context).splitlines())
-
+
body = render_to_string('notification/email_body.txt', {
'message': messages['plain'],
}, context)
-
- notice = Notice(user=user, message=message,
notice_type=notice_type)
- notice.save()
+
+ notice = Notice.objects.create(user=user,
message=messages['teaser'],
+ notice_type=notice_type, on_site=on_site)
for key, backend in NOTIFICATION_BACKENDS:
recipients = backend_recipients.setdefault(key, [])
if backend.can_send(user, notice_type):
@@ -251,6 +307,16 @@
# reset environment to original language
activate(current_language)
+def queue(users, label, extra_context={}, on_site=True):
+ if isinstance(users, QuerySet):
+ users = [row["pk"] for row in users.values("pk")]
+ else:
+ users = [user.pk for user in users]
+ notices = []
+ for user in users:
+ notices.append((user, label, extra_context, on_site))
+ NoticeQueueBatch(pickled_data=pickle.dumps(notices)).save()
+
class ObservedItemManager(models.Manager):
def all_for(self, observed, signal):
@@ -261,7 +327,7 @@
content_type = ContentType.objects.get_for_model(observed)
observed_items = self.filter(content_type=content_type,
object_id=observed.id, signal=signal)
return observed_items
-
+
def get_for(self, observed, observer, signal):
content_type = ContentType.objects.get_for_model(observed)
observed_item = self.get(content_type=content_type,
object_id=observed.id, user=observer, signal=signal)
@@ -271,42 +337,39 @@
class ObservedItem(models.Model):
user = models.ForeignKey(User, verbose_name=_('user'))
-
+
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
observed_object =
generic.GenericForeignKey('content_type', 'object_id')
-
+
notice_type = models.ForeignKey(NoticeType, verbose_name=_('notice
type'))
- message_template = models.TextField(verbose_name=_('message template'))
-
+
added = models.DateTimeField(_('added'), default=datetime.datetime.now)
-
+
# the signal that will be listened to send the notice
signal = models.TextField(verbose_name=_('signal'))
-
+
objects = ObservedItemManager()
-
+
class Meta:
ordering = ['-added']
verbose_name = _('observed item')
verbose_name_plural = _('observed items')
-
- class Admin:
- pass
-
+
def send_notice(self):
- send([self.user], self.notice_type.label, self.message_template,
- [self.observed_object])
+ send([self.user], self.notice_type.label,
+ {'observed': self.observed_object})
-def observe(observed, observer, notice_type_label, message_template,
signal='post_save'):
+def observe(observed, observer, notice_type_label, signal='post_save'):
"""
Create a new ObservedItem.
-
+
To be used by applications to register a user as an observer for some
object.
"""
notice_type = NoticeType.objects.get(label=notice_type_label)
- observed_item = ObservedItem(user=observer, observed_object=observed,
notice_type=notice_type, message_template=message_template, signal=signal)
+ observed_item = ObservedItem(user=observer, observed_object=observed,
+ notice_type=notice_type, signal=signal)
observed_item.save()
return observed_item
@@ -327,6 +390,8 @@
return observed_items
def is_observing(observed, observer, signal='post_save'):
+ if isinstance(observer, AnonymousUser):
+ return False
try:
observed_items = ObservedItem.objects.get_for(observed, observer,
signal)
return True
Modified:
branches/pluggable-backends/notification/templates/notification/teaser.html
==============================================================================
---
branches/pluggable-backends/notification/templates/notification/teaser.html
(original)
+++
branches/pluggable-backends/notification/templates/notification/teaser.html
Wed Oct 1 09:35:10 2008
@@ -1 +1 @@
-{% load i18n %}{% blocktrans %}{{ notice }}{% endblocktrans %}
+{% load i18n %}{% blocktrans %}{{ notice }}{% endblocktrans %}
\ No newline at end of file
Modified: branches/pluggable-backends/notification/urls.py
==============================================================================
--- branches/pluggable-backends/notification/urls.py (original)
+++ branches/pluggable-backends/notification/urls.py Wed Oct 1 09:35:10
2008
@@ -2,8 +2,11 @@
# @@@ from atom import Feed
-from notification.views import notices
+from notification.views import notices, mark_all_seen, feed_for_user,
single
urlpatterns = patterns('',
url(r'^$', notices, name="notification_notices"),
+ url(r'^(\d+)/$', single, name="notification_notice"),
+ url(r'^feed/$', feed_for_user, name="notification_feed_for_user"),
+ url(r'^mark_all_seen/$', mark_all_seen,
name="notification_mark_all_seen"),
)
Modified: branches/pluggable-backends/notification/views.py
==============================================================================
--- branches/pluggable-backends/notification/views.py (original)
+++ branches/pluggable-backends/notification/views.py Wed Oct 1 09:35:10
2008
@@ -1,14 +1,25 @@
-from django.shortcuts import render_to_response
-from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect, Http404
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
+from django.contrib.syndication.views import feed
from notification.models import *
+from notification.decorators import basic_auth_required,
simple_basic_auth_callback
+from notification.feeds import NoticeUserFeed
+
[EMAIL PROTECTED](realm='Notices Feed',
callback_func=simple_basic_auth_callback)
+def feed_for_user(request):
+ url = "feed/%s" % request.user.username
+ return feed(request, url, {
+ "feed": NoticeUserFeed,
+ })
@login_required
def notices(request):
notice_types = NoticeType.objects.all()
- notices = Notice.objects.notices_for(request.user)
+ notices = Notice.objects.notices_for(request.user, on_site=True)
settings_table = []
for notice_type in NoticeType.objects.all():
settings_row = []
@@ -36,6 +47,15 @@
}, context_instance=RequestContext(request))
@login_required
+def single(request, id):
+ notice = get_object_or_404(Notice, id=id)
+ if request.user == notice.user:
+ return render_to_response("notification/single.html", {
+ "notice": notice,
+ }, context_instance=RequestContext(request))
+ raise Http404
+
[EMAIL PROTECTED]
def archive(request, noticeid=None, next_page=None):
if noticeid:
try:
@@ -61,4 +81,12 @@
return HttpResponseRedirect(next_page)
except Notice.DoesNotExist:
return HttpResponseRedirect(next_page)
- return HttpResponseRedirect(next_page)
\ No newline at end of file
+ return HttpResponseRedirect(next_page)
+
[EMAIL PROTECTED]
+def mark_all_seen(request):
+ for notice in Notice.objects.notices_for(request.user, unseen=True):
+ notice.unseen = False
+ notice.save()
+ return HttpResponseRedirect(reverse("notification_notices"))
+
\ No newline at end of file
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"pinax-updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/pinax-updates?hl=en
-~----------~----~----~----~------~----~------~--~---