Skip to content

Commit dd69e7c

Browse files
committed
V1 rewrite (#2)
This is a complete rewrite of django-generic-notifications. It keeps the same generic idea but much more flexible and modern. The last release was before Django even had migrations - it still used South! - The NotificationEngine has been renamed to NotificationRegistry but behaves mostly the same. - Backends have been renamed to channels, and we now ship with 2: website and email. - Notification types work in much the same way, except for `allowed_backends` we now have `required_channels`. New features: - The website channel means it's now very easy to show notifications on the website. - Email digest with custom frequencies (daily, weekly). - Notification grouping. - Cleaned up and simplified. For example there's no more built-in queue, as it's not needed for the built-in channels. This allows people building custom channels to write their own processing functions that behave however they want. - Unit tests 🎉 - Example project
1 parent cf07329 commit dd69e7c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+4929
-1145
lines changed

.github/workflows/release.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
permissions:
9+
contents: write
10+
id-token: write
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Install uv
19+
uses: astral-sh/setup-uv@v3
20+
with:
21+
version: "latest"
22+
enable-cache: true
23+
24+
- name: Install dependencies
25+
run: uv sync --dev
26+
27+
- name: Run type checking
28+
run: uv run mypy .
29+
30+
- name: Run linting
31+
run: uv run ruff check .
32+
33+
test:
34+
runs-on: ubuntu-latest
35+
strategy:
36+
matrix:
37+
python-version: ["3.10", "3.11", "3.12", "3.13"]
38+
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Install uv
43+
uses: astral-sh/setup-uv@v3
44+
with:
45+
version: "latest"
46+
enable-cache: true
47+
48+
- name: Install dependencies
49+
run: uv sync --dev --python ${{ matrix.python-version }}
50+
51+
- name: Run tests
52+
run: uv run pytest
53+
54+
build-and-publish:
55+
needs: [lint, test]
56+
runs-on: ubuntu-latest
57+
58+
steps:
59+
- uses: actions/checkout@v4
60+
61+
- name: Install uv
62+
uses: astral-sh/setup-uv@v3
63+
with:
64+
version: "latest"
65+
enable-cache: true
66+
67+
- name: Build package
68+
run: uv build
69+
70+
- name: Publish to PyPI
71+
uses: pypa/gh-action-pypi-publish@release/v1
72+
73+
- name: Create changelog text
74+
id: changelog
75+
uses: loopwerk/tag-changelog@v1
76+
with:
77+
token: ${{ secrets.GITHUB_TOKEN }}
78+
exclude_types: other,doc,chore,build
79+
80+
- name: Create release
81+
uses: softprops/action-gh-release@v2
82+
with:
83+
body: ${{ steps.changelog.outputs.changes }}
84+
token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/tests.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Unit tests
2+
3+
on:
4+
push:
5+
branches: ["*"]
6+
pull_request:
7+
branches: ["*"]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
max-parallel: 4
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
- uses: astral-sh/setup-uv@v3
18+
- run: uv python install
19+
- run: uv sync --group dev
20+
- run: uv run pytest

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
__pycache__/
2+
*.py[oc]
3+
build/
4+
dist/
5+
wheels/
6+
*.egg-info
7+
.venv
8+
.claude
9+
.pytest_cache
10+
example/db.sqlite3
11+
/uv.lock

AUTHORS

Lines changed: 0 additions & 8 deletions
This file was deleted.

CHANGELOG

Lines changed: 0 additions & 32 deletions
This file was deleted.

CLAUDE.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Commands
6+
7+
### Development
8+
9+
```bash
10+
# Install dependencies (using uv)
11+
uv sync
12+
13+
# Run tests
14+
uv run pytest
15+
16+
# Run a single test file
17+
uv run pytest tests/test_models.py
18+
19+
# Run tests with verbose output
20+
uv run pytest -v
21+
22+
# Type checking
23+
uv run .
24+
25+
# Linting and formatting
26+
uv run ruff check .
27+
uv run ruff format .
28+
```
29+
30+
## Architecture Overview
31+
32+
This is a Django package for handling generic notifications across multiple channels (email, website, etc.) with configurable delivery frequencies.
33+
34+
### Core Components
35+
36+
1. **Registry Pattern** (`registry.py`): Central registry that manages notification types, channels, and frequencies. All components must be registered here to be available.
37+
38+
2. **Notification Types** (`types.py`): Define different kinds of notifications (e.g., SystemMessage). Each type specifies:
39+
40+
- Default email frequency (realtime vs digest)
41+
- Required channels that cannot be disabled
42+
- Dynamic subject/text generation methods
43+
- Custom channels can be added by subclassing `NotificationType`
44+
45+
3. **Channels** (`channels.py`): Delivery mechanisms for notifications:
46+
47+
- `WebsiteChannel`: Stores in database for UI display
48+
- `EmailChannel`: Sends via email (supports realtime + digest)
49+
- Custom channels can be added by subclassing `NotificationChannel`
50+
51+
4. **Frequencies** (`frequencies.py`): Email delivery timing options:
52+
53+
- `RealtimeFrequency`: Send immediately
54+
- `DailyFrequency`: Bundle into daily digest
55+
- Custom frequencies can be added by subclassing `NotificationFrequency`
56+
57+
5. **Models** (`models.py`):
58+
- `Notification`: Core notification instance with recipient, type, channels, content
59+
- `DisabledNotificationTypeChannel`: Opt-out preferences (presence = disabled)
60+
- `EmailFrequency`: Per-user email frequency preferences
61+
62+
### Key Design Decisions
63+
64+
- **Opt-out model**: Notifications are enabled by default; users disable specific type/channel combinations
65+
- **Channel determination at creation**: When a notification is created, enabled channels are determined and stored in the `channels` JSONField
66+
- **Digest processing**: Email digests are handled by a management command that queries unsent, unread notifications
67+
- **Generic relations**: Notifications can reference any Django model via ContentType/GenericForeignKey
68+
- **PostgreSQL optimization**: Uses GIN indexes for efficient JSONField queries
69+
70+
### Common Workflows
71+
72+
1. **Sending a notification**:
73+
74+
```python
75+
from generic_notifications import send_notification
76+
from myapp.notifications import CommentNotification
77+
78+
send_notification(
79+
recipient=user,
80+
notification_type=CommentNotification,
81+
actor=commenter,
82+
target=post,
83+
subject="New comment",
84+
text="Someone commented on your post"
85+
)
86+
```
87+
88+
2. **Registering a new notification type**:
89+
90+
```python
91+
from generic_notifications.types import NotificationType, register
92+
93+
@register
94+
class CommentNotification(NotificationType):
95+
key = "comment"
96+
name = "Comments"
97+
description = "When someone comments on your content"
98+
default_email_frequency = DailyFrequency
99+
```
100+
101+
3. **User preferences**: Managed through `DisabledNotificationTypeChannel` and `EmailFrequency` models

INSTALL.rst

Lines changed: 0 additions & 12 deletions
This file was deleted.

LICENSE

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
Copyright (c) 2011, Kevin Renskers and individual contributors.
2-
All rights reserved.
1+
MIT License
32

4-
Redistribution and use in source and binary forms, with or without modification,
5-
are permitted provided that the following conditions are met:
3+
Copyright (c) Loopwerk
64

7-
1. Redistributions of source code must retain the above copyright notice,
8-
this list of conditions and the following disclaimer.
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
911

10-
2. Redistributions in binary form must reproduce the above copyright
11-
notice, this list of conditions and the following disclaimer in the
12-
documentation and/or other materials provided with the distribution.
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
1314

14-
3. Neither the name of django-generic-notifications nor the names of its
15-
contributors may be used to endorse or promote products derived from
16-
this software without specific prior written permission.
17-
18-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19-
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20-
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22-
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23-
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24-
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25-
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27-
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MANIFEST.in

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)