This commit is contained in:
Samuel Spencer 2016-02-08 03:57:02 +00:00
commit 1b85a996df
30 changed files with 350 additions and 983 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ MANIFEST
.coverage .coverage
htmlcov htmlcov
/.idea/ /.idea/
*.egg-info

View file

@ -1,27 +1,27 @@
language: python language: python
python: python:
- "2.6"
- "2.7" - "2.7"
- "3.3" - "3.3"
- "3.4" - "3.4"
- "3.5"
env: env:
- DJANGO=1.6
- DJANGO=1.7 - DJANGO=1.7
- DJANGO=1.8 - DJANGO=1.8
- DJANGO=1.9
install: install:
# command to install dependencies # command to install dependencies
- "pip install coveralls" - "pip install coveralls"
- "pip install pytz" # Needed for tests - "pip install pytz" # Needed for tests
- pip install -q Django==$DJANGO --use-mirrors - pip install -q Django==$DJANGO
- "pip install ." - "pip install ."
# command to run tests # command to run tests
script: script:
- coverage run --branch --source=notifications manage.py test - coverage run --branch --source=notifications manage.py test
matrix: matrix:
exclude: exclude:
- python: "2.6" - python: "3.5"
env: DJANGO=1.7 env: DJANGO=1.7
- python: "2.6" - python: "3.3"
env: DJANGO=1.8 env: DJANGO=1.9
after_success: after_success:
- coveralls - coveralls

View file

