import logging
from django import http
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from intranet import settings
from ...utils.html import safe_html
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,
ClubAnnouncementEditForm,
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,
)
logger = logging.getLogger(__name__)
@login_required
@deny_restricted
def view_announcements(request):
"""Show the dashboard with only announcements."""
return dashboard_view(request, show_widgets=False, ignore_dashboard_types=["event"])
@login_required
@deny_restricted
def view_announcements_archive(request):
"""Show the dashboard with only announcements, showing expired posts."""
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"])
[docs]def announcement_posted_hook(request, obj):
"""Runs whenever a new announcement is created, or a request is approved and posted.
obj: The Announcement object
"""
if obj.notify_post:
announcement_posted_twitter(request, obj)
try:
notify_all = obj.notify_email_all
except AttributeError:
notify_all = False
try:
if notify_all:
announcement_posted_email(request, obj, True)
else:
announcement_posted_email(request, obj)
except Exception as e:
logger.error("Exception when emailing announcement: %s", e)
messages.error(request, f"Exception when emailing announcement: {e}")
raise e
else:
logger.debug("Announcement notify off")
[docs]def announcement_approved_hook(request, obj, req):
"""Runs whenever an administrator approves an announcement request.
obj: the Announcement object
req: the AnnouncementRequest object
"""
announcement_approved_email(request, obj, req)
@login_required
@deny_restricted
def request_announcement_view(request):
"""The request announcement page."""
if request.method == "POST":
form = AnnouncementRequestForm(request.POST)
if form.is_valid():
teacher_objs = form.cleaned_data["teachers_requested"]
if len(teacher_objs) > 2:
messages.error(request, "Please select a maximum of 2 teachers to approve this post.")
else:
obj = form.save(commit=True)
obj.user = request.user
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
ann = AnnouncementRequest.objects.get(id=obj.id)
approve_self = False
for teacher in teacher_objs:
ann.teachers_requested.add(teacher)
if teacher == request.user:
ann.teachers_approved.add(teacher)
approve_self = True
ann.save()
if approve_self:
if settings.SEND_ANNOUNCEMENT_APPROVAL:
admin_request_announcement_email(request, form, ann)
ann.admin_email_sent = True
ann.save()
return redirect("request_announcement_success_self")
else:
if settings.SEND_ANNOUNCEMENT_APPROVAL:
request_announcement_email(request, form, obj)
return redirect("request_announcement_success")
return redirect("index")
else:
messages.error(request, "Error adding announcement request")
else:
form = AnnouncementRequestForm()
return render(request, "announcements/request.html", {"form": form, "action": "add"})
@login_required
@deny_restricted
def add_club_announcement_view(request):
is_announcements_admin = request.user.is_announcements_admin
is_club_sponsor = request.user.is_club_sponsor
is_club_officer = request.user.is_club_officer
if not (is_announcements_admin or is_club_sponsor or is_club_officer):
messages.error(request, "You do not have permission to post club announcements.")
return redirect("club_announcements")
if request.method == "POST":
form = ClubAnnouncementForm(request.user, request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
announcement_posted_hook(request, obj)
messages.success(request, "Successfully posted club announcement.")
return redirect("club_announcements")
else:
messages.error(request, "Error adding club announcement")
else:
form = ClubAnnouncementForm(request.user)
if not form.fields["activity"].queryset.exists():
if is_announcements_admin:
messages.error(request, "No clubs have enabled this feature yet.")
elif is_club_sponsor:
messages.error(request, "Please enable club announcements for your club.")
else:
messages.error(request, "Please ask your club sponsor to enable posting announcements for your club.")
return redirect("club_announcements")
return render(request, "announcements/club-request.html", {"form": form, "action": "post"})
@login_required
@deny_restricted
def modify_club_announcement_view(request, announcement_id):
announcement = get_object_or_404(Announcement, id=announcement_id)
if not announcement.is_club_announcement:
messages.error(request, "This announcement is not a club announcement.")
return redirect("club_announcements")
if not announcement.can_modify(request.user):
messages.error(request, "You do not have permission to modify this club announcement.")
return redirect("club_announcements")
if request.method == "POST":
form = ClubAnnouncementEditForm(request.POST, instance=announcement)
if form.is_valid():
obj = form.save(commit=False)
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
messages.success(request, "Successfully modified club announcement.")
return redirect("club_announcements")
else:
messages.error(request, "Error modifying club announcement")
else:
form = ClubAnnouncementEditForm(instance=announcement)
return render(request, "announcements/club-request.html", {"form": form, "action": "modify"})
@login_required
@deny_restricted
def request_announcement_success_view(request):
return render(request, "announcements/success.html", {"type": "request"})
@login_required
@deny_restricted
def request_announcement_success_self_view(request):
return render(request, "announcements/success.html", {"type": "request", "self": True})
@login_required
@deny_restricted
def approve_announcement_view(request, req_id):
"""The approve announcement page. Teachers will be linked to this page from an email.
req_id: The ID of the AnnouncementRequest
"""
req = get_object_or_404(AnnouncementRequest, id=req_id)
requested_teachers = req.teachers_requested.all()
if request.user not in requested_teachers:
messages.error(request, "You do not have permission to approve this announcement.")
return redirect("index")
if request.method == "POST":
form = AnnouncementRequestForm(request.POST, instance=req)
if form.is_valid():
obj = form.save(commit=True)
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
if "approve" in request.POST:
obj.teachers_approved.add(request.user)
obj.save()
if not obj.admin_email_sent:
if settings.SEND_ANNOUNCEMENT_APPROVAL:
admin_request_announcement_email(request, form, obj)
obj.admin_email_sent = True
obj.save()
return redirect("approve_announcement_success")
else:
obj.save()
return redirect("approve_announcement_reject")
form = AnnouncementRequestForm(instance=req)
context = {"form": form, "req": req, "admin_approve": False}
return render(request, "announcements/approve.html", context)
@login_required
@deny_restricted
def approve_announcement_success_view(request):
return render(request, "announcements/success.html", {"type": "approve", "status": "accept"})
@login_required
@deny_restricted
def approve_announcement_reject_view(request):
return render(request, "announcements/success.html", {"type": "approve", "status": "reject"})
@announcements_admin_required
@deny_restricted
def admin_approve_announcement_view(request, req_id):
"""The administrator approval announcement request page. Admins will view this page through the
UI.
req_id: The ID of the AnnouncementRequest
"""
req = get_object_or_404(AnnouncementRequest, id=req_id)
if request.method == "POST":
form = AnnouncementRequestForm(request.POST, instance=req)
admin_form = AnnouncementAdminForm(request.POST)
if form.is_valid() and admin_form.is_valid():
req = form.save(commit=True)
# SAFE HTML
req.content = safe_html(req.content)
if "approve" in request.POST:
groups = []
if "groups" in request.POST:
group_ids = request.POST.getlist("groups")
groups = Group.objects.filter(id__in=group_ids)
announcement = Announcement.objects.create(
title=req.title,
content=req.content,
author=req.author,
user=req.user,
expiration_date=req.expiration_date,
notify_post=admin_form.cleaned_data["notify_post"],
notify_email_all=admin_form.cleaned_data["notify_email_all"],
)
for g in groups:
announcement.groups.add(g)
announcement.save()
req.posted = announcement
req.posted_by = request.user
req.save()
announcement_approved_hook(request, announcement, req)
announcement_posted_hook(request, announcement)
messages.success(request, "Successfully approved announcement request. It has been posted.")
logger.info("Admin %s approved announcement: %s (%s)", request.user, announcement, announcement.id)
else:
req.rejected = True
req.rejected_by = request.user
req.save()
messages.success(request, "You did not approve this request. It will be hidden.")
logger.info("Admin %s rejected announcement: %s (%s)", request.user, req.title, req.id)
return redirect("index")
form = AnnouncementRequestForm(instance=req)
admin_form = AnnouncementAdminForm()
all_groups = Group.objects.all()
context = {"form": form, "admin_form": admin_form, "req": req, "admin_approve": True, "all_groups": all_groups}
return render(request, "announcements/approve.html", context)
@announcements_admin_required
@deny_restricted
def admin_request_status_view(request):
prefetch_fields = ["user", "teachers_requested", "teachers_approved", "posted", "posted_by", "rejected_by"]
all_waiting = AnnouncementRequest.objects.filter(posted=None, rejected=False).this_year().prefetch_related(*prefetch_fields)
awaiting_teacher = all_waiting.filter(teachers_approved__isnull=True)
awaiting_approval = all_waiting.filter(teachers_approved__isnull=False)
approved = AnnouncementRequest.objects.exclude(posted=None).this_year().prefetch_related(*prefetch_fields)
rejected = AnnouncementRequest.objects.filter(rejected=True).this_year().prefetch_related(*prefetch_fields)
context = {"awaiting_teacher": awaiting_teacher, "awaiting_approval": awaiting_approval, "approved": approved, "rejected": rejected}
return render(request, "announcements/request_status.html", context)
@announcements_admin_required
@deny_restricted
def add_announcement_view(request):
"""Add an announcement."""
if request.method == "POST":
form = AnnouncementForm(request.POST)
if form.is_valid():
obj = form.save()
obj.user = request.user
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
announcement_posted_hook(request, obj)
messages.success(request, "Successfully added announcement.")
logger.info("Admin %s added announcement: %s (%s)", request.user, obj, obj.id)
return redirect("index")
else:
messages.error(request, "Error adding announcement")
else:
form = AnnouncementForm()
return render(request, "announcements/add_modify.html", {"form": form, "action": "add"})
@login_required
@deny_restricted
def view_announcement_view(request, announcement_id):
"""View an announcement.
announcement_id: announcement id
"""
announcement = get_object_or_404(Announcement, id=announcement_id)
if not announcement.is_visible(request.user):
messages.error(request, "You don't have permission to view this announcement.")
return redirect("/")
return render(request, "announcements/view.html", {"announcement": announcement})
@announcements_admin_required
@deny_restricted
def modify_announcement_view(request, announcement_id=None):
"""Modify an announcement.
announcement_id: announcement id
"""
if request.method == "POST":
announcement = get_object_or_404(Announcement, id=announcement_id)
form = AnnouncementEditForm(request.POST, instance=announcement)
if form.is_valid():
obj = form.save()
if form.cleaned_data.get("update_added_date"):
obj.added = timezone.now()
if form.cleaned_data.get("notify_post_resend") or form.cleaned_data.get("notify_email_all_resend"):
obj.notify_post = form.cleaned_data["notify_post_resend"]
obj.notify_email_all = form.cleaned_data["notify_email_all_resend"]
obj.save()
announcement_posted_hook(request, obj)
# SAFE HTML
obj.content = safe_html(obj.content)
obj.save()
messages.success(request, "Successfully modified announcement.")
logger.info("Admin %s modified announcement: %s (%s)", request.user, announcement, announcement.id)
return redirect("index")
else:
messages.error(request, "Error modifying announcement")
else:
announcement = get_object_or_404(Announcement, id=announcement_id)
form = AnnouncementEditForm(instance=announcement)
context = {"form": form, "action": "modify", "id": announcement_id, "announcement": announcement}
return render(request, "announcements/add_modify.html", context)
@deny_restricted
def delete_announcement_view(request, announcement_id):
"""Delete an announcement.
announcement_id: announcement id
"""
announcement = get_object_or_404(Announcement, id=announcement_id)
if not (request.user.is_announcements_admin or announcement.is_club_announcement and announcement.can_modify(request.user)):
messages.error(request, "You do not have permission to delete this announcement.")
return redirect("index")
if request.method == "POST":
if request.POST.get("full_delete", False) and request.user.is_announcements_admin:
announcement.delete()
messages.success(request, "Successfully deleted announcement.")
logger.info("Admin %s deleted announcement: %s (%s)", request.user, announcement, announcement.id)
else:
announcement.expiration_date = timezone.localtime()
announcement.save()
messages.success(request, "Successfully expired announcement.")
logger.info("%s expired announcement: %s (%s)", request.user, announcement, announcement.id)
if announcement.is_club_announcement:
return redirect("club_announcements")
return redirect("index")
return render(request, "announcements/delete.html", {"announcement": announcement})
@login_required
@deny_restricted
@transaction.atomic
def show_announcement_view(request):
"""Unhide an announcement that was hidden by the logged-in user.
announcements_hidden in the user model is the related_name for
"users_hidden" in the announcement model.
"""
if request.method == "POST":
announcement_id = request.POST.get("announcement_id")
if announcement_id:
announcement = Announcement.objects.get(id=announcement_id)
announcement.user_map.users_hidden.remove(request.user)
announcement.user_map.save()
return http.HttpResponse("Unhidden")
raise http.Http404
else:
return http.HttpResponseNotAllowed(["POST"], "HTTP 405: METHOD NOT ALLOWED")
@login_required
@deny_restricted
@transaction.atomic
def hide_announcement_view(request):
"""Hide an announcement for the logged-in user.
announcements_hidden in the user model is the related_name for
"users_hidden" in the announcement model.
"""
if request.method == "POST":
announcement_id = request.POST.get("announcement_id")
if announcement_id:
announcement = Announcement.objects.get(id=announcement_id)
try:
announcement.user_map.users_hidden.add(request.user)
announcement.user_map.save()
except IntegrityError:
logger.warning("Duplicate value when hiding announcement %d for %s.", announcement_id, request.user.username)
return http.HttpResponse("Hidden")
raise http.Http404
else:
return http.HttpResponseNotAllowed(["POST"], "HTTP 405: METHOD NOT ALLOWED")