Skip to content

Commit

Permalink
feat(announcements): implement club announcements
Browse files Browse the repository at this point in the history
  • Loading branch information
krishnans2006 committed Feb 14, 2024
1 parent d348d9c commit f9eedf3
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 35 deletions.
38 changes: 23 additions & 15 deletions intranet/apps/announcements/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,38 @@
class AnnouncementForm(forms.ModelForm):
"""A form for generating an announcement."""

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

self.fields["notify_post"].help_text = "If this box is checked, students who have signed up for notifications will receive an email."
class Meta:
model = Announcement
fields = ["title", "author", "content", "groups", "expiration_date", "notify_post", "notify_email_all", "update_added_date", "pinned"]
help_texts = {
"expiration_date": "By default, announcements expire after two weeks. To change this, click in the box above.",
"notify_post": "If this box is checked, students who have signed up for notifications will receive an email.",
"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.",
"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.",
}

self.fields["notify_email_all"].help_text = (
"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."
)

self.fields["update_added_date"].help_text = (
"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."
)
class ClubAnnouncementForm(forms.ModelForm):
"""A form for posting a club announcement."""

def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["activity"].queryset = user.officer_for_set

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

class Meta:
model = Announcement
fields = ["title", "author", "content", "groups", "expiration_date", "notify_post", "notify_email_all", "update_added_date", "pinned"]
fields = ["title", "author", "content", "activity", "expiration_date", "update_added_date"]
help_texts = {
"expiration_date": "By default, announcements expire after two weeks. To change this, click in the box above.",
"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.",
}


class AnnouncementEditForm(forms.ModelForm):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.2.20 on 2024-02-14 00:06

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('eighth', '0067_eighthactivity_subscribers'),
('announcements', '0032_alter_warningannouncement_type'),
]

operations = [
migrations.AddField(
model_name='announcement',
name='activity',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='eighth.eighthactivity'),
),
]
8 changes: 8 additions & 0 deletions intranet/apps/announcements/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from ...utils.deletion import set_historical_user
from ...utils.html import nullify_links

from ..eighth.models import EighthActivity


class AnnouncementManager(Manager):
def visible_to_user(self, user):
Expand Down Expand Up @@ -110,6 +112,8 @@ class Announcement(models.Model):
updated = models.DateTimeField(auto_now=True)
groups = models.ManyToManyField(DjangoGroup, blank=True)

activity = models.ForeignKey(EighthActivity, null=True, blank=True, on_delete=models.CASCADE)

expiration_date = models.DateTimeField(auto_now=False, default=timezone.make_aware(datetime(3000, 1, 1)))

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

@property
def is_club_announcement(self):
return self.activity is not None

def is_visible(self, user):
return self in Announcement.objects.visible_to_user(user)

Expand Down
11 changes: 11 additions & 0 deletions intranet/apps/announcements/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.core import exceptions
from django.db.models import Q
from django.urls import reverse

from ...utils.date import get_senior_graduation_year
Expand Down Expand Up @@ -119,6 +120,16 @@ def announcement_posted_email(request, obj, send_all=False):
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year())
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"]))
)
elif obj.club:
filter = Q(subscribed_to_set__contains=obj.club) & (
Q(user_type="student") & Q(graduation_year__gte=get_senior_graduation_year()) | Q(user_type__in=["teacher", "counselor"])
)
users = (
get_user_model()
.objects.filter(user_type="student", graduation_year__gte=get_senior_graduation_year(), subscribed_to_set__contains=obj.club)
.union(get_user_model().objects.filter(user_type__in=["teacher", "counselor"], subscribed_to_set__contains=obj.club))
)

