Skip to content

Commit f9eedf3

Browse files
committed
feat(announcements): implement club announcements
1 parent d348d9c commit f9eedf3

20 files changed

+546
-35
lines changed

intranet/apps/announcements/forms.py

+23-15
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,38 @@
88
class AnnouncementForm(forms.ModelForm):
99
"""A form for generating an announcement."""
1010

11-
def __init__(self, *args, **kwargs):
12-
super().__init__(*args, **kwargs)
13-
self.fields["expiration_date"].help_text = "By default, announcements expire after two weeks. To change this, click in the box above."
11+
expiration_date = forms.DateTimeInput()
12+
notify_email_all = forms.BooleanField(required=False, label="Send Email to All")
13+
update_added_date = forms.BooleanField(required=False, label="Update Added Date")
1414

15-
self.fields["notify_post"].help_text = "If this box is checked, students who have signed up for notifications will receive an email."
15+
class Meta:
16+
model = Announcement
17+
fields = ["title", "author", "content", "groups", "expiration_date", "notify_post", "notify_email_all", "update_added_date", "pinned"]
18+
help_texts = {
19+
"expiration_date": "By default, announcements expire after two weeks. To change this, click in the box above.",
20+
"notify_post": "If this box is checked, students who have signed up for notifications will receive an email.",
21+
"notify_email_all": "This will send an email notification to all of the users who can see this post. This option does NOT take users' email notification preferences into account, so please use with care.",
22+
"update_added_date": "If this announcement has already been added, update the added date to now so that the announcement is pushed to the top. If this option is not selected, the announcement will stay in its current position.",
23+
}
1624

17-
self.fields["notify_email_all"].help_text = (
18-
"This will send an email notification to all of the users who can see this post. This option "
19-
"does NOT take users' email notification preferences into account, so please use with care."
20-
)
2125

22-
self.fields["update_added_date"].help_text = (
23-
"If this announcement has already been added, update the added date to now so that the "
24-
"announcement is pushed to the top. If this option is not selected, the announcement will stay in "
25-
"its current position."
26-
)
26+
class ClubAnnouncementForm(forms.ModelForm):
27+
"""A form for posting a club announcement."""
28+
29+
def __init__(self, user, *args, **kwargs):
30+
super().__init__(*args, **kwargs)
31+
self.fields["activity"].queryset = user.officer_for_set
2732

2833
expiration_date = forms.DateTimeInput()
29-
notify_email_all = forms.BooleanField(required=False, label="Send Email to All")
3034
update_added_date = forms.BooleanField(required=False, label="Update Added Date")
3135

3236
class Meta:
3337
model = Announcement
34-
fields = ["title", "author", "content", "groups", "expiration_date", "notify_post", "notify_email_all", "update_added_date", "pinned"]
38+
fields = ["title", "author", "content", "activity", "expiration_date", "update_added_date"]
39+
help_texts = {
40+
"expiration_date": "By default, announcements expire after two weeks. To change this, click in the box above.",
41+
"update_added_date": "If this announcement has already been added, update the added date to now so that the announcement is pushed to the top. If this option is not selected, the announcement will stay in its current position.",
42+
}
3543

3644

3745
class AnnouncementEditForm(forms.ModelForm):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 3.2.20 on 2024-02-14 00:06
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('eighth', '0067_eighthactivity_subscribers'),
11+
('announcements', '0032_alter_warningannouncement_type'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='announcement',
17+
name='activity',
18+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='eighth.eighthactivity'),
19+
),
20+
]

intranet/apps/announcements/models.py

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from ...utils.deletion import set_historical_user
1212
from ...utils.html import nullify_links
1313

14+
from ..eighth.models import EighthActivity
15+
1416

1517
class AnnouncementManager(Manager):
1618
def visible_to_user(self, user):
@@ -110,6 +112,8 @@ class Announcement(models.Model):
110112
updated = models.DateTimeField(auto_now=True)
111113
groups = models.ManyToManyField(DjangoGroup, blank=True)
112114

115+
activity = models.ForeignKey(EighthActivity, null=True, blank=True, on_delete=models.CASCADE)
116+
113117
expiration_date = models.DateTimeField(auto_now=False, default=timezone.make_aware(datetime(3000, 1, 1)))
114118

115119
notify_post = models.BooleanField(default=True)
@@ -141,6 +145,10 @@ def is_this_year(self):
141145
"""Return whether the announcement was created after July 1st of this school year."""
142146
return is_current_year(self.added)
143147

148+
@property
149+
def is_club_announcement(self):
150+
return self.activity is not None
151+
144152
def is_visible(self, user):
145153
return self in Announcement.objects.visible_to_user(user)
146154

intranet/apps/announcements/notifications.py

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.contrib import messages
1111
from django.contrib.auth import get_user_model
1212
from django.core import exceptions
13+
from django.db.models import Q
1314
from django.urls import reverse
1415