@ -1,8 +1,38 @@
Changelog Changelog
========== =========
1.0
-----
The first major version that requires Django 1.7+.
- Drop support for Django 1.6 and below (@zhang-z)
- Django 1.9 compability (@illing2005)
- Now depends on Django built-in migration facility, "south_migrations" dependence was removed (@zhang-z)
- Make django-notification compatible with django-model-utils >= 2.4 ( #87, #88, #90 ) (@zhang-z)
- Fix a RemovedInDjango110Warning in unittest (@zhang-z)
- Fix pep8 & use setuptools (@areski)
- Fix typo- in doc (@areski, @zhang-z)
- Add app_name in urls.py (@zhang-z)
- Use Django's vendored copy of six (@funkybob)
- Tidy with flake8 (@funkybob)
- Remove custom now() function (@funkybob, @yangyubo)
- notify.send() accepts User or Group (@Evidlo)
0.8.0
-----
0.8 is the last major version supports Django 1.4~1.6, version 0.8.0 will go into bugfix mode, no new features will be accepted.
- Bugfixes for live-updater, and added a live tester page (@LegoStormtroopr)
- Class-based classes (@alazaro)
- Fixed urls in tests (@alazaro)
- Added app_label to Notification model in order to fix a Django 1.9 deprecation warning (@Heldroe)
- django-model-utils compatible issue (must >=2.0.3 and <2.4) (@zhang-z)
- Reliable setup.py versioning (@yangyubo)
0.7.1 0.7.1
------ -----
- Able to pass level when adding notification (@Arthur) - Able to pass level when adding notification (@Arthur)
- Fix deprecation notice in Django 1.8 (@ashokfernandez) - Fix deprecation notice in Django 1.8 (@ashokfernandez)
@ -12,7 +42,7 @@ Changelog
- Add missing migration for Notification model (@shezadkhan137) - Add missing migration for Notification model (@shezadkhan137)
0.7.0 0.7.0
------ -----
- Add filters and displays to Django model Admin - Add filters and displays to Django model Admin
- Support Django 1.8, compatible with both django-south (django < 1.7) and built-in schema migration (django >= 1.7) - Support Django 1.8, compatible with both django-south (django < 1.7) and built-in schema migration (django >= 1.7)
@ -20,14 +50,14 @@ Changelog
- Test fixtures, and integrated with travis-ci - Test fixtures, and integrated with travis-ci
0.6.2 0.6.2
------ -----
- Fix README.rst reStructuredText syntax format - Fix README.rst reStructuredText syntax format
- Use relative imports - Use relative imports
- Add contributors to AUTHORS.txt - Add contributors to AUTHORS.txt
0.6.1 0.6.1
------ -----
- Add support for custom user model - Add support for custom user model
- mark_as_unread - mark_as_unread
@ -35,13 +65,13 @@ Changelog
- Use different `now` function according to the `USE_TZ` setting - Use different `now` function according to the `USE_TZ` setting
0.6.0 0.6.0
------ -----
- Improve documentation - Improve documentation
- Add unicode support at admin panel or shell - Add unicode support at admin panel or shell
0.5.5 0.5.5
------ -----
Support for arbitrary data attribute. Support for arbitrary data attribute.
@ -51,7 +81,7 @@ Support for arbitrary data attribute.
Fix package descriptions and doc links. Fix package descriptions and doc links.
0.5 0.5
----- ---
First version based on `django-activity-stream <https://github.com/justquick/django-activity-stream>`_ v0.4.3 First version based on `django-activity-stream <https://github.com/justquick/django-activity-stream>`_ v0.4.3

View file

@ -1,6 +1,7 @@
``django-notifications`` Documentation ``django-notifications`` Documentation
======================================= =======================================
|build-status| |coveralls| |build-status| |coveralls|
`django-notifications <https://github.com/django-notifications/django-notifications>`_ is a GitHub notification alike app for Django, it was derived from `django-activity-stream <https://github.com/justquick/django-activity-stream>`_ `django-notifications <https://github.com/django-notifications/django-notifications>`_ is a GitHub notification alike app for Django, it was derived from `django-activity-stream <https://github.com/justquick/django-activity-stream>`_
@ -24,6 +25,12 @@ For example: `justquick <https://github.com/justquick/>`_ ``(actor)`` *closed* `
Nomenclature of this specification is based on the Activity Streams Spec: `<http://activitystrea.ms/specs/atom/1.0/>`_ Nomenclature of this specification is based on the Activity Streams Spec: `<http://activitystrea.ms/specs/atom/1.0/>`_
Requirements
============
- Python 2.7, 3.3, 3.4, 3.5
- Django 1.7, 1.8, 1.9
Installation Installation
============ ============
@ -58,34 +65,15 @@ Add the notifications urls to your urlconf::
import notifications import notifications
urlpatterns = patterns('', urlpatterns = [
... ...
url('^inbox/notifications/', include(notifications.urls)), url('^inbox/notifications/', include(notifications.urls, namespace='notifications')),
... ...
) ]
The method of installing these urls, importing rather than using ``'notifications.urls'``, is required to ensure that the urls are installed in the ``notifications`` namespace. The method of installing these urls, importing rather than using ``'notifications.urls'``, is required to ensure that the urls are installed in the ``notifications`` namespace.
To run schema migration, execute ``python manage.py migrate notifications``.
How to migrate schema
=========================================
For Django 1.4~1.6
------------------
`django-south` support is shipped with `django-notifications`.
#. Install latest version (>=1.0) `django-south <http://pypi.python.org/pypi/South/>`_
#. Execute ``manage.py migrate notifications`` to migrate `django-notifications` schema
Note: If you use a `custom user model <https://docs.djangoproject.com/en/1.6/topics/auth/customizing/#auth-custom-user>`_ in your application, you may want to check `reverse dependencies <http://south.aeracode.org/wiki/Dependencies>`_ of South to run your migrations in the correct order.
For Django 1.7
--------------
Django 1.7 has built-in support for migrations. No need to install `django-south`.
#. Execute ``manage.py migrate notifications``
Generating Notifications Generating Notifications
========================= =========================
@ -95,7 +83,7 @@ Generating notifications is probably best done in a separate signal.
:: ::
from django.db.models.signals import post_save from django.db.models.signals import post_save
from notifications import notify from notifications.signals import notify
from myapp.models import MyModel from myapp.models import MyModel
def my_handler(sender, instance, created, **kwargs): def my_handler(sender, instance, created, **kwargs):
@ -107,11 +95,12 @@ To generate an notification anywhere in your code, simply import the notify sign
:: ::
from notifications import notify from notifications.signals import notify
notify.send(user, recipient=user, verb='you reached level 10') notify.send(user, recipient=user, verb='you reached level 10')
notify.send(comment.user, recipient=user, verb=u'replied', action_object=comment, // "recipient" can also be a Group, the notification will be sent to all the Users in the Group
notify.send(comment.user, recipient=group, verb=u'replied', action_object=comment,
description=comment.comment, target=comment.content_object) description=comment.comment, target=comment.content_object)
notify.send(follow_instance.user, recipient=follow_instance.follow_object, verb=u'has followed you', notify.send(follow_instance.user, recipient=follow_instance.follow_object, verb=u'has followed you',
@ -245,7 +234,7 @@ Storing the count in a variable for further processing is advised, such as::
Live-updater API Live-updater API
================ ================
To ensure users always have the most up-to-date notfications, `django-notifications` includes a simple javascript API To ensure users always have the most up-to-date notifications, `django-notifications` includes a simple javascript API
for updating specific fields within a django template. for updating specific fields within a django template.
There are two possible API calls that can be made: There are two possible API calls that can be made:

View file

@ -7,4 +7,4 @@ if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "notifications.tests.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "notifications.tests.settings")
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)

View file

@ -1,32 +1,13 @@
try: # -*- coding: utf-8 -*-
from notifications.signals import notify """
except ImportError: django-notifications
pass ~~~~~
A GitHub notification alike app for Django.
:copyright: (c) 2015 by django-notifications team.
:license: BSD, see LICENSE.txt for more details.
"""
try: # PEP 386-compliant version number: N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
from notifications.urls import urlpatterns __version__ = '1.0'
urls = (urlpatterns, 'notifications', 'notifications')
except ImportError:
pass
__version_info__ = {
'major': 0,
'minor': 8,
'micro': 0,
'releaselevel': 'final',
'serial': 0
}
def get_version(release_level=True):
"""
Return the formatted version information
"""
vers = ["%(major)i.%(minor)i.%(micro)i" % __version_info__]
if release_level and __version_info__['releaselevel'] != 'final':
vers.append('%(releaselevel)s%(serial)i' % __version_info__)
return ''.join(vers)
__version__ = get_version()
default_app_config = 'notifications.apps.Config'

View file

@ -3,6 +3,7 @@
from django.contrib import admin from django.contrib import admin
from .models import Notification from .models import Notification
class NotificationAdmin(admin.ModelAdmin): class NotificationAdmin(admin.ModelAdmin):
list_display = ('recipient', 'actor', list_display = ('recipient', 'actor',
'level', 'target', 'unread', 'public') 'level', 'target', 'unread', 'public')

11
notifications/apps.py Normal file
View file

@ -0,0 +1,11 @@
from django.apps import AppConfig
class Config(AppConfig):
name = "notifications"
def ready(self):
super(Config, self).ready()
# this is for backwards compability
import notifications.signals
notifications.notify = notifications.signals.notify

View file

@ -2,7 +2,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
import notifications.models from django.utils import timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='notification', model_name='notification',
name='timestamp', name='timestamp',
field=models.DateTimeField(default=notifications.models.now), field=models.DateTimeField(default=timezone.now),
), ),
] ]

View file

