Skip to content

Commit 56ea8f3

Browse files
committed
temp working commit
1 parent ffdd800 commit 56ea8f3

27 files changed

+531
-349
lines changed

intranet/apps/announcements/admin.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55

66
class AnnouncementAdmin(admin.ModelAdmin):
7-
list_display = ("title", "user", "author", "added")
8-
list_filter = ("added", "updated")
7+
list_display = ("title", "user", "author", "activity", "added")
8+
list_filter = ("added", "updated", "activity")
99
ordering = ("-added",)
1010
raw_id_fields = ("user",)
1111

intranet/apps/announcements/forms.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
from ..users.forms import SortedTeacherMultipleChoiceField
66
from .models import Announcement, AnnouncementRequest
7+
from ..eighth.models import EighthActivity
78

9+
import logging
10+
11+
logger = logging.getLogger(__name__)
812

913
class AnnouncementForm(forms.ModelForm):
1014
"""A form for generating an announcement."""
@@ -29,17 +33,28 @@ class ClubAnnouncementForm(forms.ModelForm):
2933

3034
def __init__(self, user, *args, **kwargs):
3135
super().__init__(*args, **kwargs)
32-
self.fields["activity"].queryset = user.officer_for_set
36+
37+
if user.is_announcements_admin:
38+
self.fields["activity"].queryset = EighthActivity.objects.filter(subscriptions_enabled=True)
39+
elif user.is_club_officer:
40+
self.fields["activity"].queryset = EighthActivity.objects.filter(subscriptions_enabled=True, officers=user)
41+
elif user.is_club_sponsor:
42+
self.fields["activity"].queryset = user.club_sponsor_for_set.filter(subscriptions_enabled=True)
43+
else:
44+
self.fields["activity"].queryset = []
45+
self.fields["activity"].required = True
46+
47+
if "instance" in kwargs: # Don't allow changing the activity once the announcement has been created
48+
self.fields["activity"].widget.attrs["disabled"] = True
49+
self.fields["activity"].required = False
3350

3451
expiration_date = forms.DateTimeInput()
35-
update_added_date = forms.BooleanField(required=False, label="Update Added Date")
3652

3753
class Meta:
3854
model = Announcement
39-
fields = ["title", "author", "content", "activity", "expiration_date", "update_added_date"]
55+
fields = ["activity", "title", "content", "expiration_date"]
4056
help_texts = {
4157
"expiration_date": "By default, announcements expire after two weeks. To change this, click in the box above.",
42-
"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.",
4358
}
4459

4560

intranet/apps/announcements/models.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from ...utils.date import get_date_range_this_year, is_current_year
1111
from ...utils.deletion import set_historical_user
1212
from ...utils.html import nullify_links
13-
1413
from ..eighth.models import EighthActivity
1514

1615

@@ -90,7 +89,7 @@ class Announcement(models.Model):
9089
The title of the announcement
9190
content
9291
The HTML content of the news post
93-
authors
92+
author
9493
The name of the author
9594
added
9695
The date the announcement was added
@@ -151,6 +150,11 @@ def is_club_announcement(self):
151150

152151
def is_visible(self, user):
153152
return self in Announcement.objects.visible_to_user(user)
153+
154+
def can_modify(self, user):
155+
return user.is_announcements_admin or self.is_club_announcement and (
156+
self.is_visible_submitter(user) or user.club_sponsor_for_set.filter(id=self.activity.id).exists()
157+
)
154158

155159
# False, not None. This can be None if no AnnouncementRequest exists for this Announcement,
156160
# and we should not reevaluate in that case.
@@ -165,13 +169,13 @@ def announcementrequest(self):
165169

166170
def is_visible_requester(self, user):
167171
try:
168-
return self.announcementrequest_set.filter(teachers_requested__id=user.id).exists()
172+
return self.announcementrequest_set.filter(teachers_requested=user).exists()
169173
except get_user_model().DoesNotExist:
170174
return False
171175

172176
def is_visible_submitter(self, user):
173177
try:
174-
return (self.announcementrequest and user.id == self.announcementrequest.user_id) or self.user_id == user.id
178+
return self.user == user or self.announcementrequest and user == self.announcementrequest.user
175179
except get_user_model().DoesNotExist:
176180
return False
177181

intranet/apps/announcements/notifications.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,12 @@ def announcement_posted_email(request, obj, send_all=False):
120120
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year())
121121
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"]))
122122
)
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-
)
123+
elif obj.activity:
124+
subject = f"Club Announcement for {obj.activity.name}: {obj.title}"
127125
users = (
128126
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))
127+
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year(), subscribed_to_set__contains=obj.activity)
128+
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"], subscribed_to_set__contains=obj.activity))
131129
)
132130

133131
else:

