Skip to content

Commit

Permalink
feat: Add session mails and notify endpoint (#7198)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamareebjamal authored Aug 9, 2020
1 parent f609453 commit 761bb3e
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 15 deletions.
7 changes: 6 additions & 1 deletion app/api/helpers/mail.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import logging
from datetime import datetime
from typing import Dict

from flask import current_app

Expand Down Expand Up @@ -171,7 +172,7 @@ def send_email_new_session(email, event_name, link):
)


def send_email_session_state_change(email, session):
def send_email_session_state_change(email, session, mail_override: Dict[str, str] = None):
"""email for new session"""
event = session.event

Expand All @@ -195,6 +196,10 @@ def send_email_session_state_change(email, session):

try:
mail = MAILS[SESSION_STATE_CHANGE][session.state]
if mail_override:
mail = mail.copy()
mail['subject'] = mail_override.get('subject') or mail['subject']
mail['message'] = mail_override.get('message') or mail['message']
except KeyError:
logger.error('No mail found for session state change: ' + session.state)
return
Expand Down
14 changes: 8 additions & 6 deletions app/api/helpers/permission_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,19 @@ def is_speaker_for_session(view, view_args, view_kwargs, *args, **kwargs):
Allows admin and super admin access to any resource irrespective of id.
Otherwise the user can only access his/her resource.
"""
not_found = NotFoundError({'parameter': 'id'}, 'Session not found.')
try:
session = Session.query.filter(Session.id == view_kwargs['id']).one()
except NoResultFound:
raise not_found

user = current_user
if user.is_admin or user.is_super_admin:
return view(*view_args, **view_kwargs)

if user.is_staff:
return view(*view_args, **view_kwargs)

try:
session = Session.query.filter(Session.id == view_kwargs['id']).one()
except NoResultFound:
raise NotFoundError({'parameter': 'id'}, 'Session not found.')
if session.deleted_at is not None:
raise not_found

if user.has_event_access(session.event_id):
return view(*view_args, **view_kwargs)
Expand Down
1 change: 0 additions & 1 deletion app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@
SessionListPost,
SessionRelationshipOptional,
SessionRelationshipRequired,
get_session_states,
)
from app.api.settings import SettingDetail
from app.api.social_links import (
Expand Down
15 changes: 14 additions & 1 deletion app/api/schema/sessions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime

from flask_rest_jsonapi.exceptions import ObjectNotFound
from marshmallow import validate, validates_schema
from marshmallow import Schema, validate, validates_schema
from marshmallow_jsonapi import fields
from marshmallow_jsonapi.flask import Relationship
from sqlalchemy.orm.exc import NoResultFound
Expand All @@ -12,6 +12,7 @@
from app.api.helpers.utilities import dasherize
from app.api.helpers.validations import validate_complex_fields_json
from app.api.schema.base import SoftDeletionSchema
from app.models.helpers.versioning import clean_html
from app.models.session import Session
from utils.common import use_defaults

Expand Down Expand Up @@ -176,3 +177,15 @@ def validate_fields(self, data, original_data):
schema='UserSchemaPublic',
type_='user',
)


# Used for customization of email notification subject and message body
class SessionNotifySchema(Schema):
subject = fields.Str(required=False, validate=validate.Length(max=250))
message = fields.Str(required=False, validate=validate.Length(max=5000))

@validates_schema
def validate_fields(self, data):
if not data:
return
data['message'] = clean_html(data.get('message'), allow_link=True)
34 changes: 29 additions & 5 deletions app/api/sessions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Dict

from flask import Blueprint, g, jsonify, request
from flask_jwt_extended import current_user
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
Expand All @@ -7,7 +9,7 @@
from app.api.events import Event
from app.api.helpers.custom_forms import validate_custom_form_constraints_request
from app.api.helpers.db import get_count, safe_query, safe_query_kwargs, save_to_db
from app.api.helpers.errors import ForbiddenError
from app.api.helpers.errors import ForbiddenError, UnprocessableEntityError
from app.api.helpers.files import make_frontend_url
from app.api.helpers.mail import send_email_new_session, send_email_session_state_change
from app.api.helpers.notification import (
Expand All @@ -17,8 +19,9 @@
from app.api.helpers.permission_manager import has_access
from app.api.helpers.query import event_query
from app.api.helpers.speaker import can_edit_after_cfs_ends
from app.api.helpers.system_mails import MAILS, SESSION_STATE_CHANGE
from app.api.helpers.utilities import require_relationship
from app.api.schema.sessions import SessionSchema
from app.api.schema.sessions import SessionNotifySchema, SessionSchema
from app.models import db
from app.models.microlocation import Microlocation
from app.models.session import Session
Expand Down Expand Up @@ -221,6 +224,11 @@ def get_session_states():
return jsonify(SESSION_STATE_DICT)


@sessions_blueprint.route('/mails')
def get_session_state_change_mails():
return jsonify(MAILS[SESSION_STATE_CHANGE])


class SessionDetail(ResourceDetail):
"""
Session detail by id
Expand Down Expand Up @@ -338,28 +346,44 @@ def after_update_object(self, session, data, view_kwargs):
}


