diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..37d6f0f
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,31 @@
+# Based on
+# https://pypi.org/project/tox-gh-actions/
+
+---
+name: Test the application using Tox.
+
+on:
+ - push
+ - pull_request
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
+
+ steps:
+ - name: apt update
+ run: sudo apt update
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install tox tox-gh-actions
+ - name: Test with tox
+ run: tox -v
diff --git a/.travis.yml b/.travis.yml
index 92f1f96..8b07556 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,15 @@
language: python
+
python:
- - "2.7"
- - "3.4"
- "3.5"
- "3.6"
+ - "3.7"
+ - "3.8"
+
env:
- - DJANGO=1.7
- - DJANGO=1.8
- - DJANGO=1.9
- - DJANGO=1.10
- - DJANGO=1.11
- - DJANGO=2.0
+ - DJANGO=2.2
+ - DJANGO=3.0
+
install:
# command to install dependencies
- "pip install coveralls"
@@ -18,17 +17,15 @@ install:
- pip install -q Django==$DJANGO
- "pip install ."
# command to run tests
+
script:
+ - SAMPLE_APP=1 coverage run --branch --source=notifications manage.py test
- coverage run --branch --source=notifications manage.py test
+
matrix:
exclude:
- - python: "2.7"
- env: DJANGO=2.0
- python: "3.5"
- env: DJANGO=1.7
- - python: "3.6"
- env: DJANGO=1.7
-
+ env: DJANGO=3.0
after_success:
- coveralls
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d074b52
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,164 @@
+# Changelog
+
+## 1.7.0
+
+ - Added support for Django 3.2 and Django 4.0
+ - Fixed bug on IE11 for using `forEach` in notify.js
+
+## 1.6.0
+
+ - Added support to Django up to version 3.0
+ - Added `AbstractNotification` model
+ - Added prefetch for actor field in admin
+ - Added never\_cache to some views to avoid no-update bug
+
+## 1.5
+
+Now all configs for the app are made inside the dictionary
+*DJANGO\_NOTIFICATION\_CONFIG* in *settings.py*.
+
+Default configs: `` `Python DJANGO_NOTIFICATION_CONFIG = {
+'PAGINATE_BY': 20, 'USE_JSONFIELD': False, 'SOFT_DELETE': False,
+'NUM_TO_FETCH': 10, } ``\`
+
+ - Improve code quality. (@AlvaroLQueiroz)
+ - Improve url patterns and remove duplicated code. (@julianogouveia)
+ - Added a view for show all notifications. \#205 (@AlvaroLQueiroz)
+ - Added a new tag to verify if an user has unread notifications. \#164
+ (@AlvaroLQueiroz)
+ - Improve documentation. (@pandabearcoder)
+ - Fix pagination in list views. \#69 (@AlvaroLQueiroz)
+ - Improve test matrix. (@AlvaroLQueiroz)
+
+## 1.4
+
+ - Adds support for django 2.0.0 (@jphamcsp and @nemesisdesign).
+ - Adds database index for some fields (@nemesisdesign).
+ - Changes the ID-based selection to a class-based selection in the
+ methods
+ \_\_[live\_notify\_badge](THIS%20VERSION%20HAS%20BREAKING%20CHANGES__:)
+ and \_\_live\_notify\_list\_\_ (@AlvaroLQueiroz).
+ - Now extra data and slug are returned on
+ \_\_live\_unread\_notification\_list\_\_ API (@AlvaroLQueiroz).
+ - Fix documentation issues (@archatas, @yaoelvon and @AlvaroLQueiroz).
+
+## 1.3
+
+ - Redirect to unread view after mark as read. (@osminogin)
+ - Django 1.10 compability. (@osminogin)
+ - Django Admin overhead reduction by removing the need to carry all
+ recipients users. (@theromis)
+ - Added option to mark as read in
+ \_\_live\_unread\_notification\_list\_\_ endpoint. (@osminogin)
+ - Fixed parameter name error in README.rst: there is no
+ \_\_api\_url\_name\_\_ parameter, the correct name is
+ \_\_api\_name\_\_ (@ikkebr)
+ - Added \_\_sent()\_\_, \_\_unsent()\_\_, \_\_mark\_as\_sent()\_\_ and
+ \_\_mark\_as\_unsent()\_\_ methods in the queryset. (@theromis)
+ - \_\_notify.send()\_\_ now returns the list of saved Notifications
+ instances. (@satyanash)
+ - Now \_\_recipient\_\_ can be a User queryset. (@AlvaroLQueiroz)
+ - Fix XMLHttpRequest onready event handler. (@AlvaroLQueiroz)
+
+## 1.2
+
+ - Django 1.9 template tag compatibility: due to `register.simple_tag`
+ automatically espacing `unsafe_html` in Django 1.9, it is now
+ recommended to use format\_html (@ikkebr)
+ - Fixed parameter name error in README.rst: there is no to\_fetch
+ parameter, the correct name is fetch (@ikkebr)
+ - Add missing migration (@marcgibbons)
+ - Minor documentation correction (@tkwon, @zhang-z)
+ - Return updated count in QuerySet (@zhang-z)
+
+## 1.1
+
+ - Custom now() invocation got overlooked by PR \#113 (@yangyuvo)
+ - Added sentinals for unauthenticated users, preventing a 500 error
+ (@LegoStormtroopr)
+ - Fix: Mark All As read fails if soft-deleted \#126 (@zhang-z)
+
+## 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
+
+ - Able to pass level when adding notification (@Arthur)
+ - Fix deprecation notice in Django 1.8 (@ashokfernandez)
+ - Fix Python 3 support for notification model (@philroche)
+ - Bugfix for wrong user unread notification count (@Geeknux)
+ - A simple javascript API for live-updating specific fields within a
+ django template (@LegoStormtroopr)
+ - Add missing migration for Notification model (@shezadkhan137)
+
+## 0.7.0
+
+ - 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)
+ - Compatible with Python 3
+ - Test fixtures, and integrated with travis-ci
+
+## 0.6.2
+
+ - Fix README.rst reStructuredText syntax format
+ - Use relative imports
+ - Add contributors to AUTHORS.txt
+
+## 0.6.1
+
+ - Add support for custom user model
+ - mark\_as\_unread
+ - Require django-model-utils \>= 2.0.3
+ - Use different now function according
+ to the USE\_TZ setting
+
+## 0.6.0
+
+ - Improve documentation
+ - Add unicode support at admin panel or shell
+
+## 0.5.5
+
+Support for arbitrary data attribute.
+
+## 0.5.1
+
+Fix package descriptions and doc links.
+
+## 0.5
+
+First version based on
+[django-activity-stream](https://github.com/justquick/django-activity-stream)
+v0.4.3
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
deleted file mode 100644
index 023ed2a..0000000
--- a/CHANGELOG.rst
+++ /dev/null
@@ -1,147 +0,0 @@
-Changelog
-=========
-
-1.5
-----
-__THIS VERSION HAS BREAKING CHANGES__:
-Now all configs for the app are made inside the dictionary *DJANGO_NOTIFICATION_CONFIG* in *settings.py*.
-
-Default configs:
-```Python
-DJANGO_NOTIFICATION_CONFIG = {
- 'PAGINATE_BY': 20,
- 'USE_JSONFIELD': False,
- 'SOFT_DELETE': False,
- 'NUM_TO_FETCH': 10,
-}
-```
-
-- Improve code quality. (@AlvaroLQueiroz)
-- Improve url patterns and remove duplicated code. (@julianogouveia)
-- Added a view for show all notifications. #205 (@AlvaroLQueiroz)
-- Added a new tag to verify if an user has unread notifications. #164 (@AlvaroLQueiroz)
-- Improve documentation. (@pandabearcoder)
-- Fix pagination in list views. #69 (@AlvaroLQueiroz)
-- Improve test matrix. (@AlvaroLQueiroz)
-
-1.4
-----
-
-- Adds support for django 2.0.0 (@jphamcsp and @nemesisdesign).
-- Adds database index for some fields (@nemesisdesign).
-- Changes the ID-based selection to a class-based selection in the methods __live_notify_badge__ and __live_notify_list__ (@AlvaroLQueiroz).
-- Now extra data and slug are returned on __live_unread_notification_list__ API (@AlvaroLQueiroz).
-- Fix documentation issues (@archatas, @yaoelvon and @AlvaroLQueiroz).
-
-1.3
------
-
-- Redirect to unread view after mark as read. (@osminogin)
-- Django 1.10 compability. (@osminogin)
-- Django Admin overhead reduction by removing the need to carry all recipients users. (@theromis)
-- Added option to mark as read in __live_unread_notification_list__ endpoint. (@osminogin)
-- Fixed parameter name error in README.rst: there is no __api_url_name__ parameter, the correct name is __api_name__ (@ikkebr)
-- Added __sent()__, __unsent()__, __mark_as_sent()__ and __mark_as_unsent()__ methods in the queryset. (@theromis)
-- __notify.send()__ now returns the list of saved Notifications instances. (@satyanash)
-- Now __recipient__ can be a User queryset. (@AlvaroLQueiroz)
-- Fix XMLHttpRequest onready event handler. (@AlvaroLQueiroz)
-
-1.2
------
-
-- Django 1.9 template tag compatibility: due to ``register.simple_tag`` automatically espacing ``unsafe_html`` in Django 1.9, it is now recommended to use format_html (@ikkebr)
-- Fixed parameter name error in README.rst: there is no to_fetch parameter, the correct name is fetch (@ikkebr)
-- Add missing migration (@marcgibbons)
-- Minor documentation correction (@tkwon, @zhang-z)
-- Return updated count in QuerySet (@zhang-z)
-
-1.1
------
-
-- Custom now() invocation got overlooked by PR #113 (@yangyuvo)
-- Added sentinals for unauthenticated users, preventing a 500 error (@LegoStormtroopr)
-- Fix: Mark All As read fails if soft-deleted #126 (@zhang-z)
-
-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
------
-
-- Able to pass level when adding notification (@Arthur)
-- Fix deprecation notice in Django 1.8 (@ashokfernandez)
-- Fix Python 3 support for notification model (@philroche)
-- Bugfix for wrong user unread notification count (@Geeknux)
-- A simple javascript API for live-updating specific fields within a django template (@LegoStormtroopr)
-- Add missing migration for Notification model (@shezadkhan137)
-
-0.7.0
------
-
-- 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)
-- Compatible with Python 3
-- Test fixtures, and integrated with travis-ci
-
-0.6.2
------
-
-- Fix README.rst reStructuredText syntax format
-- Use relative imports
-- Add contributors to AUTHORS.txt
-
-0.6.1
------
-
-- Add support for custom user model
-- mark_as_unread
-- Require django-model-utils >= 2.0.3
-- Use different `now` function according to the `USE_TZ` setting
-
-0.6.0
------
-
-- Improve documentation
-- Add unicode support at admin panel or shell
-
-0.5.5
------
-
-Support for arbitrary data attribute.
-
-0.5.1
------
-
-Fix package descriptions and doc links.
-
-0.5
----
-
-First version based on `django-activity-stream `_ v0.4.3
diff --git a/MANIFEST.in b/MANIFEST.in
index 66ef6b8..c633a41 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
-include MANIFEST.in README.rst AUTHORS.txt LICENSE.txt CHANGELOG.rst
+include MANIFEST.in README.md AUTHORS.txt LICENSE.txt CHANGELOG.md
recursive-include notifications *.py *.html *.txt *.po
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8fa5b54
--- /dev/null
+++ b/README.md
@@ -0,0 +1,518 @@
+# `django-notifications` Documentation
+
+[](https://travis-ci.org/django-notifications/django-notifications)
+[](https://coveralls.io/github/django-notifications/django-notifications?branch=master)
+
+
+[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)
+
+The major difference between `django-notifications` and `django-activity-stream`:
+
+- `django-notifications` is for building something like Github "Notifications"
+- While `django-activity-stream` is for building Github "News Feed"
+
+Notifications are actually actions events, which are categorized by four main components.
+
+- `Actor`. The object that performed the activity.
+- `Verb`. The verb phrase that identifies the action of the activity.
+- `Action Object`. *(Optional)* The object linked to the action
+ itself.
+- `Target`. *(Optional)* The object to which the activity was
+ performed.
+
+`Actor`, `Action Object` and `Target` are `GenericForeignKeys` to any
+arbitrary Django object. An action is a description of an action that
+was performed (`Verb`) at some instant in time by some `Actor` on some
+optional `Target` that results in an `Action Object` getting
+created/updated/deleted.
+
+For example: [justquick](https://github.com/justquick/) `(actor)`
+*closed* `(verb)` [issue
+2](https://github.com/justquick/django-activity-stream/issues/2)
+`(action_object)` on
+[activity-stream](https://github.com/justquick/django-activity-stream/)
+`(target)` 12 hours ago
+
+Nomenclature of this specification is based on the Activity Streams
+Spec:
+
+## Requirements
+
+- Python 3.7, 3.8, 3.9, 3.10, 3.11
+- Django 3.2, 4.0, 4.1
+
+## Installation
+
+Installation is easy using `pip` and will install all required
+libraries.
+```bash
+$ pip install django-notifications-hq
+```
+or get it from source
+
+```bash
+$ git clone https://github.com/django-notifications/django-notifications
+$ cd django-notifications
+$ python setup.py sdist
+$ pip install dist/django-notifications-hq*
+```
+
+Note that [django-model-utils](http://pypi.python.org/pypi/django-model-utils)
+will be installed: this is required for the pass-through QuerySet manager.
+
+Then to add the Django Notifications to your project add the app
+`notifications` to your `INSTALLED_APPS` and urlconf.
+
+The app should go somewhere after all the apps that are going to be
+generating notifications like `django.contrib.auth`
+
+```python
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ ...
+ 'notifications',
+ ...
+)
+```
+
+Add the notifications urls to your urlconf:
+
+```python
+import notifications.urls
+
+urlpatterns = [
+ ...
+ 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.
+
+To run schema migration, execute
+`python manage.py migrate notifications`.
+
+## Generating Notifications
+
+Generating notifications is probably best done in a separate signal.
+
+```python
+from django.db.models.signals import post_save
+from notifications.signals import notify
+from myapp.models import MyModel
+
+def my_handler(sender, instance, created, **kwargs):
+ notify.send(instance, verb='was saved')
+
+post_save.connect(my_handler, sender=MyModel)
+```
+To generate an notification anywhere in your code, simply import the
+notify signal and send it with your actor, recipient, and verb.
+
+```python
+from notifications.signals import notify
+
+notify.send(user, recipient=user, verb='you reached level 10')
+```
+
+The complete syntax is.
+
+```python
+notify.send(actor, recipient, verb, action_object, target, level, description, public, timestamp, **kwargs)
+```
+
+Arguments:
+
+- **actor**: An object of any type. (Required) Note: Use
+ **sender** instead of **actor** if you intend to use keyword
+ arguments
+- **recipient**: A **Group** or a **User QuerySet** or a list of
+ **User**. (Required)
+- **verb**: An string. (Required)
+- **action\_object**: An object of any type. (Optional)
+- **target**: An object of any type. (Optional)
+- **level**: One of Notification.LEVELS (\'success\', \'info\',
+ \'warning\', \'error\') (default=info). (Optional)
+- **description**: An string. (Optional)
+- **public**: An boolean (default=True). (Optional)
+- **timestamp**: An tzinfo (default=timezone.now()). (Optional)
+
+### Extra data
+
+You can attach arbitrary data to your notifications by doing the
+following:
+
+- Add to your settings.py:
+ `DJANGO_NOTIFICATIONS_CONFIG = { 'USE_JSONFIELD': True}`
+
+Then, any extra arguments you pass to `notify.send(...)` will be
+attached to the `.data` attribute of the notification object. These will
+be serialised using the JSONField\'s serialiser, so you may need to take
+that into account: using only objects that will be serialised is a good
+idea.
+
+### Soft delete
+
+By default, `delete/(?P\d+)/` deletes specified notification
+record from DB. You can change this behaviour to \"mark
+`Notification.deleted` field as `True`\" by:
+
+- Add to your settings.py:
+ `DJANGO_NOTIFICATIONS_CONFIG = { 'SOFT_DELETE': True}`
+
+With this option, QuerySet methods `unread` and `read` contain one more
+filter: `deleted=False`. Meanwhile, QuerySet methods `deleted`,
+`active`, `mark_all_as_deleted`, `mark_all_as_active` are turned on. See
+more details in QuerySet methods section.
+
+## API
+
+### QuerySet methods
+
+Using `django-model-utils`, we get the ability to add queryset methods
+to not only the manager, but to all querysets that will be used,
+including related objects. This enables us to do things like:
+
+```python
+ Notification.objects.unread()
+```
+
+which returns all unread notifications. To do this for a single user, we
+can do:
+
+```python
+ user = User.objects.get(pk=pk)
+ user.notifications.unread()
+```
+
+There are some other QuerySet methods, too.
+
+#### `qs.unsent()`
+
+Return all of the unsent notifications, filtering the current queryset.
+(emailed=False)
+
+#### `qs.sent()`
+
+Return all of the sent notifications, filtering the current queryset.
+(emailed=True)
+
+#### `qs.unread()`
+
+Return all of the unread notifications, filtering the current queryset.
+When `SOFT_DELETE=True`, this filter contains `deleted=False`.
+
+#### `qs.read()`
+
+Return all of the read notifications, filtering the current queryset.
+When `SOFT_DELETE=True`, this filter contains `deleted=False`.
+
+#### `qs.mark_all_as_read()` \| `qs.mark_all_as_read(recipient)`
+
+Mark all of the unread notifications in the queryset (optionally also
+filtered by `recipient`) as read.
+
+#### `qs.mark_all_as_unread()` \| `qs.mark_all_as_unread(recipient)`
+
+Mark all of the read notifications in the queryset (optionally also
+filtered by `recipient`) as unread.
+
+#### `qs.mark_as_sent()` \| `qs.mark_as_sent(recipient)`
+
+Mark all of the unsent notifications in the queryset (optionally also
+filtered by `recipient`) as sent.
+
+#### `qs.mark_as_unsent()` \| `qs.mark_as_unsent(recipient)`
+
+Mark all of the sent notifications in the queryset (optionally also
+filtered by `recipient`) as unsent.
+
+#### `qs.deleted()`
+
+Return all notifications that have `deleted=True`, filtering the current
+queryset. Must be used with `SOFT_DELETE=True`.
+
+#### `qs.active()`
+
+Return all notifications that have `deleted=False`, filtering the
+current queryset. Must be used with `DELETE=True`.
+
+#### `qs.mark_all_as_deleted()` \| `qs.mark_all_as_deleted(recipient)`
+
+Mark all notifications in the queryset (optionally also filtered by
+`recipient`) as `deleted=True`. Must be used with `DELETE=True`.
+
+#### `qs.mark_all_as_active()` \| `qs.mark_all_as_active(recipient)`
+
+Mark all notifications in the queryset (optionally also filtered by
+`recipient`) as `deleted=False`. Must be used with `SOFT_DELETE=True`.
+
+### Model methods
+
+#### `obj.timesince([datetime])`
+
+A wrapper for Django\'s `timesince` function.
+
+#### `obj.mark_as_read()`
+
+Mark the current object as read.
+
+### Template tags
+
+Put `{% load notifications\_tags %}` in the template before
+you actually use notification tags.
+
+### `notifications_unread`
+
+```python
+ {% notifications_unread %}
+```
+
+Give the number of unread notifications for a user, or nothing (an empty
+string) for an anonymous user.
+
+Storing the count in a variable for further processing is advised, such
+as:
+
+```python
+ {% notifications_unread as unread_count %}
+ ...
+ {% if unread_count %}
+ You have {{ unread_count }} unread notifications.
+ {% endif %}
+```
+
+## Live-updater 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.
+
+There are two possible API calls that can be made:
+
+1. `api/unread_count/` that returns a javascript object with 1 key:
+ `unread_count` eg:
+
+ {"unread_count":1}
+
+2. `api/unread_list/` that returns a javascript object with 2 keys:
+ `unread_count` and `unread_list` eg:
+
+ {
+ "unread_count":1,
+ "unread_list":[--list of json representations of notifications--]
+ }
+
+ Representations of notifications are based on the django method:
+ `model_to_dict`
+
+ Query string arguments:
+
+ - **max** - maximum length of unread list.
+ - **mark\_as\_read** - mark notification in list as read.
+
+ For example, get `api/unread_list/?max=3&mark_as_read=true` returns
+ 3 notifications and mark them read (remove from list on next
+ request).
+
+### How to use:
+
+1. Put `{% load notifications_tags %}` in the template before you
+ actually use notification tags.
+
+2. In the area where you are loading javascript resources add the
+ following tags in the order below:
+
+
+ {% register_notify_callbacks callbacks='fill_notification_list,fill_notification_badge' %}
+
+ `register_notify_callbacks` takes the following arguments:
+
+ 1. `badge_class` (default `live_notify_badge`) - The identifier
+ `class` of the element to show the unread count,
+ that will be periodically updated.
+ 2. `menu_class` (default `live_notify_list`) - The identifier
+ `class` of the element to insert a list of unread
+ items, that will be periodically updated.
+ 3. `refresh_period` (default `15`) - How often to fetch unread
+ items from the server (integer in seconds).
+ 4. `fetch` (default `5`) - How many notifications to fetch each
+ time.
+ 5. `callbacks` (default ``) - A comma-separated list
+ of javascript functions to call each period.
+ 6. `api_name` (default `list`) - The name of the API to call (this
+ can be either `list` or `count`).
+
+3. To insert a live-updating unread count, use the following template:
+
+ {% live_notify_badge %}
+
+ `live_notify_badge` takes the following arguments:
+
+ - `badge_class` (default `live_notify_badge`) - The identifier
+ `class` for the `` element that will be created to show
+ the unread count.
+
+4. To insert a live-updating unread list, use the following template:
+
+ {% live_notify_list %}
+
+ `live_notify_list` takes the following arguments:
+
+ - `list_class` (default `live_notify_list`) - The identifier
+ `class` for the `
` element that will be created to insert
+ the list of notifications into.
+
+### Using the live-updater with bootstrap
+
+The Live-updater can be incorporated into bootstrap with minimal code.
+
+To create a live-updating bootstrap badge containing the unread count,
+simply use the template tag:
+
+ {% live_notify_badge badge_class="badge" %}
+
+To create a live-updating bootstrap dropdown menu containing a selection
+of recent unread notifications, simply use the template tag:
+
+ {% live_notify_list list_class="dropdown-menu" %}
+
+### Customising the display of notifications using javascript callbacks
+
+While the live notifier for unread counts should suit most use cases,
+users may wish to alter how unread notifications are shown.
+
+The `callbacks` argument of the `register_notify_callbacks` dictates
+which javascript functions are called when the unread api call is made.
+
+To add a custom javascript callback, simply add this to the list, like
+so:
+
+ {% register_notify_callbacks callbacks='fill_notification_badge,my_special_notification_callback' %}
+
+The above would cause the callback to update the unread count badge, and
+would call the custom function
+`my_special_notification_callback`. All callback
+functions are passed a single argument by convention called
+`data`, which contains the entire result from the API.
+
+For example, the below function would get the recent list of unread
+messages and log them to the console:
+
+```javascript
+function my_special_notification_callback(data) {
+ for (var i=0; i < data.unread_list.length; i++) {
+ msg = data.unread_list[i];
+ console.log(msg);
+ }
+}
+```
+
+### Testing the live-updater
+
+1. Clone the repo
+2. Run `./manage.py runserver`
+3. Browse to `yourserverip/test/`
+4. Click \'Make a notification\' and a new notification should appear
+ in the list in 5-10 seconds.
+
+## Serializing the django-notifications Model
+
+See here -
+
+In this example the target object can be of type Foo or Bar and the
+appropriate serializer will be used.
+
+```python
+class GenericNotificationRelatedField(serializers.RelatedField):
+
+ def to_representation(self, value):
+ if isinstance(value, Foo):
+ serializer = FooSerializer(value)
+ if isinstance(value, Bar):
+ serializer = BarSerializer(value)
+
+ return serializer.data
+
+
+class NotificationSerializer(serializers.Serializer):
+ recipient = PublicUserSerializer(User, read_only=True)
+ unread = serializers.BooleanField(read_only=True)
+ target = GenericNotificationRelatedField(read_only=True)
+```
+
+Thanks to @DaWy
+
+### `AbstractNotification` model
+
+In case you need to customize the notification model in order to add
+field or customised features that depend on your application, you can
+inherit and extend the `AbstractNotification` model, example:
+
+```python
+#In your_app/models.py
+
+from django.db import models
+from notifications.base.models import AbstractNotification
+
+
+class Notification(AbstractNotification):
+ # custom field example
+ category = models.ForeignKey('myapp.Category',
+ on_delete=models.CASCADE)
+
+ class Meta(AbstractNotification.Meta):
+ abstract = False
+```
+
+You will require to define `NOTIFICATIONS_NOTIFICATION_MODEL` setting in
+`setting.py` as follows:
+
+```python
+# In your_project/settings.py
+
+NOTIFICATIONS_NOTIFICATION_MODEL = 'your_app.Notification'
+```
+
+## Notes
+
+### Email Notification
+
+Sending email to users has not been integrated into this library. So for
+now you need to implement it if needed. There is a reserved field
+`Notification.emailed` to make it easier.
+
+### Sample App
+
+A sample app has been implemented in
+`notifications/tests/sample_notifications` that extends
+`django-notifications` with the sole purpose of testing its
+extensibility. You can run the SAMPLE APP by setting the environment
+variable `SAMPLE_APP` as follows
+
+```bash
+export SAMPLE_APP=1
+# Run the Django development server with sample_notifications app installed
+python manage.py runserver
+# Unset SAMPLE_APP to remove sample_notifications app from list of INSTALLED_APPS
+unset SAMPLE_APP
+```
+
+## `django-notifications` Team
+
+Core contributors (in alphabetical order):
+
+- [Alvaro Leonel](https://github.com/AlvaroLQueiroz)
+- [Federico Capoano](https://github.com/nemesisdesign)
+- [Samuel Spencer](https://github.com/LegoStormtroopr)
+- [Yang Yubo](https://github.com/yangyubo)
+- [YPCrumble](https://github.com/YPCrumble)
+- [Zhongyuan Zhang](https://github.com/zhang-z)
+
+## Contribute
+
+We are looking for contributors, for anyone who\'d like to contribute
+and willing to put time and energy on this project, please contact [Yang
+Yubo](https://github.com/yangyubo).
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 32048b4..0000000
--- a/README.rst
+++ /dev/null
@@ -1,429 +0,0 @@
-``django-notifications`` Documentation
-=======================================
-
-
-|build-status| |coveralls|
-
-`django-notifications `_ is a GitHub notification alike app for Django, it was derived from `django-activity-stream `_
-
-The major difference between ``django-notifications`` and ``django-activity-stream``:
-
-* ``django-notifications`` is for building something like Github "Notifications"
-* While ``django-activity-stream`` is for building Github "News Feed"
-
-Notifications are actually actions events, which are categorized by four main components.
-
-* ``Actor``. The object that performed the activity.
-* ``Verb``. The verb phrase that identifies the action of the activity.
-* ``Action Object``. *(Optional)* The object linked to the action itself.
-* ``Target``. *(Optional)* The object to which the activity was performed.
-
-``Actor``, ``Action Object`` and ``Target`` are ``GenericForeignKeys`` to any arbitrary Django object.
-An action is a description of an action that was performed (``Verb``) at some instant in time by some ``Actor`` on some optional ``Target`` that results in an ``Action Object`` getting created/updated/deleted.
-
-For example: `justquick `_ ``(actor)`` *closed* ``(verb)`` `issue 2 `_ ``(action_object)`` on `activity-stream `_ ``(target)`` 12 hours ago
-
-Nomenclature of this specification is based on the Activity Streams Spec: ``_
-
-Requirements
-============
-
-- Python 2.7, 3.4, 3.5, 3.6
-- Django 1.7, 1.8, 1.9, 1.10, 1.11, 2.0
-
-Installation
-============
-
-Installation is easy using ``pip`` and will install all required libraries.
-
-::
-
- $ pip install django-notifications-hq
-
-or get it from source
-
-::
-
- $ git clone https://github.com/django-notifications/django-notifications
- $ cd django-notifications
- $ python setup.py sdist
- $ pip install dist/django-notifications-hq*a
-
-Note that `django-model-utils `_ will be installed: this is required for the pass-through QuerySet manager.
-
-Then to add the Django Notifications to your project add the app ``notifications`` to your ``INSTALLED_APPS`` and urlconf.
-
-The app should go somewhere after all the apps that are going to be generating notifications like ``django.contrib.auth``
-
-::
-
- INSTALLED_APPS = (
- 'django.contrib.auth',
- ...
- 'notifications',
- ...
- )
-
-Add the notifications urls to your urlconf::
-
- import notifications.urls
-
- urlpatterns = [
- ...
- 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.
-
-To run schema migration, execute ``python manage.py migrate notifications``.
-
-Generating Notifications
-=========================
-
-Generating notifications is probably best done in a separate signal.
-
-::
-
- from django.db.models.signals import post_save
- from notifications.signals import notify
- from myapp.models import MyModel
-
- def my_handler(sender, instance, created, **kwargs):
- notify.send(instance, verb='was saved')
-
- post_save.connect(my_handler, sender=MyModel)
-
-To generate an notification anywhere in your code, simply import the notify signal and send it with your actor, recipient, and verb.
-
-::
-
- from notifications.signals import notify
-
- notify.send(user, recipient=user, verb='you reached level 10')
-
-The complete syntax is.
-
-::
-
- notify.send(actor, recipient, verb, action_object, target, level, description, public, timestamp, **kwargs)
-
-Arguments:
- * **actor**: An object of any type. (Required) Note: Use **sender** instead of **actor** if you intend to use keyword arguments
- * **recipient**: A **Group** or a **User QuerySet** or a list of **User**. (Required)
- * **verb**: An string. (Required)
- * **action_object**: An object of any type. (Optional)
- * **target**: An object of any type. (Optional)
- * **level**: One of Notification.LEVELS ('success', 'info', 'warning', 'error') (default=info). (Optional)
- * **description**: An string. (Optional)
- * **public**: An boolean (default=True). (Optional)
- * **timestamp**: An tzinfo (default=timezone.now()). (Optional)
-
-Extra data
-----------
-
-You can attach arbitrary data to your notifications by doing the following:
-
-* Add to your settings.py: ``DJANGO_NOTIFICATIONS_CONFIG = { 'USE_JSONFIELD': True}``
-
-Then, any extra arguments you pass to ``notify.send(...)`` will be attached to the ``.data`` attribute of the notification object.
-These will be serialised using the JSONField's serialiser, so you may need to take that into account: using only objects that will be serialised is a good idea.
-
-Soft delete
------------
-
-By default, ``delete/(?P\d+)/`` deletes specified notification record from DB.
-You can change this behaviour to "mark ``Notification.deleted`` field as ``True``" by:
-
-* Add to your settings.py: ``DJANGO_NOTIFICATIONS_CONFIG = { 'SOFT_DELETE': True}``
-
-With this option, QuerySet methods ``unread`` and ``read`` contain one more filter: ``deleted=False``.
-Meanwhile, QuerySet methods ``deleted``, ``active``, ``mark_all_as_deleted``, ``mark_all_as_active`` are turned on.
-See more details in QuerySet methods section.
-
-API
-====
-
-QuerySet methods
------------------
-
-Using ``django-model-utils``, we get the ability to add queryset methods to not only the manager, but to all querysets that will be used, including related objects. This enables us to do things like::
-
- Notification.objects.unread()
-
-which returns all unread notifications. To do this for a single user, we can do::
-
- user = User.objects.get(pk=pk)
- user.notifications.unread()
-
-There are some other QuerySet methods, too.
-
-``qs.unsent()``
-~~~~~~~~~~~~~~~
-
-Return all of the unsent notifications, filtering the current queryset. (emailed=False)
-
-``qs.sent()``
-~~~~~~~~~~~~~~~
-
-Return all of the sent notifications, filtering the current queryset. (emailed=True)
-
-``qs.unread()``
-~~~~~~~~~~~~~~~
-
-Return all of the unread notifications, filtering the current queryset.
-When ``SOFT_DELETE=True``, this filter contains ``deleted=False``.
-
-``qs.read()``
-~~~~~~~~~~~~~~~
-
-Return all of the read notifications, filtering the current queryset.
-When ``SOFT_DELETE=True``, this filter contains ``deleted=False``.
-
-
-``qs.mark_all_as_read()`` | ``qs.mark_all_as_read(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all of the unread notifications in the queryset (optionally also filtered by ``recipient``) as read.
-
-
-``qs.mark_all_as_unread()`` | ``qs.mark_all_as_unread(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all of the read notifications in the queryset (optionally also filtered by ``recipient``) as unread.
-
-``qs.mark_as_sent()`` | ``qs.mark_as_sent(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all of the unsent notifications in the queryset (optionally also filtered by ``recipient``) as sent.
-
-
-``qs.mark_as_unsent()`` | ``qs.mark_as_unsent(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all of the sent notifications in the queryset (optionally also filtered by ``recipient``) as unsent.
-
-``qs.deleted()``
-~~~~~~~~~~~~~~~~
-
-Return all notifications that have ``deleted=True``, filtering the current queryset.
-Must be used with ``SOFT_DELETE=True``.
-
-``qs.active()``
-~~~~~~~~~~~~~~~
-
-Return all notifications that have ``deleted=False``, filtering the current queryset.
-Must be used with ``DELETE=True``.
-
-``qs.mark_all_as_deleted()`` | ``qs.mark_all_as_deleted(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all notifications in the queryset (optionally also filtered by ``recipient``) as ``deleted=True``.
-Must be used with ``DELETE=True``.
-
-``qs.mark_all_as_active()`` | ``qs.mark_all_as_active(recipient)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Mark all notifications in the queryset (optionally also filtered by ``recipient``) as ``deleted=False``.
-Must be used with ``SOFT_DELETE=True``.
-
-
-Model methods
--------------
-
-``obj.timesince([datetime])``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A wrapper for Django's ``timesince`` function.
-
-``obj.mark_as_read()``
-~~~~~~~~~~~~~~~~~~~~~~
-
-Mark the current object as read.
-
-
-Template tags
--------------
-
-Put `{% load notifications_tags %}` in the template before you actually use notification tags.
-
-
-``notifications_unread``
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-::
-
- {% notifications_unread %}
-
-Give the number of unread notifications for a user, or nothing (an empty string) for an anonymous user.
-
-Storing the count in a variable for further processing is advised, such as::
-
- {% notifications_unread as unread_count %}
- ...
- {% if unread_count %}
- You have {{ unread_count }} unread notifications.
- {% endif %}
-
-Live-updater 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.
-
-There are two possible API calls that can be made:
-
-1. ``api/unread_count/`` that returns a javascript object with 1 key: ``unread_count`` eg::
-
- {"unread_count":1}
-
-#. ``api/unread_list/`` that returns a javascript object with 2 keys: `unread_count` and `unread_list` eg::
-
- {
- "unread_count":1,
- "unread_list":[--list of json representations of notifications--]
- }
-
- Representations of notifications are based on the django method: ``model_to_dict``
-
- Query string arguments:
-
- - **max** - maximum length of unread list.
- - **mark_as_read** - mark notification in list as read.
-
- For example, get ``api/unread_list/?max=3&mark_as_read=true`` returns 3 notifications and mark them read (remove from list on next request).
-
-
-How to use:
------------
-
-1. Put ``{% load notifications_tags %}`` in the template before you actually use notification tags.
-2. In the area where you are loading javascript resources add the following tags in the order below::
-
-
- {% register_notify_callbacks callbacks='fill_notification_list,fill_notification_badge' %}
-
- ``register_notify_callbacks`` takes the following arguments:
-
- 1. ``badge_class`` (default ``live_notify_badge``) - The identifier `class` of the element to show the unread count, that will be periodically updated.
- #. ``menu_class`` (default ``live_notify_list``) - The identifier `class` of the element to insert a list of unread items, that will be periodically updated.
- #. ``refresh_period`` (default ``15``) - How often to fetch unread items from the server (integer in seconds).
- #. ``fetch`` (default ``5``) - How many notifications to fetch each time.
- #. ``callbacks`` (default ````) - A comma-separated list of javascript functions to call each period.
- #. ``api_name`` (default ``list``) - The name of the API to call (this can be either ``list`` or ``count``).
-
-3. To insert a live-updating unread count, use the following template::
-
- {% live_notify_badge %}
-
- ``live_notify_badge`` takes the following arguments:
-
- 1. ``badge_class`` (default ``live_notify_badge``) - The identifier ``class`` for the ```` element that will be created to show the unread count.
-
-4. To insert a live-updating unread list, use the following template::
-
- {% live_notify_list %}
-
- ``live_notify_list`` takes the following arguments:
-
- 1. ``list_class`` (default ``live_notify_list``) - The identifier ``class`` for the ``
`` element that will be created to insert the list of notifications into.
-
-Using the live-updater with bootstrap
--------------------------------------
-
-The Live-updater can be incorporated into bootstrap with minimal code.
-
-To create a live-updating bootstrap badge containing the unread count, simply use the template tag::
-
- {% live_notify_badge badge_class="badge" %}
-
-To create a live-updating bootstrap dropdown menu containing a selection of recent unread notifications, simply use the template tag::
-
- {% live_notify_list list_class="dropdown-menu" %}
-
-Customising the display of notifications using javascript callbacks
--------------------------------------------------------------------
-
-While the live notifier for unread counts should suit most use cases, users may wish to alter how
-unread notifications are shown.
-
-The ``callbacks`` argument of the ``register_notify_callbacks`` dictates which javascript functions are called when
-the unread api call is made.
-
-To add a custom javascript callback, simply add this to the list, like so::
-
- {% register_notify_callbacks callbacks='fill_notification_badge,my_special_notification_callback' %}
-
-The above would cause the callback to update the unread count badge, and would call the custom function `my_special_notification_callback`.
-All callback functions are passed a single argument by convention called `data`, which contains the entire result from the API.
-
-For example, the below function would get the recent list of unread messages and log them to the console::
-
- function my_special_notification_callback(data) {
- for (var i=0; i < data.unread_list.length; i++) {
- msg = data.unread_list[i];
- console.log(msg);
- }
- }
-
-Testing the live-updater
-------------------------
-
-1. Clone the repo
-2. Run `./manage.py runserver`
-3. Browse to `yourserverip/test/`
-4. Click 'Make a notification' and a new notification should appear in the list in 5-10 seconds.
-
-Serializing the django-notifications Model
-==========================================
-
-See here - http://www.django-rest-framework.org/api-guide/relations/#generic-relationships
-
-In this example the target object can be of type Foo or Bar and the appropriate serializer will be used.
-
-::
-
- class GenericNotificationRelatedField(serializers.RelatedField):
-
- def to_representation(self, value):
- if isinstance(value, Foo):
- serializer = FooSerializer(value)
- if isinstance(value, Bar):
- serializer = BarSerializer(value)
-
- return serializer.data
-
-
- class NotificationSerializer(serializers.Serializer):
- recipient = PublicUserSerializer(User, read_only=True)
- unread = serializers.BooleanField(read_only=True)
- target = GenericNotificationRelatedField(read_only=True)
-
-Thanks to @DaWy
-
-Notes
-=====
-
-Email Notification
-------------------
-
-Sending email to users has not been integrated into this library. So for now you need to implement it if needed. There is a reserved field `Notification.emailed` to make it easier.
-
-
-``django-notifications`` Team
-==============================
-
-Core contributors (in alphabetical order):
-
-- `Alvaro Leonel `_
-- `Samuel Spencer `_
-- `Yang Yubo `_
-- `Zhongyuan Zhang `_
-
-.. |build-status| image:: https://travis-ci.org/django-notifications/django-notifications.svg
- :target: https://travis-ci.org/django-notifications/django-notifications
-
-.. |coveralls| image:: https://coveralls.io/repos/django-notifications/django-notifications/badge.png?branch=master
- :alt: Code coverage on coveralls
- :scale: 100%
- :target: https://coveralls.io/r/django-notifications/django-notifications?branch=master
diff --git a/notifications/__init__.py b/notifications/__init__.py
index 6a254b8..55a4735 100644
--- a/notifications/__init__.py
+++ b/notifications/__init__.py
@@ -8,6 +8,6 @@
"""
# PEP 386-compliant version number: N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
-__version__ = '1.4.0'
+__version__ = '1.7.0'
default_app_config = 'notifications.apps.Config' # pylint: disable=invalid-name
diff --git a/notifications/admin.py b/notifications/admin.py
index 72b9e5d..3d9c172 100644
--- a/notifications/admin.py
+++ b/notifications/admin.py
@@ -1,14 +1,21 @@
''' Django notifications admin file '''
# -*- coding: utf-8 -*-
from django.contrib import admin
-from .models import Notification
+from notifications.base.admin import AbstractNotificationAdmin
+from swapper import load_model
+
+Notification = load_model('notifications', 'Notification')
-class NotificationAdmin(admin.ModelAdmin):
+class NotificationAdmin(AbstractNotificationAdmin):
raw_id_fields = ('recipient',)
list_display = ('recipient', 'actor',
'level', 'target', 'unread', 'public')
list_filter = ('level', 'unread', 'public', 'timestamp',)
+ def get_queryset(self, request):
+ qs = super(NotificationAdmin, self).get_queryset(request)
+ return qs.prefetch_related('actor')
+
admin.site.register(Notification, NotificationAdmin)
diff --git a/notifications/apps.py b/notifications/apps.py
index 2a1a387..1480c3a 100644
--- a/notifications/apps.py
+++ b/notifications/apps.py
@@ -5,6 +5,7 @@ from django.apps import AppConfig
class Config(AppConfig):
name = "notifications"
+ default_auto_field = 'django.db.models.AutoField'
def ready(self):
super(Config, self).ready()
diff --git a/notifications/base/__init__.py b/notifications/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/notifications/base/admin.py b/notifications/base/admin.py
new file mode 100644
index 0000000..29c20c2
--- /dev/null
+++ b/notifications/base/admin.py
@@ -0,0 +1,12 @@
+from django.contrib import admin
+
+
+class AbstractNotificationAdmin(admin.ModelAdmin):
+ raw_id_fields = ('recipient',)
+ list_display = ('recipient', 'actor',
+ 'level', 'target', 'unread', 'public')
+ list_filter = ('level', 'unread', 'public', 'timestamp',)
+
+ def get_queryset(self, request):
+ qs = super(AbstractNotificationAdmin, self).get_queryset(request)
+ return qs.prefetch_related('actor')
diff --git a/notifications/base/models.py b/notifications/base/models.py
new file mode 100644
index 0000000..43f9ee3
--- /dev/null
+++ b/notifications/base/models.py
@@ -0,0 +1,313 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=too-many-lines
+from distutils.version import \
+ StrictVersion # pylint: disable=no-name-in-module,import-error
+
+from django import get_version
+from django.conf import settings
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ImproperlyConfigured
+from django.db import models
+from django.db.models.query import QuerySet
+from django.utils import timezone
+from jsonfield.fields import JSONField
+from model_utils import Choices
+
+from notifications import settings as notifications_settings
+from notifications.signals import notify
+from notifications.utils import id2slug
+from swapper import load_model
+
+if StrictVersion(get_version()) >= StrictVersion('1.8.0'):
+ from django.contrib.contenttypes.fields import GenericForeignKey # noqa
+else:
+ from django.contrib.contenttypes.generic import GenericForeignKey # noqa
+
+
+EXTRA_DATA = notifications_settings.get_config()['USE_JSONFIELD']
+
+
+def is_soft_delete():
+ return notifications_settings.get_config()['SOFT_DELETE']
+
+
+def assert_soft_delete():
+ if not is_soft_delete():
+ # msg = """To use 'deleted' field, please set 'SOFT_DELETE'=True in settings.
+ # Otherwise NotificationQuerySet.unread and NotificationQuerySet.read do NOT filter by 'deleted' field.
+ # """
+ msg = 'REVERTME'
+ raise ImproperlyConfigured(msg)
+
+
+class NotificationQuerySet(models.query.QuerySet):
+ ''' Notification QuerySet '''
+ def unsent(self):
+ return self.filter(emailed=False)
+
+ def sent(self):
+ return self.filter(emailed=True)
+
+ def unread(self, include_deleted=False):
+ """Return only unread items in the current queryset"""
+ if is_soft_delete() and not include_deleted:
+ return self.filter(unread=True, deleted=False)
+
+ # When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
+ # In this case, to improve query performance, don't filter by 'deleted' field
+ return self.filter(unread=True)
+
+ def read(self, include_deleted=False):
+ """Return only read items in the current queryset"""
+ if is_soft_delete() and not include_deleted:
+ return self.filter(unread=False, deleted=False)
+
+ # When SOFT_DELETE=False, developers are supposed NOT to touch 'deleted' field.
+ # In this case, to improve query performance, don't filter by 'deleted' field
+ return self.filter(unread=False)
+
+ def mark_all_as_read(self, recipient=None):
+ """Mark as read any unread messages in the current queryset.
+
+ Optionally, filter these by recipient first.
+ """
+ # We want to filter out read ones, as later we will store
+ # the time they were marked as read.
+ qset = self.unread(True)
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+
+ return qset.update(unread=False)
+
+ def mark_all_as_unread(self, recipient=None):
+ """Mark as unread any read messages in the current queryset.
+
+ Optionally, filter these by recipient first.
+ """
+ qset = self.read(True)
+
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+
+ return qset.update(unread=True)
+
+ def deleted(self):
+ """Return only deleted items in the current queryset"""
+ assert_soft_delete()
+ return self.filter(deleted=True)
+
+ def active(self):
+ """Return only active(un-deleted) items in the current queryset"""
+ assert_soft_delete()
+ return self.filter(deleted=False)
+
+ def mark_all_as_deleted(self, recipient=None):
+ """Mark current queryset as deleted.
+ Optionally, filter by recipient first.
+ """
+ assert_soft_delete()
+ qset = self.active()
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+
+ return qset.update(deleted=True)
+
+ def mark_all_as_active(self, recipient=None):
+ """Mark current queryset as active(un-deleted).
+ Optionally, filter by recipient first.
+ """
+ assert_soft_delete()
+ qset = self.deleted()
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+
+ return qset.update(deleted=False)
+
+ def mark_as_unsent(self, recipient=None):
+ qset = self.sent()
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+ return qset.update(emailed=False)
+
+ def mark_as_sent(self, recipient=None):
+ qset = self.unsent()
+ if recipient:
+ qset = qset.filter(recipient=recipient)
+ return qset.update(emailed=True)
+
+
+class AbstractNotification(models.Model):
+ """
+ Action model describing the actor acting out a verb (on an optional
+ target).
+ Nomenclature based on http://activitystrea.ms/specs/atom/1.0/
+
+ Generalized Format::
+
+