Skip to content

Commit 85bac14

Browse files
committed
Cleanup, fix tests, add celery schedule
1 parent f973d33 commit 85bac14

File tree

17 files changed

+76
-57
lines changed

17 files changed

+76
-57
lines changed

adhocracy-plus/config/settings/production.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from celery.schedules import crontab
2+
13
from .base import *
24

35
DEBUG = False
@@ -31,3 +33,18 @@
3133
CKEDITOR_CONFIGS["video-editor"]["embed_provider"] = CKEDITOR_URL
3234
except NameError:
3335
pass
36+
37+
CELERY_BEAT_SCHEDULE = {
38+
"send-recently-started-project-notifications": {
39+
"task": "send_recently_started_project_notifications",
40+
"schedule": crontab.from_string("0 0 */3 * *"),
41+
},
42+
"send-recently-completed-project-notifications": {
43+
"task": "send_recently_completed_project_notifications",
44+
"schedule": crontab.from_string("0 0 */3 * *"),
45+
},
46+
"send_upcoming-event-notifications": {
47+
"task": "send_upcoming_event_notifications",
48+
"schedule": crontab.from_string("0 0 */3 * *"),
49+
},
50+
}

adhocracy-plus/templates/includes/header_notification_button.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
aria-label="Notifications"
77
hx-get="{% url 'notification-count-partial' %}"
88
hx-trigger="updateNotificationCount from:body, every 15s"
9-
hx-target=".notification-badge-container"
10-
hx-swap="innerHTML">
9+
hx-target=".notification-badge-container">
1110

1211
<i class="fas fa-bell"></i>
1312
<span class="notification-badge-container">

apps/ideas/views.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
from apps.contrib.widgets import FreeTextFilterWidget
1919
from apps.moderatorfeedback.forms import ModeratorFeedbackForm
2020
from apps.moderatorfeedback.models import ModeratorFeedback
21-
22-
# from apps.notifications.emails import NotifyCreatorOnModeratorFeedback
2321
from apps.organisations.mixins import UserFormViewMixin
2422

2523
from . import forms
@@ -197,7 +195,6 @@ def forms_save(self, forms, commit=True):
197195
feedback_text.save()
198196
moderateable.moderator_feedback_text = feedback_text
199197
moderateable.save()
200-
# NotifyCreatorOnModeratorFeedback.send(self.object)
201198
return objects
202199

203200
def get_instance(self, name):

apps/notifications/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ class Config(AppConfig):
66
label = "a4_candy_notifications"
77

88
def ready(self):
9-
from . import signals
9+
from . import signals # noqa:F401

apps/notifications/migrations/0001_initial.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 5.1.11 on 2025-10-22 07:52
1+
# Generated by Django 5.1.11 on 2025-11-23 15:59
22

33
import apps.notifications.models
44
import django.db.models.deletion
@@ -36,7 +36,6 @@ def remove_notification_settings(apps, schema_editor):
3636
class Migration(migrations.Migration):
3737

