Merge branch 'master' into 233

This commit is contained in:
Alvaro Leonel 2023-05-07 17:41:35 -03:00 committed by GitHub
commit 82df6b6083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1315 additions and 977 deletions

31
.github/workflows/test.yml vendored Normal file
View file

@ -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

View file

@ -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

164
CHANGELOG.md Normal file
View file

@ -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 <span class="title-ref">now</span> function according
to the <span class="title-ref">USE\_TZ</span> 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

View file

@ -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 <https://github.com/justquick/django-activity-stream>`_ v0.4.3

View file

@ -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

518
README.md Normal file
View file

@ -0,0 +1,518 @@
# `django-notifications` Documentation
[![build-status](https://travis-ci.org/django-notifications/django-notifications.svg)](https://travis-ci.org/django-notifications/django-notifications)
[![Coverage Status](https://coveralls.io/repos/github/django-notifications/django-notifications/badge.svg?branch=master)](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: <http://activitystrea.ms/specs/atom/1.0/>
## 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<slug>\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 <strong>{{ unread_count }}</strong> 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:
<script src="{% static 'notifications/notify.js' %}" type="text/javascript"></script>
{% 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 `<empty string>`) - 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 `<span>` 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 `<ul>` 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 - <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.
```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).

View file

@ -1,429 +0,0 @@
``django-notifications`` Documentation
=======================================
|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>`_
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: `<http://activitystrea.ms/specs/atom/1.0/>`_
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 <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``
::
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<slug>\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 <strong>{{ unread_count }}</strong> 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::
<script src="{% static 'notifications/notify.js' %}" type="text/javascript"></script>
{% 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 ``<empty string>``) - 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 ``<span>`` 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 ``<ul>`` 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 <https://github.com/AlvaroLQueiroz>`_
- `Samuel Spencer <https://github.com/LegoStormtroopr>`_
- `Yang Yubo <https://github.com/yangyubo>`_
- `Zhongyuan Zhang <https://github.com/zhang-z>`_
.. |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

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

View file

@ -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')

View file

@ -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::
<actor> <verb> <time>
<actor> <verb> <target> <time>
<actor> <verb> <action_object> <target> <time>
Examples::
<justquick> <reached level 60> <1 minute ago>
<brosner> <commented on> <pinax/pinax> <2 hours ago>
<washingtontimes> <started follow> <justquick> <8 minutes ago>
<mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 2 hours ago>
Unicode Representation::
justquick reached level 60 1 minute ago
mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
HTML Representation::
<a href="http://oebfare.com/">brosner</a> commented on <a href="http://github.com/pinax/pinax">pinax/pinax</a> 2 hours ago # noqa
"""
LEVELS = Choices('success', 'info', 'warning', 'error')
level = models.CharField(choices=LEVELS, default=LEVELS.info, max_length=20)
recipient = models.ForeignKey(
settings.AUTH_USER_MODEL,
blank=False,
related_name='notifications',
on_delete=models.CASCADE
)
unread = models.BooleanField(default=True, blank=False, db_index=True)
actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
actor_object_id = models.CharField(max_length=255)
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
verb = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
target_content_type = models.ForeignKey(
ContentType,
related_name='notify_target',
blank=True,
null=True,
on_delete=models.CASCADE
)
target_object_id = models.CharField(max_length=255, blank=True, null=True)
target = GenericForeignKey('target_content_type', 'target_object_id')
action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True,
related_name='notify_action_object', on_delete=models.CASCADE)
action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
action_object = GenericForeignKey('action_object_content_type', 'action_object_object_id')
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
public = models.BooleanField(default=True, db_index=True)
deleted = models.BooleanField(default=False, db_index=True)
emailed = models.BooleanField(default=False, db_index=True)
data = JSONField(blank=True, null=True)
objects = NotificationQuerySet.as_manager()
class Meta:
abstract = True
ordering = ('-timestamp',)
# speed up notifications count query
index_together = ('recipient', 'unread')
def __str__(self):
ctx = {
'actor': self.actor,
'verb': self.verb,
'action_object': self.action_object,
'target': self.target,
'timesince': self.timesince()
}
if self.target:
if self.action_object:
return u'%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago' % ctx
return u'%(actor)s %(verb)s %(target)s %(timesince)s ago' % ctx
if self.action_object:
return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
def timesince(self, now=None):
"""
Shortcut for the ``django.utils.timesince.timesince`` function of the
current timestamp.
"""
from django.utils.timesince import timesince as timesince_
return timesince_(self.timestamp, now)
@property
def slug(self):
return id2slug(self.id)
def mark_as_read(self):
if self.unread:
self.unread = False
self.save()
def mark_as_unread(self):
if not self.unread:
self.unread = True
self.save()
def notify_handler(verb, **kwargs):
"""
Handler function to create Notification instance upon action signal call.
"""
# Pull the options out of kwargs
kwargs.pop('signal', None)
recipient = kwargs.pop('recipient')
actor = kwargs.pop('sender')
optional_objs = [
(kwargs.pop(opt, None), opt)
for opt in ('target', 'action_object')
]
public = bool(kwargs.pop('public', True))
description = kwargs.pop('description', None)
timestamp = kwargs.pop('timestamp', timezone.now())
Notification = load_model('notifications', 'Notification')
level = kwargs.pop('level', Notification.LEVELS.info)
# Check if User or Group
if isinstance(recipient, Group):
recipients = recipient.user_set.all()
elif isinstance(recipient, (QuerySet, list)):
recipients = recipient
else:
recipients = [recipient]
new_notifications = []
for recipient in recipients:
newnotify = Notification(
recipient=recipient,
actor_content_type=ContentType.objects.get_for_model(actor),
actor_object_id=actor.pk,
verb=str(verb),
public=public,
description=description,
timestamp=timestamp,
level=level,
)
# 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 kwargs and EXTRA_DATA:
newnotify.data = kwargs.copy()
newnotify.save()
new_notifications.append(newnotify)
return new_notifications
# connect the signal
notify.connect(notify_handler, dispatch_uid='notifications.models.notification')

View file

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import swapper
class Migration(migrations.Migration):
@ -33,6 +32,7 @@ class Migration(migrations.Migration):
('target_content_type', models.ForeignKey(related_name='notify_target', blank=True, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)),
],
options={
'swappable': swapper.swappable_setting('notifications', 'Notification'),
'ordering': ('-timestamp',),
},
bases=(models.Model,),

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import jsonfield.fields

View file

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.utils import timezone

View file

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-05-04 15:20
from __future__ import unicode_literals
from django.db import migrations, models

View file

@ -0,0 +1,19 @@
# Generated by Django 2.0.9 on 2018-10-26 10:41
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('notifications', '0006_indexes'),
]
operations = [
migrations.AlterField(
model_name='notification',
name='timestamp',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.1.8 on 2019-04-27 21:26
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('notifications', '0007_add_timestamp_index'),
]
operations = [
migrations.AlterIndexTogether(
name='notification',
index_together={('recipient', 'unread')},
),
]

View file

@ -1,265 +1,18 @@
''' Django notifications models file '''
# -*- coding: utf-8 -*-
# pylint: disable=too-many-lines
from distutils.version import (
StrictVersion,
) # pylint: disable=no-name-in-module,import-error
from swapper import swappable_setting
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 django.utils.six import text_type
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
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
from .base.models import AbstractNotification, notify_handler # 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.
"""
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 Notification(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::
<actor> <verb> <time>
<actor> <verb> <target> <time>
<actor> <verb> <action_object> <target> <time>
Examples::
<justquick> <reached level 60> <1 minute ago>
<brosner> <commented on> <pinax/pinax> <2 hours ago>
<washingtontimes> <started follow> <justquick> <8 minutes ago>
<mitsuhiko> <closed> <issue 70> on <mitsuhiko/flask> <about 2 hours ago>
Unicode Representation::
justquick reached level 60 1 minute ago
mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago
HTML Representation::
<a href="http://oebfare.com/">brosner</a> commented on <a href="http://github.com/pinax/pinax">pinax/pinax</a> 2 hours ago # noqa
"""
LEVELS = Choices('success', 'info', 'warning', 'error')
level = models.CharField(choices=LEVELS, default=LEVELS.info, max_length=20)
recipient = models.ForeignKey(
settings.AUTH_USER_MODEL,
blank=False,
related_name='notifications',
on_delete=models.CASCADE,
)
unread = models.BooleanField(default=True, blank=False, db_index=True)
actor_content_type = models.ForeignKey(
ContentType, related_name='notify_actor', on_delete=models.CASCADE
)
actor_object_id = models.CharField(max_length=255)
actor = GenericForeignKey('actor_content_type', 'actor_object_id')
verb = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
target_content_type = models.ForeignKey(
ContentType,
related_name='notify_target',
blank=True,
null=True,
on_delete=models.CASCADE,
)
target_object_id = models.CharField(max_length=255, blank=True, null=True)
target = GenericForeignKey('target_content_type', 'target_object_id')
action_object_content_type = models.ForeignKey(
ContentType,
blank=True,
null=True,
related_name='notify_action_object',
on_delete=models.CASCADE,
)
action_object_object_id = models.CharField(max_length=255, blank=True, null=True)
action_object = GenericForeignKey(
'action_object_content_type', 'action_object_object_id'
)
timestamp = models.DateTimeField(default=timezone.now)
public = models.BooleanField(default=True, db_index=True)
deleted = models.BooleanField(default=False, db_index=True)
emailed = models.BooleanField(default=False, db_index=True)
data = JSONField(blank=True, null=True)
objects = NotificationQuerySet.as_manager()
class Meta:
ordering = ('-timestamp',)
app_label = 'notifications'
def __unicode__(self):
ctx = {
'actor': self.actor,
'verb': self.verb,
'action_object': self.action_object,
'target': self.target,
'timesince': self.timesince(),
}
if self.target:
if self.action_object:
return (
u'%(actor)s %(verb)s %(action_object)s on %(target)s %(timesince)s ago'
% ctx
)
return u'%(actor)s %(verb)s %(target)s %(timesince)s ago' % ctx
if self.action_object:
return u'%(actor)s %(verb)s %(action_object)s %(timesince)s ago' % ctx
return u'%(actor)s %(verb)s %(timesince)s ago' % ctx
def __str__(self): # Adds support for Python 3
return self.__unicode__()
def timesince(self, now=None):
"""
Shortcut for the ``django.utils.timesince.timesince`` function of the
current timestamp.
"""
from django.utils.timesince import timesince as timesince_
return timesince_(self.timestamp, now)
class Notification(AbstractNotification):
class Meta(AbstractNotification.Meta):
abstract = False
swappable = swappable_setting('notifications', 'Notification')
def naturalday(self):
"""
Shortcut for the ``humanize``.
Take a parameter humanize_type. This parameter control the which humanize method use.
Return ``today``, ``yesterday`` ,``now``, ``2 seconds ago``etc.
"""
from django.contrib.humanize.templatetags.humanize import naturalday
@ -267,80 +20,4 @@ class Notification(models.Model):
def naturaltime(self):
from django.contrib.humanize.templatetags.humanize import naturaltime
return naturaltime(self.timestamp)
@property
def slug(self):
return id2slug(self.id)
def mark_as_read(self):
if self.unread:
self.unread = False
self.save()
def mark_as_unread(self):
if not self.unread:
self.unread = True
self.save()
def notify_handler(verb, **kwargs):
"""
Handler function to create Notification instance upon action signal call.
"""
# Pull the options out of kwargs
kwargs.pop('signal', None)
recipient = kwargs.pop('recipient')
actor = kwargs.pop('sender')
optional_objs = [
(kwargs.pop(opt, None), opt) for opt in ('target', 'action_object')
]
public = bool(kwargs.pop('public', True))
description = kwargs.pop('description', None)
timestamp = kwargs.pop('timestamp', timezone.now())
level = kwargs.pop('level', Notification.LEVELS.info)
# Check if User or Group
if isinstance(recipient, Group):
recipients = recipient.user_set.all()
elif isinstance(recipient, (QuerySet, list)):
recipients = recipient
else:
recipients = [recipient]
new_notifications = []
for recipient in recipients:
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,
)
# 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 kwargs and EXTRA_DATA:
newnotify.data = kwargs
newnotify.save()
new_notifications.append(newnotify)
return new_notifications
# connect the signal
notify.connect(notify_handler, dispatch_uid='notifications.models.notification')
return naturaltime(self.timestamp)

View file

@ -2,7 +2,4 @@
# -*- coding: utf-8 -*-
from django.dispatch import Signal
notify = Signal(providing_args=[ # pylint: disable=invalid-name
'recipient', 'actor', 'verb', 'action_object', 'target', 'description',
'timestamp', 'level'
])
notify = Signal()

View file

@ -56,7 +56,9 @@ function fetch_api_data() {
if (this.status === 200){
consecutive_misfires = 0;
var data = JSON.parse(r.responseText);
registered_functions.forEach(function (func) { func(data); });
for(var i = 0; i < registered_functions.length; i++) {
registered_functions[i](data);
}
}else{
consecutive_misfires++;
}

View file

@ -0,0 +1 @@
default_app_config = 'notifications.tests.sample_notifications.apps.SampleNotificationsConfig'

View file

@ -0,0 +1,10 @@
import swapper
from django.contrib import admin
from notifications.base.admin import AbstractNotificationAdmin
Notification = swapper.load_model('notifications', 'Notification')
@admin.register(Notification)
class NotificationAdmin(AbstractNotificationAdmin):
pass

View file

@ -0,0 +1,6 @@
from notifications.apps import Config as NotificationConfig
class SampleNotificationsConfig(NotificationConfig):
name = 'notifications.tests.sample_notifications'
label = 'sample_notifications'

View file

@ -0,0 +1,48 @@
# Generated by Django 3.0.5 on 2020-04-11 12:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('level', models.CharField(choices=[('success', 'success'), ('info', 'info'), ('warning', 'warning'), ('error', 'error')], default='info', max_length=20)),
('unread', models.BooleanField(db_index=True, default=True)),
('actor_object_id', models.CharField(max_length=255)),
('verb', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('target_object_id', models.CharField(blank=True, max_length=255, null=True)),
('action_object_object_id', models.CharField(blank=True, max_length=255, null=True)),
('timestamp', models.DateTimeField(db_index=True, default=django.utils.timezone.now)),
('public', models.BooleanField(db_index=True, default=True)),
('deleted', models.BooleanField(db_index=True, default=False)),
('emailed', models.BooleanField(db_index=True, default=False)),
('data', jsonfield.fields.JSONField(blank=True, null=True)),
('details', models.CharField(blank=True, max_length=64, null=True)),
('action_object_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.ContentType')),
('actor_content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.ContentType')),
('recipient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.ContentType')),
],
options={
'ordering': ('-timestamp',),
'abstract': False,
'index_together': {('recipient', 'unread')},
},
),
]

View file

@ -0,0 +1,9 @@
from django.db import models
from notifications.base.models import AbstractNotification
class Notification(AbstractNotification):
details = models.CharField(max_length=64, blank=True, null=True)
class Meta(AbstractNotification.Meta):
abstract = False

View file

@ -0,0 +1 @@
from notifications.templatetags.notifications_tags import register

View file

@ -0,0 +1,22 @@
import os
from unittest import skipUnless
import swapper
from notifications.tests.tests import AdminTest as BaseAdminTest
from notifications.tests.tests import NotificationTest as BaseNotificationTest
Notification = swapper.load_model('notifications', 'Notification')
@skipUnless(os.environ.get('SAMPLE_APP', False), 'Running tests on standard django-notifications models')
class AdminTest(BaseAdminTest):
@classmethod
def setUpClass(cls):
super().setUpClass()
BaseAdminTest.app_name = 'sample_notifications'
@skipUnless(os.environ.get('SAMPLE_APP', False), 'Running tests on standard django-notifications models')
class NotificationTest(BaseNotificationTest):
pass

View file

@ -4,7 +4,7 @@ import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
SECRET_KEY = 'secret_key' # noqa
SECRET_KEY = 'secret_key' # noqa
DEBUG = True
TESTING = True
@ -20,20 +20,22 @@ DATABASES = {
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware'
)
# Django >= 2.0
MIDDLEWARE = MIDDLEWARE_CLASSES
INSTALLED_APPS = (
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sessions',
'notifications.tests',
'notifications',
)
]
ROOT_URLCONF = 'notifications.tests.urls'
STATIC_URL = '/static/'
@ -46,8 +48,11 @@ TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'loaders' : [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
@ -66,3 +71,9 @@ DJANGO_NOTIFICATIONS_CONFIG = {
'USE_JSONFIELD': True,
}
USE_TZ = True
if os.environ.get('SAMPLE_APP', False):
INSTALLED_APPS.remove('notifications')
INSTALLED_APPS.append('notifications.tests.sample_notifications')
NOTIFICATIONS_NOTIFICATION_MODEL = 'sample_notifications.Notification'
TEMPLATES[0]['DIRS'] += [os.path.join(BASE_DIR, '../templates')]

View file

@ -8,17 +8,22 @@ Replace this with more appropriate tests for your application.
import json
import pytz
from django.conf import settings
from django.contrib.auth.models import Group, User
from django.core.exceptions import ImproperlyConfigured
from django.db import connection
from django.template import Context, Template
from django.test import RequestFactory, TestCase
from django.test.utils import CaptureQueriesContext
from django.utils import timezone
from django.utils.timezone import localtime, utc
from notifications.models import Notification, notify_handler
from notifications.base.models import notify_handler
from notifications.signals import notify
from notifications.utils import id2slug
from swapper import load_model
Notification = load_model('notifications', 'Notification')
try:
# Django >= 1.7
@ -524,3 +529,30 @@ class TagTest(TestCase):
context = {"user":self.to_user}
output = u"True"
self.tag_test(template, context, output)
class AdminTest(TestCase):
app_name = "notifications"
def setUp(self):
self.message_count = 10
self.from_user = User.objects.create_user(username="from", password="pwd", email="example@example.com")
self.to_user = User.objects.create_user(username="to", password="pwd", email="example@example.com")
self.to_user.is_staff = True
self.to_user.is_superuser = True
self.to_user.save()
for _ in range(self.message_count):
notify.send(
self.from_user,
recipient=self.to_user,
verb='commented',
action_object=self.from_user,
)
def test_list(self):
self.client.login(username='to', password='pwd')
with CaptureQueriesContext(connection=connection) as context:
response = self.client.get(reverse('admin:{0}_notification_changelist'.format(self.app_name)))
self.assertLessEqual(len(context), 6)
self.assertEqual(response.status_code, 200, response.content)

View file

@ -4,12 +4,22 @@ from distutils.version import StrictVersion # pylint: disable=no-name-in-module
from django import get_version
from django.contrib import admin
from django.contrib.auth.views import login
from notifications.tests.views import (live_tester, # pylint: disable=no-name-in-module,import-error
make_notification)
if StrictVersion(get_version()) >= StrictVersion('2.0'):
if StrictVersion(get_version()) >= StrictVersion('2.1'):
from django.urls import include, path # noqa
from django.contrib.auth.views import LoginView
urlpatterns = [
path('test_make/', make_notification),
path('test/', live_tester),
path('login/', LoginView.as_view(), name='login'), # reverse for django login is not working
path('admin/', admin.site.urls),
path('', include('notifications.urls', namespace='notifications')),
]
elif StrictVersion(get_version()) >= StrictVersion('2.0') and StrictVersion(get_version()) < StrictVersion('2.1'):
from django.urls import include, path # noqa
from django.contrib.auth.views import login
urlpatterns = [
path('test_make/', make_notification),
path('test/', live_tester),
@ -19,6 +29,7 @@ if StrictVersion(get_version()) >= StrictVersion('2.0'):
]
else:
from django.conf.urls import include, url
from django.contrib.auth.views import login
urlpatterns = [
url(r'^login/$', login, name='login'), # reverse for django login is not working
url(r'^test_make/', make_notification),

View file

@ -1,17 +1,21 @@
# -*- coding: utf-8 -*-
''' Django Notifications exemple views '''
from distutils.version import StrictVersion # pylint: disable=no-name-in-module,import-error
''' Django Notifications example views '''
from distutils.version import \
StrictVersion # pylint: disable=no-name-in-module,import-error
from django import get_version
from django.contrib.auth.decorators import login_required
from django.forms import model_to_dict
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.generic import ListView
from notifications import settings
from notifications.models import Notification
from notifications.utils import id2slug, slug2id
from notifications.settings import get_config
from notifications.utils import id2slug, slug2id
from swapper import load_model
Notification = load_model('notifications', 'Notification')
if StrictVersion(get_version()) >= StrictVersion('1.7.0'):
from django.http import JsonResponse # noqa
@ -123,6 +127,7 @@ def delete(request, slug=None):
return redirect('notifications:all')
@never_cache
def live_unread_notification_count(request):
try:
user_is_authenticated = request.user.is_authenticated()
@ -140,6 +145,7 @@ def live_unread_notification_count(request):
return JsonResponse(data)
@never_cache
def live_unread_notification_list(request):
''' Return a json with a unread notification list '''
try:
@ -187,6 +193,7 @@ def live_unread_notification_list(request):
return JsonResponse(data)
@never_cache
def live_all_notification_list(request):
''' Return a json with a unread notification list '''
try:

View file

@ -21,24 +21,26 @@ setup(
name='django-notifications-hq',
version=version,
description='GitHub notifications alike app for Django.',
long_description=open('README.rst').read(),
long_description=open('README.md').read(),
author='django-notifications team',
author_email='yang@yangyubo.com',
url='http://github.com/django-notifications/django-notifications',
install_requires=[
'django>=1.7',
'django-model-utils>=2.0.3',
'jsonfield>=1.0.3',
'pytz'
'django>=3.2',
'django-model-utils>=3.1.0',
'jsonfield>=2.1.0',
'pytz',
'swapper'
],
test_requires=[
'django>=1.7',
'django-model-utils>=2.0.3',
'jsonfield>=1.0.3',
'django>=3.2',
'django-model-utils>=3.1.0',
'jsonfield>=2.1.0',
'pytz'
],
packages=[
'notifications',
'notifications.base',
'notifications.templatetags',
'notifications.migrations',
],
@ -53,18 +55,18 @@ setup(
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Framework :: Django',
'Framework :: Django :: 1.10',
'Framework :: Django :: 1.9',
'Framework :: Django :: 1.8',
'Framework :: Django :: 1.7',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Utilities'
],
keywords='django notifications github action event stream',

23
tox.ini
View file

@ -1,18 +1,23 @@
# -- FILE: tox.ini
[tox]
envlist =
py{27,33,34}-django17
py{27,33,34,35,36}-django{18,19,110,111}
py{34,35,36}-django200
py{37,38,39,310,311}-django32
py{38,39,310}-django40
py{38,39,310,311}-django41
[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
[testenv]
commands =
coverage run --branch --source=notifications manage.py test
deps =
coverage
django17: Django>=1.7,<1.8
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11
django111: Django>=1.11,<2.0
django200: Django>=2.0,<3.0
django32: Django>=3.2,<4.0
django40: Django>=4.0,<4.1
django41: Django>=4.1,<4.2