@ -1,8 +1,8 @@
import datetime
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django import get_version from django import get_version
from django.utils import timezone
from distutils.version import StrictVersion from distutils.version import StrictVersion
if StrictVersion(get_version()) >= StrictVersion('1.8.0'): if StrictVersion(get_version()) >= StrictVersion('1.8.0'):
@ -12,30 +12,20 @@ else:
from django.db import models from django.db import models
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from six import text_type from django.utils.six import text_type
from .utils import id2slug from .utils import id2slug
from .signals import notify from .signals import notify
from model_utils import managers, Choices from model_utils import Choices
from jsonfield.fields import JSONField from jsonfield.fields import JSONField
from django.contrib.auth.models import Group
def now():
# Needs to be be a function as USE_TZ can change based on if we are testing or not.
_now = datetime.datetime.now
if getattr(settings, 'USE_TZ'):
try:
from django.utils import timezone
_now = timezone.now
except ImportError:
pass
return _now()
#SOFT_DELETE = getattr(settings, 'NOTIFICATIONS_SOFT_DELETE', False) # SOFT_DELETE = getattr(settings, 'NOTIFICATIONS_SOFT_DELETE', False)
def is_soft_delete(): def is_soft_delete():
#TODO: SOFT_DELETE = getattr(settings, ...) doesn't work with "override_settings" decorator in unittest # TODO: SOFT_DELETE = getattr(settings, ...) doesn't work with "override_settings" decorator in unittest
# But is_soft_delete is neither a very elegant way. Should try to find better approach # But is_soft_delete is neither a very elegant way. Should try to find better approach
return getattr(settings, 'NOTIFICATIONS_SOFT_DELETE', False) return getattr(settings, 'NOTIFICATIONS_SOFT_DELETE', False)
@ -170,27 +160,23 @@ class Notification(models.Model):
verb = models.CharField(max_length=255) verb = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
target_content_type = models.ForeignKey(ContentType, related_name='notify_target', target_content_type = models.ForeignKey(ContentType, related_name='notify_target', blank=True, null=True)
blank=True, null=True)
target_object_id = models.CharField(max_length=255, blank=True, null=True) target_object_id = models.CharField(max_length=255, blank=True, null=True)
target = GenericForeignKey('target_content_type', target = GenericForeignKey('target_content_type', 'target_object_id')
'target_object_id')
action_object_content_type = models.ForeignKey(ContentType, action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
related_name='notify_action_object', blank=True, null=True) related_name='notify_action_object')
action_object_object_id = models.CharField(max_length=255, blank=True, action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
null=True) action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
action_object = GenericForeignKey('action_object_content_type',
'action_object_object_id')
timestamp = models.DateTimeField(default=now) timestamp = models.DateTimeField(default=timezone.now)
public = models.BooleanField(default=True) public = models.BooleanField(default=True)
deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False)
emailed = models.BooleanField(default=False) emailed = models.BooleanField(default=False)
data = JSONField(blank=True, null=True) data = JSONField(blank=True, null=True)
objects = managers.PassThroughManager.for_queryset_class(NotificationQuerySet)() objects = NotificationQuerySet.as_manager()
class Meta: class Meta:
ordering = ('-timestamp', ) ordering = ('-timestamp', )
@ -212,7 +198,7 @@ class Notification(models.Model):
return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
return u'%(actor)s %(verb)s %(timesince)s ago' % ctx return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
def __str__(self):#Adds support for Python 3 def __str__(self): # Adds support for Python 3
return self.__unicode__() return self.__unicode__()
def timesince(self, now=None): def timesince(self, now=None):
@ -249,31 +235,48 @@ def notify_handler(verb, **kwargs):
Handler function to create Notification instance upon action signal call. Handler function to create Notification instance upon action signal call.
""" """
# Pull the options out of kwargs
kwargs.pop('signal', None) kwargs.pop('signal', None)
recipient = kwargs.pop('recipient') recipient = kwargs.pop('recipient')
actor = kwargs.pop('sender') actor = kwargs.pop('sender')
newnotify = Notification( optional_objs = [
recipient = recipient, (kwargs.pop(opt, None), opt)
actor_content_type=ContentType.objects.get_for_model(actor), for opt in ('target', 'action_object')
actor_object_id=actor.pk, ]
verb=text_type(verb), public = bool(kwargs.pop('public', True))
public=bool(kwargs.pop('public', True)), description = kwargs.pop('description', None)
description=kwargs.pop('description', None), timestamp = kwargs.pop('timestamp', timezone.now())
timestamp=kwargs.pop('timestamp', now()), level = kwargs.pop('level', Notification.LEVELS.info)
level=kwargs.pop('level', Notification.LEVELS.info),
)
for opt in ('target', 'action_object'): # Check if User or Group
obj = kwargs.pop(opt, None) if isinstance(recipient, Group):
if not obj is None: recipients = recipient.user_set.all()
setattr(newnotify, '%s_object_id' % opt, obj.pk) else:
setattr(newnotify, '%s_content_type' % opt, recipients = [recipient]
ContentType.objects.get_for_model(obj))
if len(kwargs) and EXTRA_DATA: for recipient in recipients:
newnotify.data = kwargs newnotify = Notification(
recipient=recipient,
actor_content_type=ContentType.objects.get_for_model(actor),
actor_object_id=actor.pk,
verb=text_type(verb),
public=public,
description=description,
timestamp=timestamp,
level=level,
)
newnotify.save() # Set optional objects
for obj, opt in optional_objs:
if obj is not None:
setattr(newnotify, '%s_object_id' % opt, obj.pk)
setattr(newnotify, '%s_content_type' % opt,
ContentType.objects.get_for_model(obj))
if len(kwargs) and EXTRA_DATA:
newnotify.data = kwargs
newnotify.save()
# connect the signal # connect the signal

View file

