Permalink
Browse files

Simplify the package.

Because the fork introduced a backward compatibility break in
django-subscription, it made no sense to maintain compatibility with
bare anymore.
  • Loading branch information...
1 parent 75f1190 commit 84c0d8bcaa7966ad2aca648ee0e6f0b3ab7b9d05 @jpic jpic committed Dec 26, 2011
Showing with 109 additions and 233 deletions.
  1. +2 −0 AUTHORS
  2. +0 −40 README
  3. +20 −7 docs/yourlabs.rst
  4. +0 −68 subscription/backends.py
  5. 0 subscription/{examples/yourlabs → }/backends/__init__.py
  6. 0 subscription/{examples/yourlabs → }/backends/base.py
  7. 0 subscription/{examples/yourlabs → }/backends/redis_backend.py
  8. 0 subscription/examples/{yourlabs/apps → }/actstream.py
  9. 0 subscription/examples/{yourlabs/apps → }/auth.py
  10. +0 −7 subscription/examples/{yourlabs/apps → }/comments.py
  11. 0 subscription/examples/{yourlabs/apps → }/django_messages.py
  12. +0 −10 subscription/examples/redis-backend.py
  13. +0 −23 subscription/examples/redis-delivery.py
  14. 0 subscription/examples/yourlabs/__init__.py
  15. 0 subscription/examples/yourlabs/apps/__init__.py
  16. +0 −1 subscription/examples/yourlabs/media
  17. +0 −6 subscription/examples/yourlabs/models.py
  18. 0 subscription/examples/yourlabs/templatetags/__init__.py
  19. +0 −31 subscription/examples/yourlabs/urls.py
  20. +12 −0 subscription/{examples/yourlabs → }/notification.py
  21. 0 subscription/{examples/yourlabs → }/settings.py
  22. 0 subscription/{examples/yourlabs → }/shortcuts.py
  23. 0 subscription/{examples/yourlabs → }/static/subscription/jquery-implementation.js
  24. +0 −11 subscription/templates/comments/email_comment_template.html
  25. 0 ...ption/{examples/yourlabs/templatetags/subscription_yourlabs_tags.py → templates/dropdown_tags.py}
  26. 0 subscription/{examples/yourlabs → }/templates/subscription/base.html
  27. 0 subscription/{examples/yourlabs → }/templates/subscription/dropdown.html
  28. 0 subscription/{examples/yourlabs → }/templates/subscription/list.html
  29. 0 subscription/{examples/yourlabs → }/templates/subscription/notifications/base.html
  30. 0 subscription/{examples/yourlabs → }/templates/subscription/notifications/comment.html
  31. 0 subscription/{examples/yourlabs → }/templates/subscription/notifications/follow.html
  32. 0 subscription/{examples/yourlabs → }/templates/subscription/notifications/message.html
  33. 0 subscription/{examples/yourlabs → }/templates/subscription/widget.html
  34. +2 −0 subscription/templatetags/social_tags.py
  35. +0 −1 subscription/tests.py
  36. +34 −2 subscription/urls.py
  37. +3 −0 subscription/views/__init__.py
  38. +1 −26 subscription/{examples/yourlabs/views.py → views/dropdown.py}
  39. +34 −0 subscription/views/notification.py
  40. 0 subscription/{views.py → views/subscription.py}
  41. BIN subscription_test_project/default.sqlite
  42. +1 −0 subscription_test_project/urls.py