else:
users = (
get_user_model()
Expand Down
2 changes: 2 additions & 0 deletions intranet/apps/announcements/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
urlpatterns = [
re_path(r"^$", views.view_announcements, name="view_announcements"),
re_path(r"^/archive$", views.view_announcements_archive, name="announcements_archive"),
re_path(r"^/club$", views.view_club_announcements, name="club_announcements"),
re_path(r"^/add$", views.add_announcement_view, name="add_announcement"),
re_path(r"^/request$", views.request_announcement_view, name="request_announcement"),
re_path(r"^/request/club$", views.request_club_announcement_view, name="request_club_announcement"),
re_path(r"^/request/success$", views.request_announcement_success_view, name="request_announcement_success"),
re_path(r"^/request/success_self$", views.request_announcement_success_self_view, name="request_announcement_success_self"),
re_path(r"^/approve/(?P<req_id>\d+)$", views.approve_announcement_view, name="approve_announcement"),
Expand Down
39 changes: 36 additions & 3 deletions intranet/apps/announcements/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
from ..auth.decorators import announcements_admin_required, deny_restricted
from ..dashboard.views import dashboard_view
from ..groups.models import Group
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm, ClubAnnouncementForm
from .models import Announcement, AnnouncementRequest
from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email, announcement_posted_twitter,
request_announcement_email)
from .notifications import (
admin_request_announcement_email,
announcement_approved_email,
announcement_posted_email,
announcement_posted_twitter,
request_announcement_email,
)

logger = logging.getLogger(__name__)

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


@login_required
@deny_restricted
def view_club_announcements(request):
"""Show the dashboard with only club posts."""
return dashboard_view(request, show_widgets=False, show_hidden_club=True, ignore_dashboard_types=["event"])


def announcement_posted_hook(request, obj):
"""Runs whenever a new announcement is created, or a request is approved and posted.
Expand Down Expand Up @@ -118,6 +130,27 @@ def request_announcement_view(request):
return render(request, "announcements/request.html", {"form": form, "action": "add"})


def request_club_announcement_view(request):
"""The request announcement page."""
if request.method == "POST":
form = ClubAnnouncementForm(request.user, request.POST)

if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
# SAFE HTML
obj.content = safe_html(obj.content)

obj.save()

return redirect("index")
else:
messages.error(request, "Error adding announcement")
else:
form = ClubAnnouncementForm(request.user)
return render(request, "announcements/club-request.html", {"form": form, "action": "add"})


@login_required
def request_announcement_success_view(request):
return render(request, "announcements/success.html", {"type": "request"})
Expand Down
68 changes: 61 additions & 7 deletions intranet/apps/dashboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,31 @@ def get_announcements_list(request, context):
return items


def paginate_announcements_list(request, context, items):
def split_club_announcements(items):
standard, club = [], []

for item in items:
if item.dashboard_type == "announcement" and item.is_club_announcement:
club.append(item)
else:
standard.append(item)

return standard, club


def filter_hidden_club_announcements(user, user_hidden_announcements, club_items):
visible, hidden = [], []

for item in club_items:
if item.id in user_hidden_announcements or user not in item.activity.subscribers.all():
hidden.append(item)
else:
visible.append(item)

return visible, hidden


def paginate_announcements_list(request, context, items, visible_club_items, hidden_club_items):
"""
***TODO*** Migrate to django Paginator (see lostitems)
Expand All @@ -287,7 +311,24 @@ def paginate_announcements_list(request, context, items):
else:
items = items_sorted

context.update({"items": items, "start_num": start_num, "end_num": end_num, "prev_page": prev_page, "more_items": more_items})
club_items = visible_club_items[:display_num]

if hidden_club_items:
more_club_items = True
else:
more_club_items = False

context.update(
{
"club_items": club_items,
"more_club_items": more_club_items,
"items": items,
"start_num": start_num,
"end_num": end_num,
"prev_page": prev_page,
"more_items": more_items,
}
)

return context, items

Expand Down Expand Up @@ -380,7 +421,7 @@ def add_widgets_context(request, context):


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

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

if not show_hidden_club:
show_hidden_club = "show_hidden_club" in request.GET

# Include show_all postfix on next/prev links
paginate_link_suffix = "&show_all=1" if show_all else ""
is_index_page = request.path_info in ["/", ""]
Expand All @@ -440,19 +484,26 @@ def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashbo
"events_admin": events_admin,
"is_index_page": is_index_page,
"show_all": show_all,
"show_hidden_club": show_hidden_club,
"paginate_link_suffix": paginate_link_suffix,
"show_expired": show_expired,
"show_tjstar": settings.TJSTAR_BANNER_START_DATE <= now.date() <= settings.TJSTAR_DATE,
}

user_hidden_announcements = Announcement.objects.hidden_announcements(user).values_list("id", flat=True)
user_hidden_events = Event.objects.hidden_events(user).values_list("id", flat=True)

# Get list of announcements
items = get_announcements_list(request, context)

# Paginate announcements list
context, items = paginate_announcements_list(request, context, items)
items, club_items = split_club_announcements(items)

user_hidden_announcements = Announcement.objects.hidden_announcements(user).values_list("id", flat=True)
user_hidden_events = Event.objects.hidden_events(user).values_list("id", flat=True)
# Paginate announcements list
if not show_hidden_club:
visible_club_items, hidden_club_items = filter_hidden_club_announcements(user, user_hidden_announcements, club_items)
context, items = paginate_announcements_list(request, context, items, visible_club_items, hidden_club_items)
else:
context, items = paginate_announcements_list(request, context, club_items, [], [])

if ignore_dashboard_types is None:
ignore_dashboard_types = []
Expand Down Expand Up @@ -483,6 +534,9 @@ def dashboard_view(request, show_widgets=True, show_expired=False, ignore_dashbo
elif show_expired:
dashboard_title = dashboard_header = "Announcement Archive"
view_announcements_url = "announcements_archive"
elif show_hidden_club:
dashboard_title = dashboard_header = "Club Announcements"
view_announcements_url = "club_announcements"
else:
dashboard_title = dashboard_header = "Announcements"

Expand Down
20 changes: 20 additions & 0 deletions intranet/apps/eighth/migrations/0066_eighthactivity_officers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.2.20 on 2024-02-11 02:29

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('eighth', '0065_auto_20220903_0038'),
]

operations = [
migrations.AddField(
model_name='eighthactivity',
name='officers',
field=models.ManyToManyField(blank=True, related_name='officer_for_set', to=settings.AUTH_USER_MODEL),
),
]
20 changes: 20 additions & 0 deletions intranet/apps/eighth/migrations/0067_eighthactivity_subscribers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.2.20 on 2024-02-14 00:06

from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('eighth', '0066_eighthactivity_officers'),
]

operations = [
migrations.AddField(
model_name='eighthactivity',
name='subscribers',
field=models.ManyToManyField(blank=True, related_name='subscribed_activity_set', to=settings.AUTH_USER_MODEL),
),
]
23 changes: 23 additions & 0 deletions intranet/apps/eighth/migrations/0068_auto_20240213_1938.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.20 on 2024-02-14 00:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('eighth', '0067_eighthactivity_subscribers'),
]

operations = [
migrations.AddField(
model_name='eighthactivity',
name='subscriptions_enabled',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='historicaleighthactivity',
name='subscriptions_enabled',
field=models.BooleanField(default=False),
),
]
Loading

0 comments on commit f9eedf3

Please sign in to comment.