@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
try:
from django.contrib.auth import get_user_model
except ImportError: # django < 1.5
from django.contrib.auth.models import User
else:
User = get_user_model()
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Notification'
db.create_table('notifications_notification', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('recipient', self.gf('django.db.models.fields.related.ForeignKey')(related_name='notifications', to=User)),
('readed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('actor_content_type', self.gf('django.db.models.fields.related.ForeignKey')(related_name='notify_actor', to=orm['contenttypes.ContentType'])),
('actor_object_id', self.gf('django.db.models.fields.CharField')(max_length=255)),
('verb', self.gf('django.db.models.fields.CharField')(max_length=255)),
('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('target_content_type', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='notify_target', null=True, to=orm['contenttypes.ContentType'])),
('target_object_id', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
('action_object_content_type', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='notify_action_object', null=True, to=orm['contenttypes.ContentType'])),
('action_object_object_id', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
('timestamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
('public', self.gf('django.db.models.fields.BooleanField')(default=True)),
))
db.send_create_signal('notifications', ['Notification'])
def backwards(self, orm):
# Deleting model 'Notification'
db.delete_table('notifications_notification')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'readed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Notification.data'
db.add_column('notifications_notification', 'data',
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Notification.data'
db.delete_column('notifications_notification', 'data')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'readed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Notification.unread'
db.add_column('notifications_notification', 'unread',
self.gf('django.db.models.fields.BooleanField')(default=True),
keep_default=True)
def backwards(self, orm):
# Deleting field 'Notification.unread'
db.delete_column('notifications_notification', 'unread')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'readed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
""
orm['notifications.Notification'].objects.filter(readed=True).update(unread=False)
orm['notifications.Notification'].objects.filter(readed=False).update(unread=True)
def backwards(self, orm):
"Write your backwards methods here."
orm['notifications.Notification'].objects.filter(unread=True).update(readed=False)
orm['notifications.Notification'].objects.filter(unread=False).update(readed=True)
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'readed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']
symmetrical = True

View file

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Notification.readed'
db.delete_column('notifications_notification', 'readed')
def backwards(self, orm):
# Adding field 'Notification.readed'
db.add_column('notifications_notification', 'readed',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=True)
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Notification.level'
db.add_column('notifications_notification', 'level',
self.gf('django.db.models.fields.CharField')(default='info', max_length=20),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Notification.level'
db.delete_column('notifications_notification', 'level')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': "orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.CharField', [], {'default': "'info'", 'max_length': '20'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Notification.data'
db.delete_column(u'notifications_notification', 'data')
# Adding field 'Notification.deleted'
db.add_column(u'notifications_notification', 'deleted',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'Notification.emailed'
db.add_column(u'notifications_notification', 'emailed',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Adding field 'Notification.data'
db.add_column(u'notifications_notification', 'data',
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
keep_default=False)
# Deleting field 'Notification.deleted'
db.delete_column(u'notifications_notification', 'deleted')
# Deleting field 'Notification.emailed'
db.delete_column(u'notifications_notification', 'emailed')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': u"orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'emailed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.CharField', [], {'default': "'info'", 'max_length': '20'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': u"orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Notification.data'
db.add_column(u'notifications_notification', 'data',
self.gf('jsonfield.fields.JSONField')(null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Notification.data'
db.delete_column(u'notifications_notification', 'data')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'notifications.notification': {
'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Notification'},
'action_object_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_action_object'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'action_object_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'actor_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notify_actor'", 'to': u"orm['contenttypes.ContentType']"}),
'actor_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'data': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}),
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'emailed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.CharField', [], {'default': "'info'", 'max_length': '20'}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'recipient': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': u"orm['auth.User']"}),
'target_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'notify_target'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'target_object_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'unread': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'verb': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['notifications']

View file

@ -1,11 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template import Library from django.template import Library
from django.template.base import TemplateSyntaxError
from django.template import Node
register = Library() register = Library()
@register.assignment_tag(takes_context=True) @register.assignment_tag(takes_context=True)
def notifications_unread(context): def notifications_unread(context):
user = user_context(context) user = user_context(context)
@ -13,34 +12,40 @@ def notifications_unread(context):
return '' return ''
return user.notifications.unread().count() return user.notifications.unread().count()
# Requires vanilla-js framework - http://vanilla-js.com/ # Requires vanilla-js framework - http://vanilla-js.com/
@register.simple_tag @register.simple_tag
def register_notify_callbacks(badge_id='live_notify_badge',menu_id='live_notify_list',refresh_period=15,callbacks="",api_name='list',fetch=5): def register_notify_callbacks(badge_id='live_notify_badge',
refresh_period=int(refresh_period)*1000 menu_id='live_notify_list',
refresh_period=15,
callbacks='',
api_name='list',
fetch=5):
refresh_period = int(refresh_period)*1000
if api_name=='list': if api_name == 'list':
api_url = reverse('notifications:live_unread_notification_list') api_url = reverse('notifications:live_unread_notification_list')
elif api_name=='count': elif api_name == 'count':
api_url = reverse('notifications:live_unread_notification_count') api_url = reverse('notifications:live_unread_notification_count')
else: else:
return "" return ""
definitions = """
definitions="""
notify_badge_id='{badge_id}'; notify_badge_id='{badge_id}';
notify_menu_id='{menu_id}'; notify_menu_id='{menu_id}';
notify_api_url='{api_url}'; notify_api_url='{api_url}';
notify_fetch_count='{fetch_count}'; notify_fetch_count='{fetch_count}';
notify_unread_url='{unread_url}'; notify_unread_url='{unread_url}';
notify_mark_all_unread_url='{mark_all_unread_url}'; notify_mark_all_unread_url='{mark_all_unread_url}';
notify_refresh_period={refresh};""".format( notify_refresh_period={refresh};
badge_id=badge_id, """.format(
menu_id=menu_id, badge_id=badge_id,
refresh=refresh_period, menu_id=menu_id,
api_url=api_url, refresh=refresh_period,
unread_url=reverse('notifications:unread'), api_url=api_url,
mark_all_unread_url=reverse('notifications:mark_all_as_read'), unread_url=reverse('notifications:unread'),
fetch_count=fetch mark_all_unread_url=reverse('notifications:mark_all_as_read'),
) fetch_count=fetch
)
script = "<script>"+definitions script = "<script>"+definitions
for callback in callbacks.split(','): for callback in callbacks.split(','):
@ -50,19 +55,23 @@ def register_notify_callbacks(badge_id='live_notify_badge',menu_id='live_notify_
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def live_notify_badge(context,badge_id='live_notify_badge',classes=""): def live_notify_badge(context, badge_id='live_notify_badge', classes=""):
user = user_context(context) user = user_context(context)
if not user: if not user:
return '' return ''
html="<span id='{badge_id}' class='{classes}'>{unread}</span>".format(badge_id=badge_id,classes=classes,unread=user.notifications.unread().count()) html = "<span id='{badge_id}' class='{classes}'>{unread}</span>".format(
badge_id=badge_id, classes=classes, unread=user.notifications.unread().count()
)
return html return html
@register.simple_tag @register.simple_tag
def live_notify_list(list_id='live_notify_list',classes=""): def live_notify_list(list_id='live_notify_list', classes=""):
html="<ul id='{list_id}' class='{classes}'></ul>".format(list_id=list_id,classes=classes) html = "<ul id='{list_id}' class='{classes}'></ul>".format(list_id=list_id, classes=classes)
return html return html
def user_context(context): def user_context(context):
if 'user' not in context: if 'user' not in context:
return None return None
@ -71,4 +80,4 @@ def user_context(context):
user = request.user user = request.user
if user.is_anonymous(): if user.is_anonymous():
return None return None
return user return user

View file

@ -1 +0,0 @@
from .tests import *

View file

@ -1,7 +1,7 @@
import os import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) BASE_DIR = os.path.abspath(os.path.dirname(__file__))
SECRET_KEY = 'secret_key' SECRET_KEY = 'secret_key'
SOUTH_TESTS_MIGRATE = True
DEBUG = True DEBUG = True
TESTING = True TESTING = True
@ -9,7 +9,7 @@ DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'test.sqlite3', 'NAME': 'test.sqlite3',
} }
} }
@ -35,14 +35,30 @@ STATICFILES_DIRS = [
] ]
STATIC_ROOT = os.path.join(BASE_DIR, "static-files") STATIC_ROOT = os.path.join(BASE_DIR, "static-files")
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# Need to skip migrations for now as migrations created with python2 break with python3 # Need to skip migrations for now as migrations created with python2 break with python3
# See https://code.djangoproject.com/ticket/23455 # See https://code.djangoproject.com/ticket/23455
class DisableMigrations(object): class DisableMigrations(object):
def __contains__(self, item): def __contains__(self, item):
return True return True
def __getitem__(self, item): def __getitem__(self, item):
return "notmigrations" return "notmigrations"
MIGRATION_MODULES = DisableMigrations() MIGRATION_MODULES = DisableMigrations()

View file

@ -12,7 +12,7 @@ except ImportError:
from django.test.utils import override_settings from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User, Group
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.timezone import utc, localtime from django.utils.timezone import utc, localtime
@ -34,10 +34,11 @@ class NotificationTest(TestCase):
to_user = User.objects.create(username="to", password="pwd", email="example@example.com") to_user = User.objects.create(username="to", password="pwd", email="example@example.com")
notify.send(from_user, recipient=to_user, verb='commented', action_object=from_user) notify.send(from_user, recipient=to_user, verb='commented', action_object=from_user)
notification = Notification.objects.get(recipient=to_user) notification = Notification.objects.get(recipient=to_user)
delta = timezone.now().replace(tzinfo=utc) - localtime(notification.timestamp,pytz.timezone(settings.TIME_ZONE)) delta = timezone.now().replace(tzinfo=utc) - localtime(notification.timestamp, pytz.timezone(settings.TIME_ZONE))
self.assertTrue(delta.seconds < 60) self.assertTrue(delta.seconds < 60)
# The delta between the two events will still be less than a second despite the different timezones # The delta between the two events will still be less than a second despite the different timezones
# The call to now and the immediate call afterwards will be within a short period of time, not 8 hours as the test above was originally. # The call to now and the immediate call afterwards will be within a short period of time, not 8 hours as the
# test above was originally.
@override_settings(USE_TZ=False) @override_settings(USE_TZ=False)
@override_settings(TIME_ZONE='Asia/Shanghai') @override_settings(TIME_ZONE='Asia/Shanghai')
@ -56,8 +57,13 @@ class NotificationManagersTest(TestCase):
self.message_count = 10 self.message_count = 10
self.from_user = User.objects.create(username="from2", password="pwd", email="example@example.com") self.from_user = User.objects.create(username="from2", password="pwd", email="example@example.com")
self.to_user = User.objects.create(username="to2", password="pwd", email="example@example.com") self.to_user = User.objects.create(username="to2", password="pwd", email="example@example.com")
self.to_group = Group.objects.create(name="to2_g")
self.to_group.user_set.add(self.to_user)
for i in range(self.message_count): for i in range(self.message_count):
notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user) notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user)
# Send notification to group
notify.send(self.from_user, recipient=self.to_group, verb='commented', action_object=self.from_user)
self.message_count += 1
def test_unread_manager(self): def test_unread_manager(self):
self.assertEqual(Notification.objects.unread().count(), self.message_count) self.assertEqual(Notification.objects.unread().count(), self.message_count)
@ -71,19 +77,19 @@ class NotificationManagersTest(TestCase):
self.assertEqual(Notification.objects.unread().count(), self.message_count) self.assertEqual(Notification.objects.unread().count(), self.message_count)
n = Notification.objects.filter(recipient=self.to_user).first() n = Notification.objects.filter(recipient=self.to_user).first()
n.mark_as_read() n.mark_as_read()
self.assertEqual(Notification.objects.read().count(),1) self.assertEqual(Notification.objects.read().count(), 1)
for n in Notification.objects.read(): for n in Notification.objects.read():
self.assertFalse(n.unread) self.assertFalse(n.unread)
def test_mark_all_as_read_manager(self): def test_mark_all_as_read_manager(self):
self.assertEqual(Notification.objects.unread().count(), self.message_count) self.assertEqual(Notification.objects.unread().count(), self.message_count)
Notification.objects.filter(recipient=self.to_user).mark_all_as_read() Notification.objects.filter(recipient=self.to_user).mark_all_as_read()
self.assertEqual(Notification.objects.unread().count(),0) self.assertEqual(Notification.objects.unread().count(), 0)
def test_mark_all_as_unread_manager(self): def test_mark_all_as_unread_manager(self):
self.assertEqual(Notification.objects.unread().count(), self.message_count) self.assertEqual(Notification.objects.unread().count(), self.message_count)
Notification.objects.filter(recipient=self.to_user).mark_all_as_read() Notification.objects.filter(recipient=self.to_user).mark_all_as_read()
self.assertEqual(Notification.objects.unread().count(),0) self.assertEqual(Notification.objects.unread().count(), 0)
Notification.objects.filter(recipient=self.to_user).mark_all_as_unread() Notification.objects.filter(recipient=self.to_user).mark_all_as_unread()
self.assertEqual(Notification.objects.unread().count(), self.message_count) self.assertEqual(Notification.objects.unread().count(), self.message_count)
@ -129,54 +135,60 @@ class NotificationTestPages(TestCase):
def logout(self): def logout(self):
self.client.post(reverse('admin:logout')+'?next=/', {}) self.client.post(reverse('admin:logout')+'?next=/', {})
def login(self,username,password): def login(self, username, password):
self.logout() self.logout()
response = self.client.post(reverse('login'), {'username': username, 'password': password}) response = self.client.post(reverse('login'), {'username': username, 'password': password})
self.assertEqual(response.status_code,302) self.assertEqual(response.status_code, 302)
return response return response
def test_all_messages_page(self): def test_all_messages_page(self):
self.login('to','pwd') self.login('to', 'pwd')
response = self.client.get(reverse('notifications:all')) response = self.client.get(reverse('notifications:all'))
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['notifications']),len(self.to_user.notifications.all())) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.all()))
def test_unread_messages_pages(self): def test_unread_messages_pages(self):
self.login('to','pwd') self.login('to', 'pwd')
response = self.client.get(reverse('notifications:unread')) response = self.client.get(reverse('notifications:unread'))
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['notifications']),len(self.to_user.notifications.unread())) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread()))
self.assertEqual(len(response.context['notifications']), self.message_count) self.assertEqual(len(response.context['notifications']), self.message_count)
for i,n in enumerate(self.to_user.notifications.all()): for i, n in enumerate(self.to_user.notifications.all()):
if i%3 == 0: if i % 3 == 0:
response = self.client.get(reverse('notifications:mark_as_read',args=[id2slug(n.id)])) response = self.client.get(reverse('notifications:mark_as_read', args=[id2slug(n.id)]))
self.assertEqual(response.status_code,302) self.assertEqual(response.status_code, 302)
response = self.client.get(reverse('notifications:unread')) response = self.client.get(reverse('notifications:unread'))
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['notifications']),len(self.to_user.notifications.unread())) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread()))
self.assertTrue(len(response.context['notifications']) < self.message_count) self.assertTrue(len(response.context['notifications']) < self.message_count)
response = self.client.get(reverse('notifications:mark_all_as_read')) response = self.client.get(reverse('notifications:mark_all_as_read'))
self.assertRedirects(response,reverse('notifications:all')) self.assertRedirects(response, reverse('notifications:all'))
response = self.client.get(reverse('notifications:unread')) response = self.client.get(reverse('notifications:unread'))
self.assertEqual(len(response.context['notifications']),len(self.to_user.notifications.unread())) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread()))
self.assertEqual(len(response.context['notifications']),0) self.assertEqual(len(response.context['notifications']), 0)
def test_next_pages(self): def test_next_pages(self):
self.login('to','pwd') self.login('to', 'pwd')
response = self.client.get(reverse('notifications:mark_all_as_read')+"?next="+reverse('notifications:unread')) response = self.client.get(reverse('notifications:mark_all_as_read'), data={
self.assertRedirects(response,reverse('notifications:unread')) "next": reverse('notifications:unread'),
})
self.assertRedirects(response, reverse('notifications:unread'))
slug = id2slug(self.to_user.notifications.first().id) slug = id2slug(self.to_user.notifications.first().id)
response = self.client.get(reverse('notifications:mark_as_read',args=[slug])+"?next="+reverse('notifications:unread')) response = self.client.get(reverse('notifications:mark_as_read', args=[slug]), data={
self.assertRedirects(response,reverse('notifications:unread')) "next": reverse('notifications:unread'),
})
self.assertRedirects(response, reverse('notifications:unread'))
slug = id2slug(self.to_user.notifications.first().id) slug = id2slug(self.to_user.notifications.first().id)
response = self.client.get(reverse('notifications:mark_as_unread',args=[slug])+"?next="+reverse('notifications:unread')) response = self.client.get(reverse('notifications:mark_as_unread', args=[slug]), {
self.assertRedirects(response,reverse('notifications:unread')) "next": reverse('notifications:unread'),
})
self.assertRedirects(response, reverse('notifications:unread'))
def test_delete_messages_pages(self): def test_delete_messages_pages(self):
self.login('to', 'pwd') self.login('to', 'pwd')
@ -213,64 +225,64 @@ class NotificationTestPages(TestCase):
self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread())) self.assertEqual(len(response.context['notifications']), len(self.to_user.notifications.unread()))
self.assertEqual(len(response.context['notifications']), self.message_count-1) self.assertEqual(len(response.context['notifications']), self.message_count-1)
def test_unread_count_api(self): def test_unread_count_api(self):
self.login('to', 'pwd') self.login('to', 'pwd')
response = self.client.get(reverse('notifications:live_unread_notification_count')) response = self.client.get(reverse('notifications:live_unread_notification_count'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(list(data.keys()),['unread_count']) self.assertEqual(list(data.keys()), ['unread_count'])
self.assertEqual(data['unread_count'],10) self.assertEqual(data['unread_count'], 10)
Notification.objects.filter(recipient=self.to_user).mark_all_as_read() Notification.objects.filter(recipient=self.to_user).mark_all_as_read()
response = self.client.get(reverse('notifications:live_unread_notification_count')) response = self.client.get(reverse('notifications:live_unread_notification_count'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(list(data.keys()),['unread_count']) self.assertEqual(list(data.keys()), ['unread_count'])
self.assertEqual(data['unread_count'],0) self.assertEqual(data['unread_count'], 0)
notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user) notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user)
response = self.client.get(reverse('notifications:live_unread_notification_count')) response = self.client.get(reverse('notifications:live_unread_notification_count'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(list(data.keys()),['unread_count']) self.assertEqual(list(data.keys()), ['unread_count'])
self.assertEqual(data['unread_count'],1) self.assertEqual(data['unread_count'], 1)
def test_unread_list_api(self): def test_unread_list_api(self):
self.login('to', 'pwd') self.login('to', 'pwd')
response = self.client.get(reverse('notifications:live_unread_notification_list')) response = self.client.get(reverse('notifications:live_unread_notification_list'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(sorted(list(data.keys())),['unread_count','unread_list']) self.assertEqual(sorted(list(data.keys())), ['unread_count', 'unread_list'])
self.assertEqual(data['unread_count'],10) self.assertEqual(data['unread_count'], 10)
self.assertEqual(len(data['unread_list']),5) self.assertEqual(len(data['unread_list']), 5)
response = self.client.get(reverse('notifications:live_unread_notification_list')+"?max=12") response = self.client.get(reverse('notifications:live_unread_notification_list'), data={"max": "12"})
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(sorted(list(data.keys())),['unread_count','unread_list']) self.assertEqual(sorted(list(data.keys())), ['unread_count', 'unread_list'])
self.assertEqual(data['unread_count'],10) self.assertEqual(data['unread_count'], 10)
self.assertEqual(len(data['unread_list']),10) self.assertEqual(len(data['unread_list']), 10)
# Test with a bad 'max' value # Test with a bad 'max' value
response = self.client.get(reverse('notifications:live_unread_notification_list')+"?max=this_is_wrong") response = self.client.get(reverse('notifications:live_unread_notification_list'), data={
"max": "this_is_wrong",
})
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(sorted(list(data.keys())),['unread_count','unread_list']) self.assertEqual(sorted(list(data.keys())), ['unread_count', 'unread_list'])
self.assertEqual(data['unread_count'],10) self.assertEqual(data['unread_count'], 10)
self.assertEqual(len(data['unread_list']),5) self.assertEqual(len(data['unread_list']), 5)
Notification.objects.filter(recipient=self.to_user).mark_all_as_read() Notification.objects.filter(recipient=self.to_user).mark_all_as_read()
response = self.client.get(reverse('notifications:live_unread_notification_list')) response = self.client.get(reverse('notifications:live_unread_notification_list'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(sorted(list(data.keys())),['unread_count','unread_list']) self.assertEqual(sorted(list(data.keys())), ['unread_count', 'unread_list'])
self.assertEqual(data['unread_count'],0) self.assertEqual(data['unread_count'], 0)
self.assertEqual(len(data['unread_list']),0) self.assertEqual(len(data['unread_list']), 0)
notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user) notify.send(self.from_user, recipient=self.to_user, verb='commented', action_object=self.from_user)
response = self.client.get(reverse('notifications:live_unread_notification_list')) response = self.client.get(reverse('notifications:live_unread_notification_list'))
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(sorted(list(data.keys())),['unread_count','unread_list']) self.assertEqual(sorted(list(data.keys())), ['unread_count', 'unread_list'])
self.assertEqual(data['unread_count'],1) self.assertEqual(data['unread_count'], 1)
self.assertEqual(len(data['unread_list']),1) self.assertEqual(len(data['unread_list']), 1)
self.assertEqual(data['unread_list'][0]['verb'],'commented') self.assertEqual(data['unread_list'][0]['verb'], 'commented')
def test_live_update_tags(self): def test_live_update_tags(self):
from django.shortcuts import render from django.shortcuts import render
@ -281,9 +293,9 @@ class NotificationTestPages(TestCase):
request = self.factory.get('/notification/live_updater') request = self.factory.get('/notification/live_updater')
request.user = self.to_user request.user = self.to_user
page = render(request, 'notifications/test_tags.html', {'request':request}) render(request, 'notifications/test_tags.html', {'request': request})
#TODO: Add more tests to check what is being output. # TODO: Add more tests to check what is being output.
def test_anon_user_gets_nothing(self): def test_anon_user_gets_nothing(self):
response = self.client.post(reverse('notifications:live_unread_notification_count')) response = self.client.post(reverse('notifications:live_unread_notification_count'))
@ -296,4 +308,3 @@ class NotificationTestPages(TestCase):
data = json.loads(response.content.decode('utf-8')) data = json.loads(response.content.decode('utf-8'))
self.assertEqual(data['unread_count'],0) self.assertEqual(data['unread_count'],0)
self.assertEqual(data['unread_list'],[]) self.assertEqual(data['unread_list'],[])

View file

@ -1,14 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.conf.urls import include, patterns, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.views import login
from notifications import urls import notifications.urls
import notifications.tests.views
urlpatterns = patterns('', urlpatterns = [
url(r'^login/$', 'django.contrib.auth.views.login', name='login'), # needed for Django 1.6 tests url(r'^login/$', login, name='login'), # needed for Django 1.6 tests
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^test_make/', 'notifications.tests.views.make_notification'), url(r'^test_make/', notifications.tests.views.make_notification),
url(r'^test/', 'notifications.tests.views.live_tester'), url(r'^test/', notifications.tests.views.live_tester),
url(r'^', include(urls, 'notifications')), url(r'^', include(notifications.urls, namespace='notifications')),
) ]

View file

@ -1,19 +1,18 @@
from django.conf import settings from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render
from notifications import notify from notifications import notify
import random import random
def live_tester(request): def live_tester(request):
notify.send(sender=request.user, recipient=request.user, verb='you loaded the page') notify.send(sender=request.user, recipient=request.user, verb='you loaded the page')
data = { return render(request, 'test_live.html', {
'unread_count': request.user.notifications.unread().count(), 'unread_count': request.user.notifications.unread().count(),
'notifications': request.user.notifications.all() 'notifications': request.user.notifications.all()
} })
return render(request,'test_live.html',data)
def make_notification(request): def make_notification(request):
the_notification = random.choice([ the_notification = random.choice([
@ -22,7 +21,7 @@ def make_notification(request):
'jumping the shark', 'jumping the shark',
'testing the app', 'testing the app',
'attaching the plumbus', 'attaching the plumbus',
]) ])
notify.send(sender=request.user, recipient=request.user, verb='you asked for a notification - you are '+the_notification)
notify.send(sender=request.user, recipient=request.user,
verb='you asked for a notification - you are ' + the_notification)

View file

@ -1,17 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.conf.urls import patterns, url from django.conf.urls import url
from . import views from . import views
urlpatterns = patterns('notifications.views', app_name = 'notifications'
urlpatterns = [
url(r'^$', views.AllNotificationsList.as_view(), name='all'), url(r'^$', views.AllNotificationsList.as_view(), name='all'),
url(r'^unread/$', views.UnreadNotificationsList.as_view(), name='unread'), url(r'^unread/$', views.UnreadNotificationsList.as_view(), name='unread'),
url(r'^mark-all-as-read/$', 'mark_all_as_read', name='mark_all_as_read'), url(r'^mark-all-as-read/$', views.mark_all_as_read, name='mark_all_as_read'),
url(r'^mark-as-read/(?P<slug>\d+)/$', 'mark_as_read', name='mark_as_read'), url(r'^mark-as-read/(?P<slug>\d+)/$', views.mark_as_read, name='mark_as_read'),
url(r'^mark-as-unread/(?P<slug>\d+)/$', 'mark_as_unread', name='mark_as_unread'), url(r'^mark-as-unread/(?P<slug>\d+)/$', views.mark_as_unread, name='mark_as_unread'),
url(r'^delete/(?P<slug>\d+)/$', 'delete', name='delete'), url(r'^delete/(?P<slug>\d+)/$', views.delete, name='delete'),
url(r'^api/unread_count/$', 'live_unread_notification_count', name='live_unread_notification_count'), url(r'^api/unread_count/$', views.live_unread_notification_count, name='live_unread_notification_count'),
url(r'^api/unread_list/$', 'live_unread_notification_list', name='live_unread_notification_list'), url(r'^api/unread_list/$', views.live_unread_notification_list, name='live_unread_notification_list'),
) ]

View file

@ -4,8 +4,10 @@ import sys
if sys.version > '3': if sys.version > '3':
long = int long = int
def slug2id(slug): def slug2id(slug):
return long(slug) - 110909 return long(slug) - 110909
def id2slug(id): def id2slug(id):
return id + 110909 return id + 110909

View file

@ -140,8 +140,8 @@ def live_unread_notification_list(request):
try: try:
num_to_fetch = request.GET.get('max', 5) # If they don't specify, make it 5. num_to_fetch = request.GET.get('max', 5) # If they don't specify, make it 5.
num_to_fetch = int(num_to_fetch) num_to_fetch = int(num_to_fetch)
num_to_fetch = max(1,num_to_fetch) # if num_to_fetch is negative, force at least one fetched notifications num_to_fetch = max(1, num_to_fetch) # if num_to_fetch is negative, force at least one fetched notifications
num_to_fetch = min(num_to_fetch,100) # put a sane ceiling on the number retrievable num_to_fetch = min(num_to_fetch, 100) # put a sane ceiling on the number retrievable
except ValueError: except ValueError:
num_to_fetch = 5 # If casting to an int fails, just make it 5. num_to_fetch = 5 # If casting to an int fails, just make it 5.
@ -157,8 +157,7 @@ def live_unread_notification_list(request):
struct['action_object'] = str(n.action_object) struct['action_object'] = str(n.action_object)
unread_list.append(struct) unread_list.append(struct)
data = { data = {
'unread_count':request.user.notifications.unread().count(), 'unread_count': request.user.notifications.unread().count(),
'unread_list':unread_list 'unread_list': unread_list
} }
return JsonResponse(data) return JsonResponse(data)

113
setup.py
View file

@ -1,66 +1,63 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from distutils.core import setup import re
from version import __version__ import ast
from setuptools import setup
setup(name='django-notifications-hq',
version=__version__,
description='GitHub notifications alike app for Django.', _version_re = re.compile(r'__version__\s+=\s+(.*)')
long_description=open('README.rst').read(), with open('notifications/__init__.py', 'rb') as f:
version = str(ast.literal_eval(_version_re.search(
f.read().decode('utf-8')).group(1)))
author='django-notifications team',
author_email='yang@yangyubo.com', setup(
name='django-notifications-hq',
url='http://github.com/django-notifications/django-notifications', version=version,
description='GitHub notifications alike app for Django.',
install_requires=[ long_description=open('README.rst').read(),
'django>=1.4', author='django-notifications team',
'django-model-utils>=2.0.3,<2.4', author_email='yang@yangyubo.com',
'six>=1.9.0', url='http://github.com/django-notifications/django-notifications',
'jsonfield>=1.0.3', install_requires=[
], 'django>=1.7',
'django-model-utils>=2.0.3',
test_requires=[ 'jsonfield>=1.0.3',
'django>=1.4', 'pytz'
'django-model-utils>=2.0.3,<2.4', ],
'six>=1.9.0', test_requires=[
'jsonfield>=1.0.3', 'django>=1.7',
'pytz' 'django-model-utils>=2.0.3',
], 'jsonfield>=1.0.3',
'pytz'
packages=['notifications', ],
'notifications.templatetags', packages=[
'notifications.migrations', 'notifications',
'notifications.south_migrations' 'notifications.templatetags',
], 'notifications.migrations',
],
package_data={'notifications': [ package_data={
'templates/notifications/*.html', 'static/notifications/*.js']}, 'notifications': ['templates/notifications/*.html', 'static/notifications/*.js']
},
classifiers=['Development Status :: 5 - Production/Stable', classifiers=[
'Environment :: Web Environment', 'Development Status :: 5 - Production/Stable',
'Framework :: Django', 'Environment :: Web Environment',
'Intended Audience :: Developers', 'Framework :: Django',
'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers',
'Operating System :: OS Independent', 'License :: OSI Approved :: BSD License',
# Specify the Python versions you support here. In particular, ensure 'Operating System :: OS Independent',
# that you indicate whether you support Python 2, Python 3 or both. # Specify the Python versions you support here. In particular, ensure
'Programming Language :: Python', # that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python :: 2', 'Programming Language :: Python',
'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.4', 'Topic :: Utilities'
'Topic :: Utilities' ],
], keywords='django notifications github action event stream',
license='MIT',
keywords='django notifications github action event stream', )
license='MIT',
)

View file

@ -1,20 +0,0 @@
__version_info__ = {
'major': 0,
'minor': 7,
'micro': 1,
'releaselevel': 'final',
'serial': 0
}
def get_version(release_level=True):
"""
Return the formatted version information
"""
vers = ["%(major)i.%(minor)i.%(micro)i" % __version_info__]
if release_level and __version_info__['releaselevel'] != 'final':
vers.append('%(releaselevel)s%(serial)i' % __version_info__)
return ''.join(vers)
__version__ = get_version()