1516
from ...utils.date import get_senior_graduation_year
@@ -119,6 +120,16 @@ def announcement_posted_email(request, obj, send_all=False):
119120
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year())
120121
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"]))
121122
)
123+
elif obj.club:
124+
filter = Q(subscribed_to_set__contains=obj.club) & (
125+
Q(user_type="student") & Q(graduation_year__gte=get_senior_graduation_year()) | Q(user_type__in=["teacher", "counselor"])
126+
)
127+
users = (
128+
get_user_model()
129+
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year(), subscribed_to_set__contains=obj.club)
130+
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"], subscribed_to_set__contains=obj.club))
131+
)
132+
122133
else:
123134
users = (
124135
get_user_model()

intranet/apps/announcements/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
urlpatterns = [
66
re_path(r"^$", views.view_announcements, name="view_announcements"),
77
re_path(r"^/archive$", views.view_announcements_archive, name="announcements_archive"),
8+
re_path(r"^/club$", views.view_club_announcements, name="club_announcements"),
89
re_path(r"^/add$", views.add_announcement_view, name="add_announcement"),
910
re_path(r"^/request$", views.request_announcement_view, name="request_announcement"),
11+
re_path(r"^/request/club$", views.request_club_announcement_view, name="request_club_announcement"),
1012
re_path(r"^/request/success$", views.request_announcement_success_view, name="request_announcement_success"),
1113
re_path(r"^/request/success_self$", views.request_announcement_success_self_view, name="request_announcement_success_self"),
1214
re_path(r"^/approve/(?P<req_id>\d+)$", views.approve_announcement_view, name="approve_announcement"),

intranet/apps/announcements/views.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313
from ..auth.decorators import announcements_admin_required, deny_restricted
1414
from ..dashboard.views import dashboard_view
1515
from ..groups.models import Group
16-
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm
16+
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm, ClubAnnouncementForm
1717
from .models import Announcement, AnnouncementRequest
18-
from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email, announcement_posted_twitter,
19-
request_announcement_email)
18+
from .notifications import (
19+
admin_request_announcement_email,
20+
announcement_approved_email,
21+
announcement_posted_email,
22+
announcement_posted_twitter,
23+
request_announcement_email,
24+
)
2025

2126
logger = logging.getLogger(__name__)
2227

@@ -35,6 +40,13 @@ def view_announcements_archive(request):
3540
return dashboard_view(request, show_widgets=False, show_expired=True, ignore_dashboard_types=["event"])
3641

3742

43+
@login_required
44+
@deny_restricted
45+
def view_club_announcements(request):
46+
"""Show the dashboard with only club posts."""
47+
return dashboard_view(request, show_widgets=False, show_hidden_club=True, ignore_dashboard_types=["event"])
48+
49+
3850
def announcement_posted_hook(request, obj):
3951
"""Runs whenever a new announcement is created, or a request is approved and posted.
4052
@@ -118,6 +130,27 @@ def request_announcement_view(request):
118130
return render(request, "announcements/request.html", {"form": form, "action": "add"})
119131

120132

133+
def request_club_announcement_view(request):
134+
"""The request announcement page."""
135+
if request.method == "POST":
136+
form = ClubAnnouncementForm(request.user, request.POST)
137+
138+
if form.is_valid():
139+
obj = form.save(commit=True)
140+
obj.user = request.user
141+
# SAFE HTML
142+
obj.content = safe_html(obj.content)
143+
144+
obj.save()
145+
146+
return redirect("index")
147+
else:
148+
messages.error(request, "Error adding announcement")
149+
else:
150+
form = ClubAnnouncementForm(request.user)
151+
return render(request, "announcements/club-request.html", {"form": form, "action": "add"})
152+
153+
121154
@login_required
122155
def request_announcement_success_view(request):
123156
return render(request, "announcements/success.html", {"type": "request"})

intranet/apps/dashboard/views.py

+61-7
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,31 @@ def get_announcements_list(request, context):
261261
return items
262262

263263

264-
def paginate_announcements_list(request, context, items):
264+
def split_club_announcements(items):
265+
standard, club = [], []
266+
267+
for item in items:
268+
if item.dashboard_type == "announcement" and item.is_club_announcement:
269+
club.append(item)
270+
else:
271+
standard.append(item)
272+
273+
return standard, club
274+
275+
276+
def filter_hidden_club_announcements(user, user_hidden_announcements, club_items):
277+
visible, hidden = [], []
278+
279+
for item in club_items:
280+
if item.id in user_hidden_announcements or user not in item.activity.subscribers.all():
281+
hidden.append(item)
282+
else:
283+
visible.append(item)
284+
285+
return visible, hidden
286+
287+
288+
def paginate_announcements_list(request, context, items, visible_club_items, hidden_club_items):
265289
"""
266290
***TODO*** Migrate to django Paginator (see lostitems)
267291
@@ -287,7 +311,24 @@ def paginate_announcements_list(request, context, items):
287311
else:
288312
items = items_sorted
289313

290-
context.update({"items": items, "start_num": start_num, "end_num": end_num, "prev_page": prev_page, "more_items": more_items})
314+
club_items = visible_club_items[:display_num]
315+
316+
if hidden_club_items:
317+
more_club_items = True
318+
else:
319+
more_club_items = False
320+
321+
context.update(
322+
{
323+
"club_items": club_items,
324+
"more_club_items": more_club_items,
325+
"items": items,
326+
"start_num": start_num,
327+
"end_num": end_num,
328+
"prev_page": prev_page,
329+
"more_items": more_items,
330+
}
331+
)
291332

292333
return context, items
293334

@@ -380,7 +421,7 @@ def add_widgets_context(request, context):
380421

381422

382423
@login_required
383-
def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashboard_types=None, show_welcome=False):
424+
def dashboard_view(request, show_widgets=True, show_expired=False, show_hidden_club=False, ignore_dashboard_types=None, show_welcome=False):
384425
"""Process and show the dashboard, which includes activities, events, and widgets."""
385426

