Source code for meetings.views

import datetime

from django.contrib.auth.decorators import login_required, permission_required
from django.core.exceptions import PermissionDenied
from django.core.paginator import InvalidPage, Paginator
from django.db.models.aggregates import Count
from django.views.generic.edit import DeleteView
from django.forms.models import inlineformset_factory
from django.http import HttpResponseRedirect, HttpResponse
from django.http.response import Http404
from django.shortcuts import get_object_or_404, render
from django.urls.base import reverse
from django.utils import timezone
from django.conf import settings
from email.mime.base import MIMEBase
from email.encoders import encode_base64
from helpers.util import curry_class

from helpers.mixins import HasPermMixin, LoginRequiredMixin, SetFormMsgMixin
from data.views import serve_file
from emails.generators import generate_notice_cc_email, generate_notice_email, DefaultLNLEmailGenerator
from events.forms import CCIForm
from events.models import BaseEvent, EventCCInstance
from events.cal import generate_ics, EventAttendee
from accounts.models import UserPreferences

from .forms import (AnnounceCCSendForm, AnnounceSendForm, MeetingAdditionForm,
                    MtgAttachmentEditForm)
from .models import AnnounceSend, Meeting, MtgAttachment


[docs]@login_required def download_att(request, mtg_id, att_id): """ Download a meeting attachment :param mtg_id: The primary key value of the meeting :param att_id: The primary key value of the attachment """ mtg = get_object_or_404(Meeting, pk=mtg_id) if not request.user.has_perm('meetings.view_mtg', mtg): raise PermissionDenied att = get_object_or_404(MtgAttachment, pk=att_id) if not att.meeting or att.meeting.pk != mtg.pk: raise PermissionDenied elif att.private and not request.user.has_perm('meetings.view_mtg_closed', mtg): raise PermissionDenied return serve_file(request, att.file)
[docs]@login_required def rm_att(request, mtg_id, att_id): """ Remove an attachment from a meeting :param mtg_id: The primary key value of the meeting :param att_id: The primary key value of the attachment """ mtg = get_object_or_404(Meeting, pk=mtg_id) if not request.user.has_perm('meetings.edit_mtg', mtg): raise PermissionDenied att = get_object_or_404(MtgAttachment, pk=att_id) if not att.meeting or att.meeting.pk != mtg.pk: raise PermissionDenied mtg.attachments.remove(att) return HttpResponseRedirect(reverse('meetings:detail', args=(mtg.pk,)))
[docs]@login_required def modify_att(request, mtg_id, att_id): """ Update an attachment :param mtg_id: The primary key value of the meeting :param att_id: The primary key value of the attachment """ context = {} mtg_perms = ('meetings.edit_mtg',) att_perms = [] context['msg'] = "Update Attachment" mtg = get_object_or_404(Meeting, pk=mtg_id) att = get_object_or_404(MtgAttachment, pk=att_id) if not (request.user.has_perms(mtg_perms, mtg) and request.user.has_perms(att_perms, att) and att.meeting and att.meeting.pk == mtg.pk): raise PermissionDenied if request.method == 'POST': form = MtgAttachmentEditForm(instance=att, data=request.POST, files=request.FILES) if form.is_valid(): form.save() url = reverse('meetings:detail', args=(mtg_id,)) + "#minutes" return HttpResponseRedirect(url) else: form = MtgAttachmentEditForm(instance=att) context['form'] = form return render(request, 'form_crispy_meetings.html', context)
[docs]@login_required def viewattendance(request, mtg_id): """ View event details """ context = {} perms = ('meetings.view_mtg_attendance',) try: m = Meeting.objects.prefetch_related('attendance').get(pk=mtg_id) except Meeting.DoesNotExist: raise Http404(0) if not (request.user.has_perms(perms) or request.user.has_perms(perms, m)): raise PermissionDenied context['m'] = m now = m.datetime yest = now + datetime.timedelta(days=-1) morethanaweek = now + datetime.timedelta(days=7, hours=12) upcoming = BaseEvent.objects.filter(datetime_start__gte=yest, datetime_start__lte=morethanaweek) context['events'] = upcoming.prefetch_related('ccinstances__crew_chief') \ .prefetch_related('ccinstances__service') lessthantwoweeks = morethanaweek + datetime.timedelta(days=7) future = BaseEvent.objects.filter(datetime_start__gte=morethanaweek, datetime_start__lte=lessthantwoweeks).order_by( 'datetime_start').prefetch_related('ccinstances__crew_chief') \ .prefetch_related('ccinstances__service') context['future'] = future return render(request, 'meeting_view.html', context)
[docs]@login_required def updateevent(request, mtg_id, event_id): """ Update crew chief assignments for an event :param mtg_id: The primary key value of the meeting (redirects to meeting detail page) :param event_id: The primary key value of the event (pre-2019 events only) """ context = {} perms = ('meetings.edit_mtg',) event = get_object_or_404(BaseEvent, pk=event_id) if not (request.user.has_perms(perms) or request.user.has_perms(perms, event)): raise PermissionDenied context['event'] = event.event_name cc_formset = inlineformset_factory(BaseEvent, EventCCInstance, extra=3, exclude=[]) cc_formset.form = curry_class(CCIForm, event=event) if request.method == 'POST': formset = cc_formset(request.POST, instance=event, prefix="main") if formset.is_valid(): formset.save() url = reverse('meetings:detail', args=(mtg_id,)) + "#events" return HttpResponseRedirect(url) else: formset = cc_formset(instance=event, prefix="main") context['formset'] = formset return render(request, 'formset_crispy_helpers.html', context)
[docs]@login_required def editattendance(request, mtg_id): """ Update event details """ context = {} perms = ('meetings.edit_mtg',) context['msg'] = "Edit Meeting" m = get_object_or_404(Meeting, pk=mtg_id) if not (request.user.has_perms(perms) or request.user.has_perms(perms, m)): raise PermissionDenied if request.method == 'POST': form = MeetingAdditionForm(data=request.POST, files=request.FILES, instance=m, request_user=request.user) if form.is_valid(): for each in form.cleaned_data.get('attachments', []): MtgAttachment.objects.create(file=each, name=each.name, author=request.user, meeting=m, private=False) for each in form.cleaned_data.get('attachments_private', []): MtgAttachment.objects.create(file=each, name=each.name, author=request.user, meeting=m, private=True) m = form.save() if 'location' in form.changed_data or 'datetime' in form.changed_data: send_invite(m, is_update=True) url = reverse('meetings:detail', args=(m.id,)) + "#attendance" return HttpResponseRedirect(url) else: form = MeetingAdditionForm(instance=m, request_user=request.user) context['form'] = form return render(request, 'form_crispy_meetings.html', context)
[docs]@login_required @permission_required('meetings.list_mtgs', raise_exception=True) def listattendance(request, page=1): """ List all meetings """ context = {} mtgs = Meeting.objects \ .select_related('meeting_type') \ .annotate(num_attendsees=Count('attendance')) past_mtgs = mtgs.filter(datetime__lte=timezone.now()) \ .order_by('-datetime') future_mtgs = mtgs.filter(datetime__gte=timezone.now()) \ .order_by('datetime') paginated = Paginator(past_mtgs, 10) try: past_mtgs = paginated.page(page) except InvalidPage: past_mtgs = paginated.page(1) paginated = Paginator(future_mtgs, 10) try: future_mtgs = paginated.page(page) except InvalidPage: future_mtgs = paginated.page(1) context['lists'] = [("Past Meetings", past_mtgs), ("Future Meetings", future_mtgs)] return render(request, 'meeting_list.html', context)
[docs]@login_required @permission_required('meetings.create_mtg', raise_exception=True) def newattendance(request): """ Create a new meeting """ context = {} if request.method == 'POST': form = MeetingAdditionForm(request.user, request.POST, request.FILES) if form.is_valid(): mtg = form.save() send_invite(mtg) for attachment in form.cleaned_data.get('attachments', []): MtgAttachment.objects.create(file=attachment, name=attachment.name, author=request.user, private=False, meeting=mtg) for attachment in form.cleaned_data.get('attachments_private', []): MtgAttachment.objects.create(file=attachment, name=attachment.name, author=request.user, private=True, meeting=mtg) return HttpResponseRedirect(reverse('meetings:detail', args=(mtg.id,))) else: form = MeetingAdditionForm(request.user) context['form'] = form context['msg'] = "New Meeting" return render(request, 'form_crispy_meetings.html', context)
[docs]class DeleteMeeting(SetFormMsgMixin, LoginRequiredMixin, HasPermMixin, DeleteView): """ Delete a meeting """ model = Meeting template_name = "form_delete_cbv.html" msg = "Delete Meeting" perms = 'meetings.edit_mtg' pk_url_kwarg = "mtg_id"
[docs] def get_success_url(self): return reverse("meetings:list")
[docs] def delete(self, request, *args, **kwargs): self.object = self.get_object() success_url = self.get_success_url() send_invite(self.object, is_cancellation=True) self.object.delete() return HttpResponseRedirect(success_url)
[docs]@login_required def mknotice(request, mtg_id): """ Send a meeting notice """ context = {} perms = ('meetings.send_mtg_notice',) meeting = get_object_or_404(Meeting, pk=mtg_id) if not (request.user.has_perms(perms) or request.user.has_perms(perms, meeting)): raise PermissionDenied if request.method == 'POST': form = AnnounceSendForm(meeting, request.POST) if form.is_valid(): notice = form.save() email = generate_notice_email(notice) # Generate calendar invite invite_filename = 'meeting.ics' invite = MIMEBase('text', "calendar", method="PUBLISH", name=invite_filename) invite.set_payload(generate_ics([meeting], None)) encode_base64(invite) invite.add_header('Content-Description', 'Add to Calendar') invite.add_header('Content-Class', "urn:content-classes:calendarmessage") invite.add_header('Filename', invite_filename) invite.add_header('Path', invite_filename) email.attach(invite) res = email.send() if res == 1: success = True else: success = False AnnounceSend.objects.create(announce=notice, sent_success=success) url = reverse('meetings:detail', args=(meeting.id,)) + "#emails" return HttpResponseRedirect(url) else: form = AnnounceSendForm(meeting) context['form'] = form context['msg'] = "New Meeting Notice" return render(request, 'form_crispy_meetings.html', context)
[docs]@login_required def mkccnotice(request, mtg_id): """ Send a CC meeting notice """ context = {} perms = ('meetings.send_mtg_notice',) meeting = get_object_or_404(Meeting, pk=mtg_id) if not (request.user.has_perms(perms) or request.user.has_perms(perms, meeting)): raise PermissionDenied if request.method == 'POST': form = AnnounceCCSendForm(meeting, request.POST) if form.is_valid(): notice = form.save() email = generate_notice_cc_email(notice) res = email.send() if res == 1: notice.sent_success = True else: notice.sent_success = False notice.save() url = reverse('meetings:detail', args=(meeting.id,)) + "#emails" return HttpResponseRedirect(url) else: form = AnnounceCCSendForm(meeting) context['form'] = form context['msg'] = "CC Meeting Notice" return render(request, 'form_crispy_meetings.html', context)
[docs]@login_required def download_invite(request, mtg_id): """ Generate and download an ics file """ meeting = get_object_or_404(Meeting, pk=mtg_id) invite = generate_ics([meeting], None) response = HttpResponse(invite, content_type="text/calendar") response['Content-Disposition'] = "attachment; filename=invite.ics" return response
[docs]def send_invite(meeting, is_update=False, is_cancellation=False): """ Generate and send an interactive calendar invite for a meeting (separate from a meeting notice). :param meeting: The meeting to include in the invite :param is_update: Set to True if the meeting details have just been updated :param is_cancellation: Set to True if the meeting has been cancelled """ subscribers = [] recipients = [] user_prefs = UserPreferences.objects.filter(meeting_invite_subscriptions__in=[meeting.meeting_type]) for record in user_prefs: if record.meeting_invites: subscribers.append(record.user) recipients.append(record.user.email) attendees = [] for user in meeting.attendance.all(): attendees.append(EventAttendee(meeting, user)) for user in subscribers: if user not in meeting.attendance.all(): attendees.append(EventAttendee(meeting, user, False)) agenda = "You're receiving this message because you signed up to receive calendar invites for certain LNL " \ "meetings. To opt-out, visit https://lnl.wpi.edu" + reverse("accounts:preferences") if meeting.agenda: agenda = "# Agenda\n" + meeting.agenda # Generate calendar invite invite_filename = 'meeting.ics' invite = MIMEBase('text', "calendar", method="REQUEST", name=invite_filename) invite.set_payload(generate_ics([meeting], attendees, True, is_cancellation)) encode_base64(invite) invite.add_header('Content-Description', 'Add to Calendar') invite.add_header('Content-Class', "urn:content-classes:calendarmessage") invite.add_header('Filename', invite_filename) invite.add_header('Path', invite_filename) subject = meeting.name if is_update: subject = "Updated: " + meeting.name if is_cancellation: subject = "Canceled: " + meeting.name email = DefaultLNLEmailGenerator(subject, to_emails=settings.EMAIL_FROM_NOREPLY, bcc=recipients, from_email=settings.EMAIL_FROM_NOREPLY, template_basename="emails/email_basic", body=agenda, attachments=[invite]) email.send()