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.
Files changed (61) hide show
  1. django_mvp_community-0.1.0/.gitignore +65 -0
  2. django_mvp_community-0.1.0/LICENSE +21 -0
  3. django_mvp_community-0.1.0/PKG-INFO +242 -0
  4. django_mvp_community-0.1.0/README.md +199 -0
  5. django_mvp_community-0.1.0/mvp_community/__init__.py +1 -0
  6. django_mvp_community-0.1.0/mvp_community/apps.py +43 -0
  7. django_mvp_community-0.1.0/mvp_community/integrations/__init__.py +1 -0
  8. django_mvp_community-0.1.0/mvp_community/integrations/activity/__init__.py +1 -0
  9. django_mvp_community-0.1.0/mvp_community/integrations/activity/menus.py +13 -0
  10. django_mvp_community-0.1.0/mvp_community/integrations/activity/streams.py +24 -0
  11. django_mvp_community-0.1.0/mvp_community/integrations/activity/urls.py +46 -0
  12. django_mvp_community-0.1.0/mvp_community/integrations/activity/views.py +159 -0
  13. django_mvp_community-0.1.0/mvp_community/integrations/notifications/__init__.py +1 -0
  14. django_mvp_community-0.1.0/mvp_community/integrations/notifications/channels.py +112 -0
  15. django_mvp_community-0.1.0/mvp_community/integrations/notifications/checks.py +51 -0
  16. django_mvp_community-0.1.0/mvp_community/integrations/notifications/frequencies.py +14 -0
  17. django_mvp_community-0.1.0/mvp_community/integrations/notifications/menus.py +13 -0
  18. django_mvp_community-0.1.0/mvp_community/integrations/notifications/setup.py +25 -0
  19. django_mvp_community-0.1.0/mvp_community/integrations/notifications/unsubscribe.py +129 -0
  20. django_mvp_community-0.1.0/mvp_community/integrations/notifications/urls.py +28 -0
  21. django_mvp_community-0.1.0/mvp_community/integrations/notifications/views.py +223 -0
  22. django_mvp_community-0.1.0/mvp_community/integrations/push/__init__.py +1 -0
  23. django_mvp_community-0.1.0/mvp_community/integrations/push/channels.py +159 -0
  24. django_mvp_community-0.1.0/mvp_community/integrations/push/checks.py +44 -0
  25. django_mvp_community-0.1.0/mvp_community/integrations/push/setup.py +14 -0
  26. django_mvp_community-0.1.0/mvp_community/integrations/push/subscriptions.py +162 -0
  27. django_mvp_community-0.1.0/mvp_community/integrations/push/urls.py +19 -0
  28. django_mvp_community-0.1.0/mvp_community/integrations/push/views.py +181 -0
  29. django_mvp_community-0.1.0/mvp_community/locale/en/LC_MESSAGES/django.po +393 -0
  30. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/entry.html +17 -0
  31. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/feed.html +22 -0
  32. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/follow.html +27 -0
  33. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/followers.html +25 -0
  34. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/following_card.html +9 -0
  35. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/name.html +5 -0
  36. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/activity/recent.html +28 -0
  37. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/example.html +12 -0
  38. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/bell.html +27 -0
  39. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/item.html +23 -0
  40. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/preferences_card.html +9 -0
  41. django_mvp_community-0.1.0/mvp_community/templates/cotton/mvp_community/notifications/push_card.html +173 -0
  42. django_mvp_community-0.1.0/mvp_community/templates/mvp/account/overview.html +12 -0
  43. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/feed.html +16 -0
  44. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/follow_control.html +1 -0
  45. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/activity/following.html +38 -0
  46. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/bell_partial.html +1 -0
  47. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/list.html +31 -0
  48. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/preferences.html +79 -0
  49. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/notifications/unsubscribe.html +43 -0
  50. django_mvp_community-0.1.0/mvp_community/templates/mvp_community/push/worker.js +16 -0
  51. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/message.html +22 -0
  52. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/message.txt +9 -0
  53. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/digest/subject.txt +1 -0
  54. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/message.html +16 -0
  55. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/message.txt +9 -0
  56. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/realtime/subject.txt +1 -0
  57. django_mvp_community-0.1.0/mvp_community/templates/notifications/email/unsubscribe_footer.html +5 -0
  58. django_mvp_community-0.1.0/mvp_community/templatetags/__init__.py +1 -0
  59. django_mvp_community-0.1.0/mvp_community/templatetags/mvp_community_activity.py +63 -0
  60. django_mvp_community-0.1.0/mvp_community/templatetags/mvp_community_notifications.py +71 -0
  61. 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
+ [![Tests](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml)
47
+ [![Build](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml)
48
+ [![Publish](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml)
49
+ [![codecov](https://codecov.io/gh/django-mvp/django-mvp-community/branch/main/graph/badge.svg)](https://codecov.io/gh/django-mvp/django-mvp-community)
50
+ [![PyPI](https://img.shields.io/pypi/v/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
51
+ [![Development status](https://img.shields.io/pypi/status/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
52
+ [![Python](https://img.shields.io/pypi/pyversions/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
53
+ [![Django](https://img.shields.io/pypi/djversions/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
54
+ [![License](https://img.shields.io/github/license/django-mvp/django-mvp-community)](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE)
55
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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
+ [![Tests](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/tests.yml)
4
+ [![Build](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/build.yml)
5
+ [![Publish](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml/badge.svg)](https://github.com/django-mvp/django-mvp-community/actions/workflows/publish.yml)
6
+ [![codecov](https://codecov.io/gh/django-mvp/django-mvp-community/branch/main/graph/badge.svg)](https://codecov.io/gh/django-mvp/django-mvp-community)
7
+ [![PyPI](https://img.shields.io/pypi/v/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
8
+ [![Development status](https://img.shields.io/pypi/status/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
9
+ [![Python](https://img.shields.io/pypi/pyversions/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
10
+ [![Django](https://img.shields.io/pypi/djversions/django-mvp-community)](https://pypi.org/project/django-mvp-community/)
11
+ [![License](https://img.shields.io/github/license/django-mvp/django-mvp-community)](https://github.com/django-mvp/django-mvp-community/blob/main/LICENSE)
12
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](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
+ ]