386427
user = request.user
@@ -429,6 +470,9 @@ def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashbo
429470
# Show all by default to 8th period office
430471
show_all = True
431472

473+
if not show_hidden_club:
474+
show_hidden_club = "show_hidden_club" in request.GET
475+
432476
# Include show_all postfix on next/prev links
433477
paginate_link_suffix = "&show_all=1" if show_all else ""
434478
is_index_page = request.path_info in ["/", ""]
@@ -440,19 +484,26 @@ def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashbo
440484
"events_admin": events_admin,
441485
"is_index_page": is_index_page,
442486
"show_all": show_all,
487+
"show_hidden_club": show_hidden_club,
443488
"paginate_link_suffix": paginate_link_suffix,
444489
"show_expired": show_expired,
445490
"show_tjstar": settings.TJSTAR_BANNER_START_DATE <= now.date() <= settings.TJSTAR_DATE,
446491
}
447492

493+
user_hidden_announcements = Announcement.objects.hidden_announcements(user).values_list("id", flat=True)
494+
user_hidden_events = Event.objects.hidden_events(user).values_list("id", flat=True)
495+
448496
# Get list of announcements
449497
items = get_announcements_list(request, context)
450498

451-
# Paginate announcements list
452-
context, items = paginate_announcements_list(request, context, items)
499+
items, club_items = split_club_announcements(items)
453500

454-
user_hidden_announcements = Announcement.objects.hidden_announcements(user).values_list("id", flat=True)
455-
user_hidden_events = Event.objects.hidden_events(user).values_list("id", flat=True)
501+
# Paginate announcements list
502+
if not show_hidden_club:
503+
visible_club_items, hidden_club_items = filter_hidden_club_announcements(user, user_hidden_announcements, club_items)
504+
context, items = paginate_announcements_list(request, context, items, visible_club_items, hidden_club_items)
505+
else:
506+
context, items = paginate_announcements_list(request, context, club_items, [], [])
456507

457508
if ignore_dashboard_types is None:
458509
ignore_dashboard_types = []
@@ -483,6 +534,9 @@ def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashbo
483534
elif show_expired:
484535
dashboard_title = dashboard_header = "Announcement Archive"
485536
view_announcements_url = "announcements_archive"
537+
elif show_hidden_club:
538+
dashboard_title = dashboard_header = "Club Announcements"
539+
view_announcements_url = "club_announcements"
486540
else:
487541
dashboard_title = dashboard_header = "Announcements"
488542

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 3.2.20 on 2024-02-11 02:29
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11+
('eighth', '0065_auto_20220903_0038'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='eighthactivity',
17+
name='officers',
18+
field=models.ManyToManyField(blank=True, related_name='officer_for_set', to=settings.AUTH_USER_MODEL),
19+
),
20+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 3.2.20 on 2024-02-14 00:06
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11+
('eighth', '0066_eighthactivity_officers'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='eighthactivity',
17+
name='subscribers',
18+
field=models.ManyToManyField(blank=True, related_name='subscribed_activity_set', to=settings.AUTH_USER_MODEL),
19+
),
20+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.2.20 on 2024-02-14 00:38
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('eighth', '0067_eighthactivity_subscribers'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='eighthactivity',
15+
name='subscriptions_enabled',
16+
field=models.BooleanField(default=False),
17+
),
18+
migrations.AddField(
19+
model_name='historicaleighthactivity',
20+
name='subscriptions_enabled',
21+
field=models.BooleanField(default=False),
22+
),
23+
]

0 commit comments

Comments
 (0)