django-mvp-community 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_mvp_community-0.1.0/.gitignore +65 -0
- django_mvp_community-0.1.0/LICENSE +21 -0
- django_mvp_community-0.1.0/PKG-INFO +242 -0
- django_mvp_community-0.1.0/README.md +199 -0
- django_mvp_community-0.1.0/mvp_community/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/apps.py +43 -0
- django_mvp_community-0.1.0/mvp_community/integrations/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/integrations/activity/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/integrations/activity/menus.py +13 -0
- django_mvp_community-0.1.0/mvp_community/integrations/activity/streams.py +24 -0
- django_mvp_community-0.1.0/mvp_community/integrations/activity/urls.py +46 -0
- django_mvp_community-0.1.0/mvp_community/integrations/activity/views.py +159 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/channels.py +112 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/checks.py +51 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/frequencies.py +14 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/menus.py +13 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/setup.py +25 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/unsubscribe.py +129 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/urls.py +28 -0
- django_mvp_community-0.1.0/mvp_community/integrations/notifications/views.py +223 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/channels.py +159 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/checks.py +44 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/setup.py +14 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/subscriptions.py +162 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/urls.py +19 -0
- django_mvp_community-0.1.0/mvp_community/integrations/push/views.py +181 -0
- django_mvp_community-0.1.0/mvp_community/locale/en/LC_MESSAGES/django.po +393 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/entry.html +17 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/feed.html +22 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/follow.html +27 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/followers.html +25 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/following_card.html +9 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/name.html +5 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/recent.html +28 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/example.html +12 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/bell.html +27 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/item.html +23 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/preferences_card.html +9 -0
- django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/push_card.html +173 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp/account/overview.html +12 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/feed.html +16 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/follow_control.html +1 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/following.html +38 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/bell_partial.html +1 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/list.html +31 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/preferences.html +79 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/unsubscribe.html +43 -0
- django_mvp_community-0.1.0/mvp_community/templates/mvp_community/push/worker.js +16 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/message.html +22 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/message.txt +9 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/subject.txt +1 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/message.html +16 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/message.txt +9 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/subject.txt +1 -0
- django_mvp_community-0.1.0/mvp_community/templates/notifications/email/unsubscribe_footer.html +5 -0
- django_mvp_community-0.1.0/mvp_community/templatetags/__init__.py +1 -0
- django_mvp_community-0.1.0/mvp_community/templatetags/mvp_community_activity.py +63 -0
- django_mvp_community-0.1.0/mvp_community/templatetags/mvp_community_notifications.py +71 -0
- django_mvp_community-0.1.0/pyproject.toml +200 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Temporary and binary files
|
|
2
|
+
*~
|
|
3
|
+
*.ruff_cache
|
|
4
|
+
*.mypy_cache
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*.so
|
|
7
|
+
*.cfg
|
|
8
|
+
!.isort.cfg
|
|
9
|
+
!setup.cfg
|
|
10
|
+
*.orig
|
|
11
|
+
*.log
|
|
12
|
+
*.pot
|
|
13
|
+
**/__pycache__
|
|
14
|
+
.cache/*
|
|
15
|
+
.*.swp
|
|
16
|
+
*/.ipynb_checkpoints/*
|
|
17
|
+
.DS_Store
|
|
18
|
+
.vscode/
|
|
19
|
+
.github/instructions/
|
|
20
|
+
docs/api/
|
|
21
|
+
|
|
22
|
+
# Unittest and coverage
|
|
23
|
+
htmlcov
|
|
24
|
+
.coverage
|
|
25
|
+
.coverage.*
|
|
26
|
+
.tox
|
|
27
|
+
junit*.xml
|
|
28
|
+
coverage.xml
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
|
|
31
|
+
# Build and docs folder/files
|
|
32
|
+
build/*
|
|
33
|
+
dist/*
|
|
34
|
+
sdist/*
|
|
35
|
+
docs/_rst/*
|
|
36
|
+
docs/_build/*
|
|
37
|
+
cover/*
|
|
38
|
+
MANIFEST
|
|
39
|
+
|
|
40
|
+
# Per-project virtualenvs
|
|
41
|
+
.venv*/
|
|
42
|
+
.conda*/
|
|
43
|
+
.python-version
|
|
44
|
+
|
|
45
|
+
# Django stuff:
|
|
46
|
+
*.log
|
|
47
|
+
local_settings.py
|
|
48
|
+
db.sqlite3
|
|
49
|
+
db.sqlite3-journal
|
|
50
|
+
media/
|
|
51
|
+
/static/
|
|
52
|
+
/*.sqlite3
|
|
53
|
+
|
|
54
|
+
# */static/vendor/*
|
|
55
|
+
|
|
56
|
+
# Environments
|
|
57
|
+
.env
|
|
58
|
+
.venv
|
|
59
|
+
env/
|
|
60
|
+
venv/
|
|
61
|
+
ENV/
|
|
62
|
+
env.bak/
|
|
63
|
+
venv.bak/
|
|
64
|
+
|
|
65
|
+
dist
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Jennings
|
|
4
|
+
|
|
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:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
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.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-mvp-community
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Notifications, mail and activity pages for django-mvp projects, built on established Django packages
|
|
5
|
+
Project-URL: Homepage, https://github.com/django-mvp/django-mvp-community
|
|
6
|
+
Project-URL: Repository, https://github.com/django-mvp/django-mvp-community
|
|
7
|
+
Project-URL: Issues, https://github.com/django-mvp/django-mvp-community/issues
|
|
8
|
+
Author-email: Samuel Jennings <samuel.scott.jennings@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cotton,django,django-mvp
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Framework :: Django :: 6.0
|
|
17
|
+
Classifier: Framework :: Django :: 6.1
|
|
18
|
+
Classifier: Intended Audience :: Developers
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Natural Language :: English
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Requires-Python: <4.0,>=3.12
|
|
25
|
+
Requires-Dist: django-mvp>=0.28.1
|
|
26
|
+
Requires-Dist: django>=5.2
|
|
27
|
+
Provides-Extra: activity
|
|
28
|
+
Requires-Dist: django-activity-stream>=2.0; extra == 'activity'
|
|
29
|
+
Provides-Extra: mail
|
|
30
|
+
Requires-Dist: django-anymail>=13; extra == 'mail'
|
|
31
|
+
Requires-Dist: django-post-office>=3.12; extra == 'mail'
|
|
32
|
+
Provides-Extra: notifications
|
|
33
|
+
Requires-Dist: django-generic-notifications>=2.4; extra == 'notifications'
|
|
34
|
+
Provides-Extra: push
|
|
35
|
+
Requires-Dist: cryptography>=42; extra == 'push'
|
|
36
|
+
Requires-Dist: django-generic-notifications>=2.4; extra == 'push'
|
|
37
|
+
Requires-Dist: django-push-notifications[wp]>=3.3; extra == 'push'
|
|
38
|
+
Requires-Dist: py-vapid>=1.9; extra == 'push'
|
|
39
|
+
Requires-Dist: requests>=2.31; extra == 'push'
|
|
40
|
+
Requires-Dist: ua-parser>=1.0; extra == 'push'
|
|
41
|
+
Provides-Extra: sharecards
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# django-mvp-community
|
|
45
|
+
|
|
46
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml)
|
|
47
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml)
|
|
48
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml)
|
|
49
|
+
[](https://codecov.io/gh/django-mvp/django-mvp-community)
|
|
50
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
51
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
52
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
53
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
54
|
+
[](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE)
|
|
55
|
+
[](https://github.com/astral-sh/ruff)
|
|
56
|
+
|
|
57
|
+
Notifications, mail and activity pages for django-mvp projects, built on established Django packages
|
|
58
|
+
|
|
59
|
+
<!--
|
|
60
|
+
The README is this package's charter, and it is read in two places: on the
|
|
61
|
+
repository page and on the package index. Write it for someone deciding
|
|
62
|
+
whether to install this, who has not read anything else here.
|
|
63
|
+
|
|
64
|
+
Every link is absolute. A relative link resolves on GitHub and 404s on PyPI,
|
|
65
|
+
which is where the person deciding is most likely to be standing.
|
|
66
|
+
|
|
67
|
+
The badge row above is the standard set for a published package: one status
|
|
68
|
+
badge per workflow that runs on a push or a pull request, then coverage, the
|
|
69
|
+
PyPI version, supported Python and Django, and the licence. A workflow added
|
|
70
|
+
later gets its badge in the same row. The PyPI badges read "not found" until
|
|
71
|
+
the first release, and the GitHub ones need the repository to be public.
|
|
72
|
+
|
|
73
|
+
Sections, in this order:
|
|
74
|
+
1. One sentence, above. What it does, for whom.
|
|
75
|
+
2. Scope & philosophy — below. What it is and is not, and the principles
|
|
76
|
+
that settle a close call.
|
|
77
|
+
3. Installation.
|
|
78
|
+
4. Quickstart — the smallest thing that works, end to end.
|
|
79
|
+
5. The public surface, in full. For a package this small, list it.
|
|
80
|
+
6. Anything genuinely surprising: what it deliberately does not do, what it
|
|
81
|
+
leaves to the host project, and the failure modes that are quiet.
|
|
82
|
+
|
|
83
|
+
Keep the CHANGELOG out of it. Link to it instead.
|
|
84
|
+
-->
|
|
85
|
+
|
|
86
|
+
## Scope & philosophy
|
|
87
|
+
|
|
88
|
+
django-mvp-community gives a django-mvp project the pages and components for
|
|
89
|
+
keeping people in touch with the site and with each other: notifications and
|
|
90
|
+
their preferences, push and reminders, email, and activity feeds. Each is built
|
|
91
|
+
on an established Django package, and this package supplies what those packages
|
|
92
|
+
leave to you: templates that match the rest of the application, menu entries,
|
|
93
|
+
Account Center pages, settings, and the few lines of wiring that connect one
|
|
94
|
+
package to another. Each feature is an optional extra, installed only when a
|
|
95
|
+
project wants it.
|
|
96
|
+
|
|
97
|
+
What it deliberately is not:
|
|
98
|
+
|
|
99
|
+
- **A notification, mail or activity engine.** It adds no logic or models of its
|
|
100
|
+
own. The packages it builds on do the work.
|
|
101
|
+
- **Real-time.** No websockets or server-sent events. Unread counts refresh by
|
|
102
|
+
polling.
|
|
103
|
+
- **Native phone push.** Web push to browsers and installed web apps only.
|
|
104
|
+
- **A mail service.** Mail goes out through whichever provider django-anymail is
|
|
105
|
+
configured for.
|
|
106
|
+
- **A forum.**
|
|
107
|
+
|
|
108
|
+
When two designs conflict, the one with less code of our own wins. A template
|
|
109
|
+
override beats a Python hook, and an existing package beats anything written
|
|
110
|
+
here.
|
|
111
|
+
|
|
112
|
+
## Installation
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install django-mvp-community
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then add it to `INSTALLED_APPS`, above `mvp`:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
INSTALLED_APPS = [
|
|
122
|
+
# ...
|
|
123
|
+
"mvp_community",
|
|
124
|
+
"mvp",
|
|
125
|
+
]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
It goes above `mvp` because some of its templates extend django-mvp's templates of
|
|
129
|
+
the same name, such as the Account Center's landing page. Django uses the first one
|
|
130
|
+
it finds, so listed below `mvp` they are never used.
|
|
131
|
+
|
|
132
|
+
This package requires [django-mvp](https://github.com/django-mvp/django-mvp).
|
|
133
|
+
It renders inside django-mvp's layout and reads its colours from the theme
|
|
134
|
+
django-mvp supplies, so it does nothing useful on its own.
|
|
135
|
+
|
|
136
|
+
## Extras
|
|
137
|
+
|
|
138
|
+
Each feature is an extra, installed by naming it:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install "django-mvp-community[notifications]"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The extras are `notifications`, `push`, `mail`, `activity` and `sharecards`. An extra
|
|
145
|
+
brings the packages its feature is built on, and the base install depends on
|
|
146
|
+
Django and django-mvp alone. Importing `mvp_community` never imports a package
|
|
147
|
+
that belongs to an extra, so a project carries nothing for a feature it does
|
|
148
|
+
not use.
|
|
149
|
+
|
|
150
|
+
Code that needs an extra's packages lives in that extra's integration module,
|
|
151
|
+
`mvp_community.integrations.<extra>`, and nowhere else in the package. A
|
|
152
|
+
project switches an extra on by installing it and importing from that module.
|
|
153
|
+
|
|
154
|
+
The `notifications` extra is built and documented in
|
|
155
|
+
[Notifications](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/notifications.md):
|
|
156
|
+
a bell in the header, a notifications page, notification preferences in
|
|
157
|
+
the Account Center, and, with the `mail` extra, notifications by email in real
|
|
158
|
+
time or as a daily or weekly digest.
|
|
159
|
+
|
|
160
|
+
The `push` extra is built and documented in
|
|
161
|
+
[Push](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/push.md):
|
|
162
|
+
a push channel for notifications, so a person who turns push on from their
|
|
163
|
+
preferences page gets each notification in their browser as it happens, even
|
|
164
|
+
when the site is closed.
|
|
165
|
+
|
|
166
|
+
The `activity` extra is built and documented in
|
|
167
|
+
[Activity](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/activity.md):
|
|
168
|
+
a feed view you mount where you like, a following page in the Account Center,
|
|
169
|
+
and components for recent activity, a follow control and a followers list.
|
|
170
|
+
|
|
171
|
+
The `mail` extra is configured and documented in
|
|
172
|
+
[Mail](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/mail.md):
|
|
173
|
+
every email your project sends is queued and logged, then delivered through
|
|
174
|
+
your provider, and stored templates extend django-mvp's email layout. It is
|
|
175
|
+
settings and documentation only, so there is no integration module to import.
|
|
176
|
+
The other extra is empty for now: installing it adds no packages, and there is
|
|
177
|
+
no integration module to import.
|
|
178
|
+
|
|
179
|
+
## Overriding a template
|
|
180
|
+
|
|
181
|
+
Any template this package ships can be replaced on its own, without forking
|
|
182
|
+
the package. A template's name is its path below `mvp_community/templates/`,
|
|
183
|
+
and Django uses the first template it finds with that name. Put a file with the
|
|
184
|
+
same name in the project's own template directory, the one listed in
|
|
185
|
+
`TEMPLATES["DIRS"]`, and Django uses it instead of this package's. A template
|
|
186
|
+
in an app listed above `mvp_community` in `INSTALLED_APPS` overrides it in the
|
|
187
|
+
same way.
|
|
188
|
+
|
|
189
|
+
Components follow the same rule. A component's tag is its path below
|
|
190
|
+
`templates/cotton/`, so this file in the project replaces the component
|
|
191
|
+
`<c-mvp_community.name>` everywhere it is used:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
templates/cotton/mvp_community/name.html
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Template names are part of the public interface. Renaming one is a breaking
|
|
198
|
+
change, made only after a release that warns about it, with the CHANGELOG
|
|
199
|
+
naming what replaces it.
|
|
200
|
+
|
|
201
|
+
## Quickstart
|
|
202
|
+
|
|
203
|
+
<!--
|
|
204
|
+
The smallest complete example: what goes in the view, what goes in the
|
|
205
|
+
template, and what appears on the page. Real code that runs, not a sketch.
|
|
206
|
+
If the example needs three files, show three files.
|
|
207
|
+
-->
|
|
208
|
+
|
|
209
|
+
## Public surface
|
|
210
|
+
|
|
211
|
+
<!--
|
|
212
|
+
Everything a host project can touch: components and their attributes,
|
|
213
|
+
settings, template tags, models, views. Being able to list it exhaustively is
|
|
214
|
+
a feature of a package this size, and the list is what makes an addition to
|
|
215
|
+
it a deliberate decision rather than a side effect.
|
|
216
|
+
-->
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
Standards for this repository live in
|
|
221
|
+
[CONSTITUTION.md](https://github.com/django-mvp/django-mvp-community/blob/main/CONSTITUTION.md),
|
|
222
|
+
and the vocabulary to use in issues and commits lives in
|
|
223
|
+
[CONTEXT.md](https://github.com/django-mvp/django-mvp-community/blob/main/CONTEXT.md).
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
uv sync
|
|
227
|
+
uv run pytest
|
|
228
|
+
uv run pre-commit install
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`demo/` is a Django project on django-mvp's application shell, for looking at
|
|
232
|
+
this package in a browser while working on it:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
uv run python manage.py migrate
|
|
236
|
+
uv run python manage.py seed_demo
|
|
237
|
+
uv run python manage.py runserver
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT. See [LICENSE](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE).
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# django-mvp-community
|
|
2
|
+
|
|
3
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml)
|
|
4
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml)
|
|
5
|
+
[](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml)
|
|
6
|
+
[](https://codecov.io/gh/django-mvp/django-mvp-community)
|
|
7
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
8
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
9
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
10
|
+
[](https://pypi.org/project/django-mvp-community/)
|
|
11
|
+
[](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE)
|
|
12
|
+
[](https://github.com/astral-sh/ruff)
|
|
13
|
+
|
|
14
|
+
Notifications, mail and activity pages for django-mvp projects, built on established Django packages
|
|
15
|
+
|
|
16
|
+
<!--
|
|
17
|
+
The README is this package's charter, and it is read in two places: on the
|
|
18
|
+
repository page and on the package index. Write it for someone deciding
|
|
19
|
+
whether to install this, who has not read anything else here.
|
|
20
|
+
|
|
21
|
+
Every link is absolute. A relative link resolves on GitHub and 404s on PyPI,
|
|
22
|
+
which is where the person deciding is most likely to be standing.
|
|
23
|
+
|
|
24
|
+
The badge row above is the standard set for a published package: one status
|
|
25
|
+
badge per workflow that runs on a push or a pull request, then coverage, the
|
|
26
|
+
PyPI version, supported Python and Django, and the licence. A workflow added
|
|
27
|
+
later gets its badge in the same row. The PyPI badges read "not found" until
|
|
28
|
+
the first release, and the GitHub ones need the repository to be public.
|
|
29
|
+
|
|
30
|
+
Sections, in this order:
|
|
31
|
+
1. One sentence, above. What it does, for whom.
|
|
32
|
+
2. Scope & philosophy — below. What it is and is not, and the principles
|
|
33
|
+
that settle a close call.
|
|
34
|
+
3. Installation.
|
|
35
|
+
4. Quickstart — the smallest thing that works, end to end.
|
|
36
|
+
5. The public surface, in full. For a package this small, list it.
|
|
37
|
+
6. Anything genuinely surprising: what it deliberately does not do, what it
|
|
38
|
+
leaves to the host project, and the failure modes that are quiet.
|
|
39
|
+
|
|
40
|
+
Keep the CHANGELOG out of it. Link to it instead.
|
|
41
|
+
-->
|
|
42
|
+
|
|
43
|
+
## Scope & philosophy
|
|
44
|
+
|
|
45
|
+
django-mvp-community gives a django-mvp project the pages and components for
|
|
46
|
+
keeping people in touch with the site and with each other: notifications and
|
|
47
|
+
their preferences, push and reminders, email, and activity feeds. Each is built
|
|
48
|
+
on an established Django package, and this package supplies what those packages
|
|
49
|
+
leave to you: templates that match the rest of the application, menu entries,
|
|
50
|
+
Account Center pages, settings, and the few lines of wiring that connect one
|
|
51
|
+
package to another. Each feature is an optional extra, installed only when a
|
|
52
|
+
project wants it.
|
|
53
|
+
|
|
54
|
+
What it deliberately is not:
|
|
55
|
+
|
|
56
|
+
- **A notification, mail or activity engine.** It adds no logic or models of its
|
|
57
|
+
own. The packages it builds on do the work.
|
|
58
|
+
- **Real-time.** No websockets or server-sent events. Unread counts refresh by
|
|
59
|
+
polling.
|
|
60
|
+
- **Native phone push.** Web push to browsers and installed web apps only.
|
|
61
|
+
- **A mail service.** Mail goes out through whichever provider django-anymail is
|
|
62
|
+
configured for.
|
|
63
|
+
- **A forum.**
|
|
64
|
+
|
|
65
|
+
When two designs conflict, the one with less code of our own wins. A template
|
|
66
|
+
override beats a Python hook, and an existing package beats anything written
|
|
67
|
+
here.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install django-mvp-community
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then add it to `INSTALLED_APPS`, above `mvp`:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
INSTALLED_APPS = [
|
|
79
|
+
# ...
|
|
80
|
+
"mvp_community",
|
|
81
|
+
"mvp",
|
|
82
|
+
]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It goes above `mvp` because some of its templates extend django-mvp's templates of
|
|
86
|
+
the same name, such as the Account Center's landing page. Django uses the first one
|
|
87
|
+
it finds, so listed below `mvp` they are never used.
|
|
88
|
+
|
|
89
|
+
This package requires [django-mvp](https://github.com/django-mvp/django-mvp).
|
|
90
|
+
It renders inside django-mvp's layout and reads its colours from the theme
|
|
91
|
+
django-mvp supplies, so it does nothing useful on its own.
|
|
92
|
+
|
|
93
|
+
## Extras
|
|
94
|
+
|
|
95
|
+
Each feature is an extra, installed by naming it:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install "django-mvp-community[notifications]"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The extras are `notifications`, `push`, `mail`, `activity` and `sharecards`. An extra
|
|
102
|
+
brings the packages its feature is built on, and the base install depends on
|
|
103
|
+
Django and django-mvp alone. Importing `mvp_community` never imports a package
|
|
104
|
+
that belongs to an extra, so a project carries nothing for a feature it does
|
|
105
|
+
not use.
|
|
106
|
+
|
|
107
|
+
Code that needs an extra's packages lives in that extra's integration module,
|
|
108
|
+
`mvp_community.integrations.<extra>`, and nowhere else in the package. A
|
|
109
|
+
project switches an extra on by installing it and importing from that module.
|
|
110
|
+
|
|
111
|
+
The `notifications` extra is built and documented in
|
|
112
|
+
[Notifications](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/notifications.md):
|
|
113
|
+
a bell in the header, a notifications page, notification preferences in
|
|
114
|
+
the Account Center, and, with the `mail` extra, notifications by email in real
|
|
115
|
+
time or as a daily or weekly digest.
|
|
116
|
+
|
|
117
|
+
The `push` extra is built and documented in
|
|
118
|
+
[Push](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/push.md):
|
|
119
|
+
a push channel for notifications, so a person who turns push on from their
|
|
120
|
+
preferences page gets each notification in their browser as it happens, even
|
|
121
|
+
when the site is closed.
|
|
122
|
+
|
|
123
|
+
The `activity` extra is built and documented in
|
|
124
|
+
[Activity](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/activity.md):
|
|
125
|
+
a feed view you mount where you like, a following page in the Account Center,
|
|
126
|
+
and components for recent activity, a follow control and a followers list.
|
|
127
|
+
|
|
128
|
+
The `mail` extra is configured and documented in
|
|
129
|
+
[Mail](https://github.com/django-mvp/django-mvp-community/blob/main/docs/developer/mail.md):
|
|
130
|
+
every email your project sends is queued and logged, then delivered through
|
|
131
|
+
your provider, and stored templates extend django-mvp's email layout. It is
|
|
132
|
+
settings and documentation only, so there is no integration module to import.
|
|
133
|
+
The other extra is empty for now: installing it adds no packages, and there is
|
|
134
|
+
no integration module to import.
|
|
135
|
+
|
|
136
|
+
## Overriding a template
|
|
137
|
+
|
|
138
|
+
Any template this package ships can be replaced on its own, without forking
|
|
139
|
+
the package. A template's name is its path below `mvp_community/templates/`,
|
|
140
|
+
and Django uses the first template it finds with that name. Put a file with the
|
|
141
|
+
same name in the project's own template directory, the one listed in
|
|
142
|
+
`TEMPLATES["DIRS"]`, and Django uses it instead of this package's. A template
|
|
143
|
+
in an app listed above `mvp_community` in `INSTALLED_APPS` overrides it in the
|
|
144
|
+
same way.
|
|
145
|
+
|
|
146
|
+
Components follow the same rule. A component's tag is its path below
|
|
147
|
+
`templates/cotton/`, so this file in the project replaces the component
|
|
148
|
+
`<c-mvp_community.name>` everywhere it is used:
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
templates/cotton/mvp_community/name.html
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Template names are part of the public interface. Renaming one is a breaking
|
|
155
|
+
change, made only after a release that warns about it, with the CHANGELOG
|
|
156
|
+
naming what replaces it.
|
|
157
|
+
|
|
158
|
+
## Quickstart
|
|
159
|
+
|
|
160
|
+
<!--
|
|
161
|
+
The smallest complete example: what goes in the view, what goes in the
|
|
162
|
+
template, and what appears on the page. Real code that runs, not a sketch.
|
|
163
|
+
If the example needs three files, show three files.
|
|
164
|
+
-->
|
|
165
|
+
|
|
166
|
+
## Public surface
|
|
167
|
+
|
|
168
|
+
<!--
|
|
169
|
+
Everything a host project can touch: components and their attributes,
|
|
170
|
+
settings, template tags, models, views. Being able to list it exhaustively is
|
|
171
|
+
a feature of a package this size, and the list is what makes an addition to
|
|
172
|
+
it a deliberate decision rather than a side effect.
|
|
173
|
+
-->
|
|
174
|
+
|
|
175
|
+
## Contributing
|
|
176
|
+
|
|
177
|
+
Standards for this repository live in
|
|
178
|
+
[CONSTITUTION.md](https://github.com/django-mvp/django-mvp-community/blob/main/CONSTITUTION.md),
|
|
179
|
+
and the vocabulary to use in issues and commits lives in
|
|
180
|
+
[CONTEXT.md](https://github.com/django-mvp/django-mvp-community/blob/main/CONTEXT.md).
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
uv sync
|
|
184
|
+
uv run pytest
|
|
185
|
+
uv run pre-commit install
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`demo/` is a Django project on django-mvp's application shell, for looking at
|
|
189
|
+
this package in a browser while working on it:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
uv run python manage.py migrate
|
|
193
|
+
uv run python manage.py seed_demo
|
|
194
|
+
uv run python manage.py runserver
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT. See [LICENSE](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The Community app for django-mvp."""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""App configuration for django-mvp-community."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig, apps
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MvpCommunityConfig(AppConfig):
|
|
8
|
+
"""What a project gets when it adds this package to INSTALLED_APPS.
|
|
9
|
+
|
|
10
|
+
The label is set explicitly rather than left to Django's default, which is
|
|
11
|
+
the last segment of the module path. Two installed apps whose paths end in
|
|
12
|
+
the same word collide on that default, and the error names neither of them
|
|
13
|
+
clearly.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
name = "mvp_community"
|
|
17
|
+
label = "mvp_community"
|
|
18
|
+
verbose_name = _("Community")
|
|
19
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
20
|
+
|
|
21
|
+
def ready(self):
|
|
22
|
+
"""Add each installed extra's Account Center entry."""
|
|
23
|
+
if apps.is_installed("generic_notifications"):
|
|
24
|
+
from mvp_community.integrations.notifications import (
|
|
25
|
+
checks, # noqa: F401
|
|
26
|
+
menus, # noqa: F401
|
|
27
|
+
setup,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
setup.configure(apps.is_installed("post_office"))
|
|
31
|
+
if apps.is_installed("generic_notifications") and apps.is_installed(
|
|
32
|
+
"push_notifications"
|
|
33
|
+
):
|
|
34
|
+
from mvp_community.integrations.push import (
|
|
35
|
+
checks as push_checks, # noqa: F401
|
|
36
|
+
)
|
|
37
|
+
from mvp_community.integrations.push import setup as push_setup
|
|
38
|
+
|
|
39
|
+
push_setup.configure()
|
|
40
|
+
if apps.is_installed("actstream"):
|
|
41
|
+
from mvp_community.integrations.activity import (
|
|
42
|
+
menus as activity, # noqa: F401
|
|
43
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Modules that import an extra's packages. A project imports one only when it uses that extra."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The activity extra, built on django-activity-stream."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""The Account Center entry for the page listing what a person follows."""
|
|
2
|
+
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
from flex_menu import MenuItem
|
|
5
|
+
from mvp.menus import AccountCenterMenu
|
|
6
|
+
|
|
7
|
+
AccountCenterMenu.append(
|
|
8
|
+
MenuItem(
|
|
9
|
+
name="following",
|
|
10
|
+
view_name="mvp_community_activity:following",
|
|
11
|
+
extra_context={"label": _("Following"), "icon": "activity.following"},
|
|
12
|
+
)
|
|
13
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""The one stream this package shapes beyond django-activity-stream's own."""
|
|
2
|
+
|
|
3
|
+
from actstream.models import Action
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def followed_activity(user, **filters):
|
|
8
|
+
"""Return the public activity of what ``user`` follows, without their own actions.
|
|
9
|
+
|
|
10
|
+
django-activity-stream's ``user`` stream also holds the person's own actions
|
|
11
|
+
where they act on something they follow, and the "started following" each
|
|
12
|
+
follow records. The feed of what a person follows leaves those out.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
user: The signed-in person.
|
|
16
|
+
**filters: Passed to the stream, such as ``verb``.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The stream, newest first.
|
|
20
|
+
"""
|
|
21
|
+
return Action.objects.user(user, **filters).exclude(
|
|
22
|
+
actor_content_type=ContentType.objects.get_for_model(user),
|
|
23
|
+
actor_object_id=str(user.pk),
|
|
24
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""The activity extra's routes.
|
|
2
|
+
|
|
3
|
+
Include this at the root, ``path("", include(...))``, as django-mvp's own URLs
|
|
4
|
+
are: each route chooses its own address.
|
|
5
|
+
|
|
6
|
+
Only django-activity-stream's four follow routes are taken from its URLconf.
|
|
7
|
+
The rest of it also shows actions that are not public and lists anyone's
|
|
8
|
+
follows to anyone. The follow routes keep their own names, with no namespace,
|
|
9
|
+
because django-activity-stream's template tags reverse them by those names.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from actstream import urls as actstream_urls
|
|
13
|
+
from django.urls import include, path
|
|
14
|
+
|
|
15
|
+
from . import views
|
|
16
|
+
|
|
17
|
+
FOLLOW_ROUTES = {
|
|
18
|
+
"actstream_follow",
|
|
19
|
+
"actstream_follow_all",
|
|
20
|
+
"actstream_unfollow",
|
|
21
|
+
"actstream_unfollow_all",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pages = [
|
|
25
|
+
path(
|
|
26
|
+
"account-center/following/",
|
|
27
|
+
views.FollowingView.as_view(),
|
|
28
|
+
name="following",
|
|
29
|
+
),
|
|
30
|
+
path(
|
|
31
|
+
"activity/control/<int:content_type_id>/<str:object_id>/",
|
|
32
|
+
views.FollowControlView.as_view(),
|
|
33
|
+
name="follow_control",
|
|
34
|
+
),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
follow_routes = [
|
|
38
|
+
route
|
|
39
|
+
for route in actstream_urls.urlpatterns
|
|
40
|
+
if getattr(route, "name", None) in FOLLOW_ROUTES
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
urlpatterns = [
|
|
44
|
+
path("", include((pages, "mvp_community_activity"))),
|
|
45
|
+
path("activity/", include(follow_routes)),
|
|
46
|
+
]
|