An email is now sent when a new speaker- or eventproposal is created and
when any such is updated.
This commit is contained in:
Stephan Telling 2017-07-11 22:02:19 +02:00
parent a67f9ee4a5
commit fcd85f680a
No known key found for this signature in database
GPG key ID: D4892289F36ADA9B
12 changed files with 247 additions and 14 deletions

102
src/program/email.py Normal file
View file

@ -0,0 +1,102 @@
from django.core.exceptions import ObjectDoesNotExist
from utils.email import add_outgoing_email
from teams.models import Team
import logging
logger = logging.getLogger("bornhack.%s" % __name__)
def add_new_speakerproposal_email(speakerproposal):
formatdict = {
'proposal': speakerproposal
}
try:
content_team = Team.objects.get(
camp=speakerproposal.camp, name='Content'
)
return add_outgoing_email(
text_template='emails/new_speakerproposal.txt',
html_template='emails/new_speakerproposal.html',
to_recipients=content_team.mailing_list,
formatdict=formatdict,
subject='New speaker proposal for {}'.format(
speakerproposal.camp.title
)
)
except ObjectDoesNotExist as e:
logger.info('There is no team with name Content: {}'.format(e))
return False
def add_new_eventproposal_email(eventproposal):
formatdict = {
'proposal': eventproposal
}
try:
content_team = Team.objects.get(
camp=eventproposal.camp, name='Content'
)
return add_outgoing_email(
text_template='emails/new_eventproposal.txt',
html_template='emails/new_eventproposal.html',
to_recipients=content_team.mailing_list,
formatdict=formatdict,
subject='New event proposal for {}'.format(
eventproposal.camp.title
)
)
except ObjectDoesNotExist as e:
logger.info('There is no team with name Content: {}'.format(e))
return False
def add_speakerproposal_updated_email(speakerproposal):
formatdict = {
'proposal': speakerproposal
}
try:
content_team = Team.objects.get(
camp=speakerproposal.camp, name='Content'
)
return add_outgoing_email(
text_template='emails/update_speakerproposal.txt',
html_template='emails/update_speakerproposal.html',
to_recipients=content_team.mailing_list,
formatdict=formatdict,
subject='Updated speaker proposal for {}'.format(
speakerproposal.camp.title
)
)
except ObjectDoesNotExist as e:
logger.info('There is no team with name Content: {}'.format(e))
return False
def add_eventproposal_updated_email(eventproposal):
formatdict = {
'proposal': eventproposal
}
try:
content_team = Team.objects.get(
camp=eventproposal.camp, name='Content'
)
return add_outgoing_email(
text_template='emails/update_eventproposal.txt',
html_template='emails/update_eventproposal.html',
to_recipients=content_team.mailing_list,
formatdict=formatdict,
subject='New event proposal for {}'.format(
eventproposal.camp.title
)
)
except ObjectDoesNotExist as e:
logger.info('There is no team with name Content: {}'.format(e))
return False

View file