intranet/apps/announcements/urls.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
re_path(r"^/club$", views.view_club_announcements, name="club_announcements"),
99
re_path(r"^/add$", views.add_announcement_view, name="add_announcement"),
1010
re_path(r"^/request$", views.request_announcement_view, name="request_announcement"),
11-
re_path(r"^/club/post$", views.post_club_announcement_view, name="post_club_announcement"),
11+
re_path(r"^/club/add$", views.add_club_announcement_view, name="add_club_announcement"),
12+
re_path(r"^/club/modify/(?P<announcement_id>\d+)$", views.modify_club_announcement_view, name="modify_club_announcement"),
1213
re_path(r"^/request/success$", views.request_announcement_success_view, name="request_announcement_success"),
1314
re_path(r"^/request/success_self$", views.request_announcement_success_self_view, name="request_announcement_success_self"),
1415
re_path(r"^/approve/(?P<req_id>\d+)$", views.approve_announcement_view, name="approve_announcement"),

intranet/apps/announcements/views.py

+46-12
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,8 @@
1515
from ..groups.models import Group
1616
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm, ClubAnnouncementForm
1717
from .models import Announcement, AnnouncementRequest
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-
)
18+
from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email, announcement_posted_twitter,
19+
request_announcement_email)
2520

2621
logger = logging.getLogger(__name__)
2722

@@ -131,7 +126,13 @@ def request_announcement_view(request):
131126
return render(request, "announcements/request.html", {"form": form, "action": "add"})
132127

133128

134-
def post_club_announcement_view(request):
129+
@login_required
130+
@deny_restricted
131+
def add_club_announcement_view(request):
132+
if not (request.user.is_announcements_admin or request.user.is_club_officer or request.user.is_club_sponsor):
133+
messages.error(request, "You do not have permission to post club announcements.")
134+
return redirect("club_announcements")
135+
135136
if request.method == "POST":
136137
form = ClubAnnouncementForm(request.user, request.POST)
137138

@@ -143,13 +144,46 @@ def post_club_announcement_view(request):
143144

144145
obj.save()
145146

146-
messages.success(request, "Successfully posted announcement.")
147+
messages.success(request, "Successfully posted club announcement.")
147148
return redirect("club_announcements")
148149
else:
149-
messages.error(request, "Error adding announcement")
150+
messages.error(request, "Error adding club announcement")
150151
else:
151152
form = ClubAnnouncementForm(request.user)
152-
return render(request, "announcements/club-request.html", {"form": form, "action": "add"})
153+
return render(request, "announcements/club-request.html", {"form": form, "action": "post"})
154+
155+
@login_required
156+
@deny_restricted
157+
def modify_club_announcement_view(request, announcement_id):
158+
announcement = get_object_or_404(Announcement, id=announcement_id)
159+
160+
if not announcement.is_club_announcement:
161+
messages.error(request, "This announcement is not a club announcement.")
162+
return redirect("club_announcements")
163+
164+
if not announcement.can_modify(request.user):
165+
messages.error(request, "You do not have permission to modify this club announcement.")
166+
return redirect("club_announcements")
167+
168+
if request.method == "POST":
169+
form = ClubAnnouncementForm(request.user, request.POST, instance=announcement)
170+
171+
if form.is_valid():
172+
obj = form.save(commit=True)
173+
obj.user = request.user
174+
obj.activity = announcement.activity
175+
# SAFE HTML
176+
obj.content = safe_html(obj.content)
177+
178+
obj.save()
179+
180+
messages.success(request, "Successfully modified club announcement.")
181+
return redirect("club_announcements")
182+
else:
183+
messages.error(request, "Error modifying club announcement")
184+
else:
185+
form = ClubAnnouncementForm(request.user, instance=announcement)
186+
return render(request, "announcements/club-request.html", {"form": form, "action": "modify"})
153187

154188

155189
@login_required
@@ -355,7 +389,7 @@ def modify_announcement_view(request, announcement_id=None):
355389
logger.info("Admin %s modified announcement: %s (%s)", request.user, announcement, announcement.id)
356390
return redirect("index")
357391
else:
358-
messages.error(request, "Error adding announcement")
392+
messages.error(request, "Error modifying announcement")
359393
else:
360394
announcement = get_object_or_404(Announcement, id=announcement_id)
361395
form = AnnouncementEditForm(instance=announcement)

intranet/apps/dashboard/views.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,11 @@ def get_announcements_list(request, context):
239239

240240
# Load information on the user who posted the announcement
241241
# Unless the announcement has a custom author (some do, but not all), we will need the user information to construct the byline,
242-
announcements = announcements.select_related("user")
242+
announcements = announcements.select_related("user", "activity")
243243