3838
dependencies = [
39-
("a4actions", "0010_alter_action_verb"),
4039
("a4phases", "0007_order_phases_also_by_id"),
4140
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
4241
]
@@ -203,16 +202,6 @@ class Migration(migrations.Migration):
203202
"target_url",
204203
models.URLField(blank=True, null=True, verbose_name="Target URL"),
205204
),
206-
(
207-
"action",
208-
models.ForeignKey(
209-
blank=True,
210-
null=True,
211-
on_delete=django.db.models.deletion.CASCADE,
212-
related_name="+",
213-
to="a4actions.action",
214-
),
215-
),
216205
(
217206
"recipient",
218207
models.ForeignKey(

apps/notifications/models.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,6 @@ class Notification(models.Model):
276276
User, on_delete=models.CASCADE, related_name="notifications"
277277
)
278278

279-
# TODO: Remove when email notifications updated
280-
action = models.ForeignKey(
281-
"a4actions.Action",
282-
on_delete=models.CASCADE,
283-
related_name="+",
284-
null=True,
285-
blank=True,
286-
)
287279
notification_type = models.CharField(
288280
max_length=30,
289281
choices=NotificationType.choices,

apps/notifications/signals.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from django.db.models.signals import m2m_changed
12
from django.db.models.signals import post_delete
23
from django.db.models.signals import post_save
34
from django.db.models.signals import pre_save
45
from django.dispatch import receiver
56

67
from adhocracy4.comments.models import Comment
8+
from adhocracy4.follows.models import Follow
79
from apps.budgeting.models import Proposal
810
from apps.ideas.models import Idea
911
from apps.mapideas.models import MapIdea
@@ -30,6 +32,34 @@
3032
from .strategies import ProposalFeedback
3133
from .strategies import UserContentCreated
3234

35+
36+
def autofollow_project(instance, pk_set, reverse):
37+
if not reverse:
38+
project = instance
39+
users_pks = pk_set
40+
41+
for user_pk in users_pks:
42+
Follow.objects.update_or_create(
43+
project=project, creator_id=user_pk, defaults={"enabled": True}
44+
)
45+
else:
46+
user = instance
47+
project_pks = pk_set
48+
for project_pk in project_pks:
49+
Follow.objects.update_or_create(
50+
project_id=project_pk, creator=user, defaults={"enabled": True}
51+
)
52+
53+
54+
# Autofollow signals
55+
56+
57+
@receiver(m2m_changed, sender=Project.moderators.through)
58+
def autofollow_project_moderators(instance, action, pk_set, reverse, **kwargs):
59+
if action == "post_add":
60+
autofollow_project(instance, pk_set, reverse)
61+
62+
3363
# Comment Signals
3464

3565

apps/notifications/strategies/moderation_strategies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ def get_recipients(self, comment) -> List[User]:
178178

179179
def create_notification_data(self, comment) -> dict:
180180
project = comment.project
181-
182181
email_context = {
183182
"subject": _("Your comment was blocked"),
184183
"headline": _("Your comment was blocked"),
@@ -192,7 +191,8 @@ def create_notification_data(self, comment) -> dict:
192191
# Template variables
193192
"project_name": project.name,
194193
"comment_text": comment.comment,
195-
"netiquette_url": "/netiquette", # You may want to make this configurable
194+
# TODO: Check netiquette_url logic
195+
"netiquette_url": project.organisation.slug,
196196
"organisation": project.organisation.name,
197197
}
198198

apps/notifications/strategies/project_strategies.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,12 @@ def create_notification_data(self, invitation) -> dict:
143143

144144
email_context = {
145145
"subject": _(
146-
'Invitation to the {project_type} project: "{project_name}"'
146+
"Invitation to the {project_type} project: {project_name}"
147147
).format(project_type=project_type, project_name=project.name),
148148
"headline": _(
149149
'Invitation to the {project_type} project: "{project_name}"'
150150
).format(project_type=project_type, project_name=project.name),
151-
"cta_url": f"https://{invitation.site}{invitation.get_absolute_url()}",
151+
"cta_url": f"{invitation.get_absolute_url()}",
152152
"cta_label": _("Accept invitation"),
153153
"reason": _("This email was sent to {receiver_email}."),
154154
"content_template": "a4_candy_notifications/emails/content/project_invitation.en.email",
@@ -298,17 +298,23 @@ def get_recipients(self, obj) -> List[User]:
298298
return self._get_project_moderators(obj.project)
299299

300300
def create_notification_data(self, obj) -> dict:
301-
# Auto-detect content type from object class if not provided
302301
content_type = self.content_type or obj.__class__.__name__
303-
content_type_display = content_type # Could add display mapping if needed
304-
302+
content_type_display = content_type
303+
content_type_article = "A"
304+
if content_type_display[0].lower() in ["a", "e", "i", "o", "u"]:
305+
content_type_article = "An"
305306
email_context = {
306-
"subject": _("A {content_type} was added to the project {project}").format(
307-
content_type=content_type_display, project=obj.project.name
307+
"subject": _(
308+
"{article} {content_type} was added to the project {project}"
309+
).format(
310+
article=content_type_article,
311+
content_type=content_type_display,
312+
project=obj.project.name,
308313
),
309314
"headline": _(
310-
"{creator_name} created a {content_type} on the project {project}"
315+
"{creator_name} created {article} {content_type} on the project {project}"
311316
).format(
317+
article=content_type_article.lower(),
312318
creator_name=obj.creator.username,
313319
content_type=content_type_display,
314320
project=obj.project.name,

apps/notifications/tasks.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
from .strategies import ProjectStarted
1414

1515

16-
# TODO: Run daily (?) via celery
17-
@shared_task
16+
@shared_task(name="send_recently_started_project_notifications")
1817
def send_recently_started_project_notifications():
1918
"""
2019
Send notifications to project followers for project completed
@@ -45,7 +44,7 @@ def is_last_phase_in_project(phase):
4544
return is_last_phase
4645

4746

48-
@shared_task
47+
@shared_task(name="send_recently_completed_project_notifications")
4948
def send_recently_completed_project_notifications():
5049
"""
5150
Send notifications to project followers for project completed
@@ -67,8 +66,7 @@ def send_recently_completed_project_notifications():
6766
return
6867

6968

70-
# TODO: Run daily (?) via celery
71-
@shared_task
69+
@shared_task(name="send_upcoming_event_notifications")
7270
def send_upcoming_event_notifications():
7371
"""
7472
Send notifications to project followers for events starting within 24 hours

0 commit comments

Comments
 (0)