@ -1,10 +1,15 @@
import uuid import uuid
import os import os
import icalendar
import CommonMark
import logging
from datetime import timedelta from datetime import timedelta
from django.contrib.postgres.fields import DateTimeRangeField from django.contrib.postgres.fields import DateTimeRangeField
from django.contrib import messages from django.contrib import messages
from django.db import models from django.db import models
from django.dispatch import receiver
from django.utils.text import slugify from django.utils.text import slugify
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -13,11 +18,11 @@ from django.core.files.storage import FileSystemStorage
from django.urls import reverse from django.urls import reverse
from django.apps import apps from django.apps import apps
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.db.models.signals import post_save
import icalendar
import CommonMark
from utils.models import CreatedUpdatedModel, CampRelatedModel from utils.models import CreatedUpdatedModel, CampRelatedModel
from .email import add_new_speakerproposal_email, add_new_eventproposal_email
logger = logging.getLogger("bornhack.%s" % __name__)
class CustomUrlStorage(FileSystemStorage): class CustomUrlStorage(FileSystemStorage):
@ -80,12 +85,14 @@ class UserSubmittedModel(CampRelatedModel):
PROPOSAL_PENDING = 'pending' PROPOSAL_PENDING = 'pending'
PROPOSAL_APPROVED = 'approved' PROPOSAL_APPROVED = 'approved'
PROPOSAL_REJECTED = 'rejected' PROPOSAL_REJECTED = 'rejected'
PROPOSAL_MODIFIED_AFTER_APPROVAL = 'modified after approval'
PROPOSAL_STATUSES = [ PROPOSAL_STATUSES = [
PROPOSAL_DRAFT, PROPOSAL_DRAFT,
PROPOSAL_PENDING, PROPOSAL_PENDING,
PROPOSAL_APPROVED, PROPOSAL_APPROVED,
PROPOSAL_REJECTED PROPOSAL_REJECTED,
PROPOSAL_MODIFIED_AFTER_APPROVAL
] ]
PROPOSAL_STATUS_CHOICES = [ PROPOSAL_STATUS_CHOICES = [
@ -93,6 +100,7 @@ class UserSubmittedModel(CampRelatedModel):
(PROPOSAL_PENDING, 'Pending approval'), (PROPOSAL_PENDING, 'Pending approval'),
(PROPOSAL_APPROVED, 'Approved'), (PROPOSAL_APPROVED, 'Approved'),
(PROPOSAL_REJECTED, 'Rejected'), (PROPOSAL_REJECTED, 'Rejected'),
(PROPOSAL_MODIFIED_AFTER_APPROVAL, 'Modified after approval'),
] ]
proposal_status = models.CharField( proposal_status = models.CharField(
@ -265,6 +273,21 @@ class EventProposal(UserSubmittedModel):
self.save() self.save()
@receiver(post_save, sender=EventProposal)
@receiver(post_save, sender=SpeakerProposal)
def notify_content_team(sender, created, instance, **kwargs):
if created and isinstance(instance, SpeakerProposal):
if not add_new_speakerproposal_email(instance):
logger.error(
'Error adding speaker proposal email to outgoing queue for {}'.format(instance)
)
if created and isinstance(instance, EventProposal):
if not add_new_eventproposal_email(instance):
logger.error(
'Error adding event proposal email to outgoing queue for {}'.format(instance)
)
############################################################################### ###############################################################################

View file

@ -0,0 +1,10 @@
Hello!<br>
<br>
Event {{ proposal.name }} was just submitted as a new proposal for {{ proposal.camp }}.
<br>
<br>
More info <a href="https://bornhack.dk/admin/program/eventproposal/{{ proposal.uuid }}/change/">here</a>.
<br>
Best regards,<br>
<br>
The BornHack Team<br>

View file

@ -0,0 +1,10 @@
Hello!
Event {{ proposal.name }} was just submitted as a new proposal for {{ proposal.camp }}.
More info: https://bornhack.dk/admin/program/speakerproposal/{{ proposal.uuid }}/change/
Best regards,
The BornHack Team

View file

@ -0,0 +1,10 @@
Hello!<br>
<br>
Speaker {{ proposal.name }} was just submitted as a new proposal for {{ proposal.camp }}.
<br>
<br>
More info <a href="https://bornhack.dk/admin/program/speakerproposal/{{ proposal.uuid }}/change/">here</a>.
<br>
Best regards,<br>
<br>
The BornHack Team<br>

View file

@ -0,0 +1,10 @@
Hello!
Speaker {{ proposal.name }} was just submitted as a new proposal for {{ proposal.camp }}.
More info: https://bornhack.dk/admin/program/speakerproposal/{{ proposal.uuid }}/change/
Best regards,
The BornHack Team

View file

@ -0,0 +1,10 @@
Hello!<br>
<br>
Event {{ proposal.name }} for {{ proposal.camp }} was just updated!
<br>
<br>
More info <a href="https://bornhack.dk/admin/program/eventproposal/{{ proposal.uuid }}/change/">here</a>.
<br>
Best regards,<br>
<br>
The BornHack Team<br>

View file

@ -0,0 +1,10 @@
Hello!
Event {{ proposal.name }} for {{ proposal.camp }} was just updated!
More info: https://bornhack.dk/admin/program/eventproposal/{{ proposal.uuid }}/change/
Best regards,
The BornHack Team

View file

@ -0,0 +1,10 @@
Hello!<br>
<br>
Speaker {{ proposal.name }} for {{ proposal.camp }} was just updated!
<br>
<br>
More info <a href="https://bornhack.dk/admin/program/speakerproposal/{{ proposal.uuid }}/change/">here</a>.
<br>
Best regards,<br>
<br>
The BornHack Team<br>

View file

@ -0,0 +1,10 @@
Hello!
Speaker {{ proposal.name }} was just submitted as a new speaker proposal for {{ proposal.camp }}.
More info: https://bornhack.dk/admin/program/speakerproposal/{{ proposal.uuid }}/change/
Best regards,
The BornHack Team

View file

@ -24,11 +24,7 @@ Proposals | {{ block.super }}
<td> <td>
<a href="{% url 'speakerproposal_detail' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Details</a> <a href="{% url 'speakerproposal_detail' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Details</a>
{% if not camp.read_only %} {% if not camp.read_only %}
{% if speakerproposal.proposal_status == "approved" %}
<a href="{% url 'speakerproposal_update' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs btn-disabled" disabled>Modify</a>
{% else %}
<a href="{% url 'speakerproposal_update' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Modify</a> <a href="{% url 'speakerproposal_update' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs">Modify</a>
{% endif %}
{% if speakerproposal.proposal_status == "pending" or speakerproposal.proposal_status == "approved" %} {% if speakerproposal.proposal_status == "pending" or speakerproposal.proposal_status == "approved" %}
<a href="{% url 'speakerproposal_submit' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs btn-disabled" disabled>Submit</a> <a href="{% url 'speakerproposal_submit' camp_slug=camp.slug pk=speakerproposal.uuid %}" class="btn btn-primary btn-xs btn-disabled" disabled>Submit</a>
{% else %} {% else %}

View file

@ -1,4 +1,6 @@
import datetime, os import datetime
import logging
import os
from django.views.generic import ListView, TemplateView, DetailView, View from django.views.generic import ListView, TemplateView, DetailView, View
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
@ -8,14 +10,26 @@ from django.http import Http404, HttpResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
import icalendar import icalendar
from camps.mixins import CampViewMixin from camps.mixins import CampViewMixin
from .mixins import CreateProposalMixin, EnsureUnapprovedProposalMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin, PictureViewMixin, EnsureCFSOpenMixin from .mixins import (
CreateProposalMixin,
EnsureUnapprovedProposalMixin,
EnsureUserOwnsProposalMixin,
EnsureWritableCampMixin,
PictureViewMixin,
EnsureCFSOpenMixin
)
from .email import (
add_speakerproposal_updated_email,
add_eventproposal_updated_email
)
from . import models from . import models
import logging
logger = logging.getLogger("bornhack.%s" % __name__)
############## ical calendar ######################################################## ############## ical calendar ########################################################
@ -84,7 +98,7 @@ class SpeakerProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateProposa
return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug}) return reverse('proposal_list', kwargs={'camp_slug': self.camp.slug})
class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnapprovedProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView): class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
model = models.SpeakerProposal model = models.SpeakerProposal
fields = ['name', 'biography', 'picture_small', 'picture_large'] fields = ['name', 'biography', 'picture_small', 'picture_large']
template_name = 'speakerproposal_form.html' template_name = 'speakerproposal_form.html'
@ -96,6 +110,15 @@ class SpeakerProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwn
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING: if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING:
messages.warning(self.request, "Your speaker proposal has been reverted to status draft. Please submit it again when you are ready.") messages.warning(self.request, "Your speaker proposal has been reverted to status draft. Please submit it again when you are ready.")
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
messages.warning(self.request, "Your speaker proposal has been set to modified after approval. Please await approval of the changes.")
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_MODIFIED_AFTER_APPROVAL
if not add_speakerproposal_updated_email(form.instance):
logger.error(
'Unable to add update email to queue for speaker: {}'.format(form.instance)
)
return super().form_valid(form) return super().form_valid(form)
@ -149,7 +172,7 @@ class EventProposalCreateView(LoginRequiredMixin, CampViewMixin, CreateProposalM
return context return context
class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureUnapprovedProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView): class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsProposalMixin, EnsureWritableCampMixin, EnsureCFSOpenMixin, UpdateView):
model = models.EventProposal model = models.EventProposal
fields = ['title', 'abstract', 'event_type', 'speakers'] fields = ['title', 'abstract', 'event_type', 'speakers']
template_name = 'eventproposal_form.html' template_name = 'eventproposal_form.html'
@ -161,6 +184,15 @@ class EventProposalUpdateView(LoginRequiredMixin, CampViewMixin, EnsureUserOwnsP
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING: if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_PENDING:
messages.warning(self.request, "Your event proposal has been reverted to status draft. Please submit it again when you are ready.") messages.warning(self.request, "Your event proposal has been reverted to status draft. Please submit it again when you are ready.")
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_DRAFT
if form.instance.proposal_status == models.UserSubmittedModel.PROPOSAL_APPROVED:
messages.warning(self.request, "Your event proposal has been set to status modified after approval. Please await approval of the changes.")
form.instance.proposal_status = models.UserSubmittedModel.PROPOSAL_MODIFIED_AFTER_APPROVAL
if not add_eventproposal_updated_email(form.instance):
logger.error(
'Unable to add update email to queue for event: {}'.format(form.instance)
)
return super().form_valid(form) return super().form_valid(form)