244244
# We may query the announcement request multiple times while checking if the user submitted or approved the announcement.
245245
# prefetch_related() will still make a separate query for each request, but the results are cached if we check them multiple times
246-
announcements = announcements.prefetch_related("announcementrequest_set")
246+
announcements = announcements.prefetch_related("announcementrequest_set", "groups")
247247

248248
if context["events_admin"] and context["show_all"]:
249249
events = Event.objects.all()
@@ -255,6 +255,8 @@ def get_announcements_list(request, context):
255255
midnight = timezone.localtime().replace(hour=0, minute=0, second=0, microsecond=0)
256256
events = Event.objects.visible_to_user(user).filter(time__gte=midnight, show_on_dashboard=True)
257257

258+
events = events.select_related("user").prefetch_related("groups")
259+
258260
items = sorted(chain(announcements, events), key=lambda item: (item.pinned, item.added))
259261
items.reverse()
260262

@@ -266,7 +268,8 @@ def split_club_announcements(items):
266268

267269
for item in items:
268270
if item.dashboard_type == "announcement" and item.is_club_announcement:
269-
club.append(item)
271+
if item.activity.subscriptions_enabled:
272+
club.append(item)
270273
else:
271274
standard.append(item)
272275

@@ -277,12 +280,13 @@ def filter_club_announcements(user, user_hidden_announcements, club_items):
277280
visible, hidden, unsubscribed = [], [], []
278281

279282
for item in club_items:
280-
if user not in item.activity.subscribers.all():
281-
unsubscribed.append(item)
282-
elif item.id in user_hidden_announcements:
283-
hidden.append(item)
284-
else:
285-
visible.append(item)
283+
if item.activity.subscriptions_enabled:
284+
if user not in item.activity.subscribers.all():
285+
unsubscribed.append(item)
286+
elif item.id in user_hidden_announcements:
287+
hidden.append(item)
288+
else:
289+
visible.append(item)
286290

287291
return visible, hidden, unsubscribed
288292

intranet/apps/eighth/forms/admin/activities.py

+20
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,16 @@ def __init__(self, *args, **kwargs):
135135
student_objects = get_user_model().objects.get_students()
136136
self.fields["users_allowed"].queryset = student_objects
137137
self.fields["users_blacklisted"].queryset = student_objects
138+
self.fields["officers"].queryset = student_objects
139+
self.fields["club_sponsors"].queryset = get_user_model().objects.filter(user_type__in=["teacher", "counselor"])
138140

139141
self.fields["presign"].label = "2 day pre-signup"
140142
self.fields["default_capacity"].help_text = "Overrides the sum of each room's capacity above, if set."
143+
self.fields["subscriptions_enabled"].label = "Enable club announcements"
144+
self.fields["subscriptions_enabled"].help_text = "Allow students to subscribe to receive announcements for this activity through Ion."
145+
self.fields["officers"].help_text = "Student officers can send club announcements to subscribers."
146+
self.fields["club_sponsors"].help_text = "Club sponsors can manage this club's announcements. May be different from the activity's scheduled sponsors."
147+
self.fields["subscribers"].help_text = "Students who subscribe to this activity will receive club announcements."
141148

142149
# These fields are rendered on the right of the page on the edit activity page.
143150
self.right_fields = set(
@@ -153,6 +160,15 @@ def __init__(self, *args, **kwargs):
153160
]
154161
)
155162

163+
self.club_announcements_fields = set(
164+
[
165+
"subscriptions_enabled",
166+
"club_sponsors",
167+
"officers",
168+
"subscribers",
169+
]
170+
)
171+
156172
class Meta:
157173
model = EighthActivity
158174
fields = [
@@ -182,6 +198,10 @@ class Meta:
182198
"fri_a",
183199
"fri_b",
184200
"admin_comments",
201+
"subscriptions_enabled",
202+
"club_sponsors",
203+
"officers",
204+
"subscribers",
185205
]
186206
widgets = {
187207
"description": forms.Textarea(attrs={"rows": 9, "cols": 46}),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 3.2.25 on 2024-03-30 04:11
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import intranet.utils.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('eighth', '0068_auto_20240213_1938'),
13+
]
14+
15+
operations = [
16+
migrations.AlterField(
17+
model_name='eighthsponsor',
18+
name='user',
19+
field=models.OneToOneField(blank=True, null=True, on_delete=intranet.utils.deletion.set_historical_user, related_name='sponsor_obj', to=settings.AUTH_USER_MODEL),
20+
),
21+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 3.2.25 on 2024-04-01 02:04
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', '0069_alter_eighthsponsor_user'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='eighthactivity',
17+
name='club_sponsors',
18+
field=models.ManyToManyField(blank=True, related_name='club_sponsor_for_set', to=settings.AUTH_USER_MODEL),
19+
),
20+
]

0 commit comments

Comments
 (0)