View
@@ -0,0 +1,2 @@
+Steve Yaego
+James Pic
View
40 README
@@ -1,40 +0,0 @@
-My goal for this is flexibility without too much structure. Contrast this to:
-
-https://github.com/justquick/django-activity-stream
-
-Which in my opinion is too restrictive in its whole actor/verb setup and
-seems to have unrealistic performance for even medium-size websites (see
-their user_stream method and imagine 60k users and 500k notifications)
-
-I also don't like how Subscription frameworks and Notification frameworks tend
-to be packaged separately. I can't imagine one without the other, so here's both.
-
-SUBSCRIBING:
-
-Subscription.objects.subscribe(user,content_object) # Subscribes a user
-
-EMITTING:
-
-Subscription.objects.emit("Some thing happened to this object!", subscribers_of=content_object)
-
-The args/kwargs to emit() are more or less shuttled straight to the SUBSCRIPTION_BACKEND(s),
-which is a dict in your settings.py like:
-
-SUBSCRIPTION_BACKENDS = {
- 'email': 'myproject.subscription_backends.Email',
- 'redis': 'myproject.subscription_backends.Redis',
-}
-
-Writing a backend:
-
-You can subclass subscription.backends.BaseBackend. Right now the options are:
-
-text - required - The message to post to subscribers
-subscribers_of - Gets the recipients from the Subscription model
-dont_send_to - Useful for supressing comment messages to their own author, for example
-send_only_to - Useful for other things I guess
-actor_display_self - Usually 'You', in case you want to emit a notice
-actor_display_other - Usually 'Some Person' <has comment on...>
-actor - Who's doing the emitting? How we figure out the above
-format_kwargs - applied to 'text' arg (above) via string.Formatter
-**kwargs - Passed onto your backend subclass in case you need more info
View
@@ -1,16 +1,29 @@
subscription.examples.yourlabs
==============================
-What this is about
-------------------
-
Yourlabs example is a demonstration of what a cool reusable app that just adds
blazing fast facebook-like notifications to your project with minimal hassle.
-I am `is_null`, a freelancer idling on `irc.freenode.net` 24/7 for years.
-In an infinite quest for karma, and maybe because the anarchist in me loves the
-idea that any worker can share tools without real notion of property, I'd love
-to help so you can just send me private messages if you need support.
+Notifications are fun
+---------------------
+
+Emiting a comment notification looks like this::
+
+ from django.contrib.comments.signals import comment_was_posted
+
+ from subscription.examples.yourlabs import Notification
+
+ def comment_notification(sender, comment=None, **kwargs):
+ Notification(comment=comment, template='comment_notification',
+ subscribers_of=comment.content_object)).emit()
+ comment_was_posted.connect(comment_notification)
+
+And easy to setup
+-----------------
+
+::
+
+ pip install -e git+git@github.com:yourlabs/django-subscription.git#egg=subscription
Concept
-------
View
@@ -1,68 +0,0 @@
-from string import Formatter
-
-from django.core.mail import send_mail
-from django.contrib.contenttypes.models import ContentType
-
-from subscription.models import Subscription
-
-class BaseBackend(object):
- def __call__(obj,*args,**kwargs):
- return obj.emit(*args,**kwargs)
-
- def emit(self,text,subscribers_of=None,dont_send_to=None,send_only_to=None,actor=None,\
- actor_display_other=None,actor_display_self=None,format_kwargs=None,**kwargs):
- # subscribers_of - Thing people are subscribed to
- # dont_send_to / send_only_to - useful maybe?
- # text - string you want to emit.
- # actor_display_other - How does the actor appear to others? <a href="">{{ user.username }}</a>
- # actor_display_self - How does the actor appear to self? aka "You"
- # format_kwargs - Will be applied to text a la: text.format(**format_kwargs)
- # **kwargs - Maybe you wrote a backend that wants more stuff than the above!!
-
- # CAREFUL: If you send a typo-kwarg it will just be sent to emit(), so no error will raise =(
-
- self.kwargs = kwargs
-
- explicit_format_options = [i[1] for i in Formatter().parse(text)]
-
- if not subscribers_of:
- for recipient in send_only_to:
- self.emit(recipient,text,**kwargs)
-
- return
-
- self.content_type = ContentType.objects.get_for_model(subscribers_of)
- subscription_kwargs = {'content_type': self.content_type, 'object_id': subscribers_of.pk}
- if send_only_to:
- subscription_kwargs.update({'user__in': send_only_to})
-
- for i in Subscription.objects.filter(**subscription_kwargs):
- if i.user in (dont_send_to or []):
- continue
-
- if send_only_to and i.user not in send_only_to:
- continue
-
- _format_kwargs = {}
- _format_kwargs.update(format_kwargs or {})
- display_text = "%s" % text
- if "actor" in explicit_format_options:
- _format_kwargs.update({'actor': actor_display_other})
- if i.user == actor:
- _format_kwargs.update({'actor': actor_display_self})
-
- text = text.format(**_format_kwargs) # Emit, somehow.
- self.emit(i.user,text,**kwargs)
-
- def user_emit(self,user,text,**kwargs):
- raise NotImplementedError("Override this!")
-
-class SimpleEmailBackend(BaseBackend):
- def user_emit(self,user,text,**kwargs):
- if not user.email:
- return
-
- send_mail(self.get_subject(),text,None,[user.email])
-
- def get_subject(self):
- return "Here's a subject!"
@@ -30,13 +30,6 @@ def kwargs_factory(cls, comment, **kwargs):
return kwargs
@property
- def queues(self):
- queues = []
- for subscriber in self.subscribers:
- queues.append('dropdown=other,user=%s,undelivered' % subscriber.pk)
- return queues
-
- @property
def subscribers(self):
content_ct = ContentType.objects.get_for_model(self.content)
actor_ct = ContentType.objects.get_for_model(self.actor)
@@ -1,10 +0,0 @@
-import json
-import time
-from subscription import backends
-from redis import Redis
-
-class ActStream(backends.BaseBackend):
- def emit(self,user,text,**kwargs):
- conn = Redis() # Need your host settings
- item = json.dumps((time.mktime(datetime.datetime.now().timetuple()),text))
- conn.lpush("actstream::%s::undelivered" % user.pk,item)
@@ -1,23 +0,0 @@
-def user_stream(user,clear_undelivered=False):
- conn = Redis()
-
- def deserialize_stream(stream):
- stream_redux = []
- for s in stream:
- s = json.loads(s)
- stream_redux.append((datetime.datetime.fromtimestamp(s[0]),s[1]))
-
- return stream_redux
-
- if clear_undelivered:
- undelivered = conn.lrange("actstream::%s::undelivered" % user.pk,0,-1)
- for u in undelivered:
- conn.lpush("actstream::%s::unacknowledged" % user.pk,u)
-
- conn.delete("actstream::%s::undelivered" % user.pk)
-
- groups = {}
- groups['acknowledged'] = deserialize_stream(conn.lrange("actstream::%s::acknowledged" % (user.pk),0,-1))
- groups['unacknowledged'] = deserialize_stream(conn.lrange("actstream::%s::unacknowledged" % (user.pk),0,-1))
- groups['undelivered'] = deserialize_stream(conn.lrange("actstream::%s::undelivered" % (user.pk),0,-1))
- return groups
No changes.
@@ -1,6 +0,0 @@
-from django.conf import settings
-from django.db.models import signals
-from django.contrib.auth.models import User
-
-from subscription.models import Subscription
-
@@ -1,31 +0,0 @@
-from django.conf.urls.defaults import *
-
-urlpatterns = patterns('subscription.examples.yourlabs.views',
- url(r'^dropdown/ajax/$', 'dropdown_ajax', {
- 'dropdowns': ['other', 'friends', 'messages'],
- 'states': ['undelivered', 'unacknowledged', 'acknowledged'],
- 'push_states': {
- 'undelivered': 'unacknowledged',
- },
- 'counter_state': 'unacknowledged',
- }, 'subscription_dropdown_ajax'),
- url(r'^dropdown/open/$', 'dropdown_open', {
- 'push_states': {
- 'unacknowledged': 'acknowledged',
- },
- }, 'subscription_dropdown_open'),
- url(r'^dropdown/more/$', 'dropdown_more', {
- 'push_states': {
- 'undelivered': 'acknowledged',
- 'unacknowledged': 'acknowledged',
- },
- }, 'subscription_dropdown_more'),
- url(r'^$', 'list', {
- 'keys': ['dropdown=other', 'dropdown=friends', 'dropdown=messages'],
- 'states': ['undelivered', 'unacknowledged', 'acknowledged'],
- 'push_states': {
- 'undelivered': 'acknowledged',
- 'unacknowledged': 'acknowledged',
- },
- }, 'subscription_list'),
-)
@@ -42,6 +42,18 @@ def emit(self, queues=None):
for backend_module in subscription.get_backends().values():
backend_module().emit(self, queues)
+ @property
+ def queues(self):
+ queues = []
+ for subscriber in self.subscribers:
+ queues.append('dropdown=other,user=%s,undelivered' % subscriber.pk)
+ return queues
+
+ @property
+ def subscribers(self):
+ for user in Subscription.objects.subscribers_of(self.subscribers_of):
+ yield user
+
def __init__(self, **kwargs):
"""
Any keyword arguments passed to the constructor is set as an instance variable.
@@ -1,11 +0,0 @@
-{% autoescape off %}
-New comment at http://{{ domain }}{{ c.get_absolute_url }}
-
-{{ c.user_name }}{% if delete %} ({{ c.user_email }} / {{ c.user_url }} / {{ c.ip_address}} ) {% endif %} says:
-
-{{ c.comment }}
-
-=====
-{% if delete %}delete - http://{{ domain }}/comments/delete/{{ c.id }}/{% endif %}
-unsubscribe - http://{{ domain }}{% url subscription_unsubscribe subscription.content_type_id subscription.object_id %}
-{% endautoescape %}
@@ -0,0 +1,2 @@
+from subscription_tags import *
+from dropdown_tags import *
View
@@ -1 +0,0 @@
-from examples.yourlabs.tests import *
View
@@ -1,6 +1,38 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('subscription.views',
- url('unsubscribe/(?P<content_type>\d+)/(?P<object_id>\d+)/', 'unsubscribe', name="subscription_unsubscribe"),
- url('subscribe/(?P<content_type>\d+)/(?P<object_id>\d+)/', 'subscribe', name="subscription_subscribe"),
+ url(r'unsubscribe/(?P<content_type>\d+)/(?P<object_id>\d+)/',
+ 'unsubscribe', {
+ },
+ 'subscription_unsubscribe'),
+ url(r'subscribe/(?P<content_type>\d+)/(?P<object_id>\d+)/',
+ 'subscribe', {
+ }, 'subscription_subscribe'),
+ url(r'^dropdown/ajax/$', 'dropdown_ajax', {
+ 'dropdowns': ['other', 'friends', 'messages'],
+ 'states': ['undelivered', 'unacknowledged', 'acknowledged'],
+ 'push_states': {
+ 'undelivered': 'unacknowledged',
+ },
+ 'counter_state': 'unacknowledged',
+ }, 'subscription_dropdown_ajax'),
+ url(r'^dropdown/open/$', 'dropdown_open', {
+ 'push_states': {
+ 'unacknowledged': 'acknowledged',
+ },
+ }, 'subscription_dropdown_open'),
+ url(r'^dropdown/more/$', 'dropdown_more', {
+ 'push_states': {
+ 'undelivered': 'acknowledged',
+ 'unacknowledged': 'acknowledged',
+ },
+ }, 'subscription_dropdown_more'),
+ url(r'^$', 'list', {
+ 'keys': ['dropdown=other', 'dropdown=friends', 'dropdown=messages'],
+ 'states': ['undelivered', 'unacknowledged', 'acknowledged'],
+ 'push_states': {
+ 'undelivered': 'acknowledged',
+ 'unacknowledged': 'acknowledged',
+ },
+ }, 'subscription_list'),
)
@@ -0,0 +1,3 @@
+from notification import notification_list
+from dropdown import dropdown_ajax, dropdown_more, dropdown_open
+from subscription import subscribe, unsubscribe, subscriptions_for_user
@@ -6,32 +6,7 @@
from django.utils import simplejson
import subscription
-from subscription.examples.yourlabs.settings import *
-
-def list(request, keys=None, states=None, push_states=None,
- backend='storage', extra_context=None,
- template_name='subscription/list.html'):
-
- if not request.user.is_authenticated():
- return http.HttpResponseForbidden()
-
- b = subscription.get_backends()[backend]()
- context = {
- 'today': datetime.date.today(),
- }
- notifications = context['notification_list'] = []
- for key in keys:
- for state in states:
- queue = '%s,user=%s,%s' % (key, request.user.pk, state)
- context['notification_list'] += b.get_notifications(queue)
-
- for old_state, new_state in push_states.items():
- b.move_queue(queue, queue.replace(old_state, new_state))
-
- notifications = sorted(notifications, key=lambda n: n.timestamp)
-
- context.update(extra_context or {})
- return shortcuts.render(request, template_name, context)
+from settings import *
def dropdown_more(request, push_states=None, backend='storage',
template_name='subscription/list.html',
@@ -0,0 +1,34 @@
+import datetime
+
+from django import template
+from django import shortcuts
+from django import http
+from django.utils import simplejson
+
+import subscription
+from settings import *
+
+def list(request, keys=None, states=None, push_states=None,
+ backend='storage', extra_context=None,
+ template_name='subscription/list.html'):
+
+ if not request.user.is_authenticated():
+ return http.HttpResponseForbidden()
+
+ b = subscription.get_backends()[backend]()
+ context = {
+ 'today': datetime.date.today(),
+ }
+ notifications = context['notification_list'] = []
+ for key in keys:
+ for state in states:
+ queue = '%s,user=%s,%s' % (key, request.user.pk, state)
+ context['notification_list'] += b.get_notifications(queue)
+
+ for old_state, new_state in push_states.items():
+ b.move_queue(queue, queue.replace(old_state, new_state))
+
+ notifications = sorted(notifications, key=lambda n: n.timestamp)
+
+ context.update(extra_context or {})
+ return shortcuts.render(request, template_name, context)
File renamed without changes.
Binary file not shown.
@@ -13,4 +13,5 @@
url(r'^comments/', include('django.contrib.comments.urls')),
url(r'^avatar/', include('avatar.urls')),
url(r'^admin/', include(admin.site.urls)),
+ url(r"^post/(new_thread|reply)/(\d+)/$", project_specific.user_detail, name='forum_post'),
)

0 comments on commit 84c0d8b

Please sign in to comment.