def notify_for_session(session):
def notify_for_session(session, mail_override: Dict[str, str] = None):
event = session.event
frontend_url = get_settings()['frontend_url']
link = "{}/events/{}/sessions/{}".format(frontend_url, event.identifier, session.id)
# Email for speaker
speakers = session.speakers
for speaker in speakers:
if not speaker.is_email_overridden:
send_email_session_state_change(speaker.email, session)
send_email_session_state_change(speaker.email, session, mail_override)
send_notif_session_state_change(
speaker.user, session.title, session.state, link, session.id
)

# Email for owner
if session.event.get_owner():
owner = session.event.get_owner()
send_email_session_state_change(owner.email, session)
send_email_session_state_change(owner.email, session, mail_override)
send_notif_session_state_change(
owner, session.title, session.state, link, session.id
)


@sessions_blueprint.route('/<int:id>/notify', methods=['POST'])
@api.has_permission('is_speaker_for_session', methods="POST")
def notify_session(id):
session = Session.query.filter_by(deleted_at=None, id=id).first_or_404()

data, errors = SessionNotifySchema().load(request.json)
if errors:
raise UnprocessableEntityError(
{'pointer': '/data', 'errors': errors}, 'Data in incorrect format'
)

notify_for_session(session, data)

return jsonify({'success': True})


class SessionRelationshipRequired(ResourceRelationship):
"""
Session Relationship
Expand Down
6 changes: 5 additions & 1 deletion app/models/helpers/versioning.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def clean_up_string(target_string):
return target_string


def clean_html(html):
def clean_html(html, allow_link=False):
if html is None:
return None
tags = [
Expand All @@ -39,8 +39,12 @@ def clean_html(html):
'ol',
'li',
'strike',
'br',
]
attrs = {'*': ['style']}
if allow_link:
tags.append('a')
attrs['a'] = ['href']
styles = ['text-align', 'font-weight', 'text-decoration']
cleaned = bleach.clean(html, tags=tags, attributes=attrs, styles=styles, strip=True)
return bleach.linkify(
Expand Down
36 changes: 36 additions & 0 deletions docs/api/blueprint/session/sessions.apib
Original file line number Diff line number Diff line change
Expand Up @@ -920,3 +920,39 @@ Get possible session state transitions. **(Public)**
"withdrawn": {}
}
}


## Sessions State Change Mails [/v1/sessions/mails]

### Sessions State Change Mails [GET]
Get mail subject and message sent on changing session state. **(Public)**

+ Response 200 (application/json)

{
"accepted": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>Your session status for the submission {session_name} for {event_name} was changed to \"Accepted\". Congratulations!<br/><br/>Your proposal will be scheduled by the event organizers and review team. Please (re)confirm your participation with the organizers of the event, if required.<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Accepted! Congratulations Your submission for {event_name} titled {session_name} has been Accepted"
},
"canceled": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>Your session status for the submission {session_name} for {event_name} was changed to \"Canceled\".<br/><br/>The status change was done by event organizers. If there are questions about this change please contact the organizers.<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Canceled! Your submission for {event_name} titled {session_name} has been Canceled"
},
"confirmed": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>Your session status for the submission {session_name} for {event_name} was changed to \"Confirmed\". Congratulations!<br/><br/>Your proposal will be scheduled by the event organizers and review team. Please inform the event organizers in case there are any changes to your participation.<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Confirmed! Congratulations Your submission for {event_name} titled {session_name} has been Confirmed"
},
"pending": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>We have received your submission {session_name} for {event_name}<br/><br/>Your proposal will be reviewed by the event organizers and review team. The current status of your session is now \"Pending\".<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Your speaker submission for {event_name} titled {session_name}"
},
"recipient": "Speaker",
"rejected": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>Unfortunately your submission {session_name} for {event_name} was not accepted. Your session status was changed to \"Rejected\".<br/><br/>The status change was done by event organizers. If there are questions about this change please contact the organizers.<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Not Accepted. Your submission for {event_name} titled {session_name} was not accepted"
},
"withdrawn": {
"message": "Hello,<br/><br/>This is an automatic message from {app_name}.<br/><br/>Your session status for the submission {session_name} for {event_name} was changed to \"Withdrawn\".<br/><br/>The status change was done by event organizers. If there are questions about this change please contact the organizers.<br/><br/>You can also check the status and details of your submission on the session page {session_link}. You need to be logged in to view it.<br/><br/>More details about the event are on the event page at {event_link}.<br/><br/>Thank you.<br/><a href='{frontend_link}'>{app_name}</a>",
"subject": "Withdrawn! Your submission for {event_name} titled {session_name} has been Withdrawn"
}
}

0 comments on commit 761bb3e

Please sign in to comment.