Merge org app email changes and access fix.
diff --git a/app/melange/logic/organization.py b/app/melange/logic/organization.py
index bba00f3..b080c26 100644
--- a/app/melange/logic/organization.py
+++ b/app/melange/logic/organization.py
@@ -149,13 +149,16 @@
@ndb.transactional
-def setStatus(organization, program, site, new_status, recipients=None):
+def setStatus(organization, program, site, program_messages,
+ new_status, recipients=None):
"""Sets status of the specified organization.
Args:
organization: Organization entity.
program: Program entity to which organization is assigned.
site: Site entity.
+ program_messages: ProgramMessages entity that holds the message
+ templates provided by the program admins.
new_status: New status of the organization. Must be one of
org_model.Status constants.
recipients: List of one or more recipients for the notification email.
@@ -172,11 +175,13 @@
if new_status == org_model.Status.ACCEPTED:
notification_context = (
notifications.OrganizationAcceptedContextProvider()
- .getContext(recipients, organization, program, site))
+ .getContext(recipients, organization, program,
+ site, program_messages))
elif new_status == org_model.Status.REJECTED:
notification_context = (
notifications.OrganizationRejectedContextProvider()
- .getContext(recipients, organization, program, site))
+ .getContext(recipients, organization, program,
+ site, program_messages))
sub_txn = mailer.getSpawnMailTaskTxn(
notification_context, parent=organization)
diff --git a/app/melange/mapreduce/apply_org_admission_decisions.py b/app/melange/mapreduce/apply_org_admission_decisions.py
index 0c9ef13..298fd77 100644
--- a/app/melange/mapreduce/apply_org_admission_decisions.py
+++ b/app/melange/mapreduce/apply_org_admission_decisions.py
@@ -21,6 +21,7 @@
from mapreduce import context as mapreduce_context
from melange.logic import organization as org_logic
+from melange.logic import profile as profile_logic
from melange.models import organization as org_model
from soc.logic import site as site_logic
@@ -45,6 +46,19 @@
# TODO(daniel): add email recipients, i.e. organization admins
site = site_logic.singleton()
+ org_admins = profile_logic.getOrgAdmins(organization.key)
+ recipients = [org_admin.contact.email for org_admin in org_admins]
+
+ # We are "prefetching" the ProgramMessages entity here instead of fetching
+ # it where it is required i.e. when the message templates are required
+ # to build the email message body. We do this because we perform the
+ # operation of fetching the ProgramMessages entity if it exists or create
+ # it if it doesn't in a Appengine regular "db" transation whereas rest
+ # of the updating of organization entities happen within an ndb transaction
+ # because Organization model is an ndb model and such cross API nested
+ # transactions are incompatible in Appengine.
+ program_messages = program.getProgramMessages()
+
@ndb.transactional
def updateOrganizationStatus():
"""Transactionally updates organization status."""
@@ -52,9 +66,11 @@
if organization.program.to_old_key() == program_key:
if organization.status == org_model.Status.PRE_ACCEPTED:
org_logic.setStatus(
- organization, program, site, org_model.Status.ACCEPTED)
+ organization, program, site, program_messages,
+ org_model.Status.ACCEPTED, recipients)
elif organization.status == org_model.Status.PRE_REJECTED:
org_logic.setStatus(
- organization, program, site, org_model.Status.REJECTED)
+ organization, program, site, program_messages,
+ org_model.Status.REJECTED, recipients)
updateOrganizationStatus()
diff --git a/app/soc/logic/helper/notifications.py b/app/soc/logic/helper/notifications.py
index 681d5e5..bf71b75 100644
--- a/app/soc/logic/helper/notifications.py
+++ b/app/soc/logic/helper/notifications.py
@@ -14,6 +14,7 @@
"""Helper functions for sending out notifications."""
+from django import template
from django.template import loader
from django.utils.translation import ugettext
@@ -39,10 +40,10 @@
'New Google Summer of Code Connection')
DEF_ACCEPTED_ORG = ugettext(
- '[%s] Your organization application has been accepted.')
+ '[%(org)s] Your organization application has been accepted.')
DEF_REJECTED_ORG = ugettext(
- '[%s] Your organization application has been rejected.')
+ '[%(org)s] Your organization application has been rejected.')
DEF_MENTOR_WELCOME_MAIL_SUBJECT = ugettext('Welcome to %s')
@@ -62,16 +63,12 @@
DEF_NEW_ANONYMOUS_CONNECTION_NOTIFICATION_TEMPLATE = \
'modules/gsoc/notification/anonymous_connection.html'
-DEF_ACCEPTED_ORG_TEMPLATE = \
- 'soc/notification/org_accepted.html'
-
-DEF_REJECTED_ORG_TEMPLATE = \
- 'soc/notification/org_rejected.html'
-
DEF_MENTOR_WELCOME_MAIL_TEMPLATE = \
'soc/notification/mentor_welcome_mail.html'
-def getContext(site, program, receivers, message_properties, subject, template):
+
+def _getContextCommon(site, program, receivers, message_properties,
+ subject, body):
"""Sends out a notification to the specified user.
Args:
@@ -80,7 +77,7 @@
receivers: Email addresses to which the notification should be sent.
message_properties: Message properties.
subject: Subject of notification email.
- template: Template used for generating notification.
+ body: Email body to be sent as notification.
Returns:
A dictionary containing the context for a message to be sent to one
or more recipients.
@@ -88,8 +85,6 @@
message_properties['sender_name'] = 'The %s Team' % site.site_name
message_properties['program_name'] = program.name
- body = loader.render_to_string(template, dictionary=message_properties)
-
# TODO(nathaniel): "to" can be a list of email addresses or a single
# email address? Is that right? The documentation of mailer.getMailContext
# affords no answer.
@@ -103,6 +98,53 @@
return mailer.getMailContext(to, subject, body, bcc=bcc)
+def getContextFromTemplateString(site, program, receivers,
+ message_properties, subject,
+ template_string):
+ """Sends out a notification to the specified user using the template
+ string to construct the notification body.
+
+ Args:
+ site: Site entity.
+ program: Program entity to which the notification applies.
+ receivers: Email addresses to which the notification should be sent.
+ message_properties: Message properties.
+ subject: Subject of notification email.
+ template_string: Template used for generating notification.
+ Returns:
+ A dictionary containing the context for a message to be sent to one
+ or more recipients.
+ """
+ template_inst = template.Template(template_string)
+ context_instance = template.Context(message_properties)
+ body = template_inst.render(context_instance)
+
+ return _getContextCommon(site, program, receivers, message_properties,
+ subject, body)
+
+
+def getContext(site, program, receivers, message_properties,
+ subject, template):
+ """Sends out a notification to the specified user by using the template
+ file to construct the notification body.
+
+ Args:
+ site: Site entity.
+ program: Program entity to which the notification applies.
+ receivers: Email addresses to which the notification should be sent.
+ message_properties: Message properties.
+ subject: Subject of notification email.
+ template: Template used for generating notification.
+ Returns:
+ A dictionary containing the context for a message to be sent to one
+ or more recipients.
+ """
+ body = loader.render_to_string(template, dictionary=message_properties)
+
+ return _getContextCommon(site, program, receivers, message_properties,
+ subject, body)
+
+
def getDefaultContext(request_data, emails, subject, extra_context=None):
"""Generate a dictionary with a default context.
@@ -319,7 +361,7 @@
is accepted into a program.
"""
- def getContext(self, emails, organization, program, site):
+ def getContext(self, emails, organization, program, site, program_messages):
"""Provides notification context of an email to send out when the specified
organization is accepted into the program.
@@ -328,18 +370,19 @@
organization: Organization entity.
program: Program entity.
site: Site entity.
+ program_messages: ProgramMessages entity that holds the message
+ templates provided by the program admins.
"""
- # TODO(daniel): replace with a dynamic mail content
- # program_messages = program.getProgramMessages()
- # template = program_messages.accepted_orgs_msg
- template = DEF_ACCEPTED_ORG_TEMPLATE
- subject = DEF_ACCEPTED_ORG % organization.name
+ subject = DEF_ACCEPTED_ORG % {
+ 'org': organization.name,
+ }
# TODO(daniel): consult what properties are needed.
message_properties = {}
- return getContext(
- site, program, emails, message_properties, subject, template)
+ return getContextFromTemplateString(
+ site, program, emails, message_properties, subject,
+ program_messages.accepted_orgs_msg)
class OrganizationRejectedContextProvider(object):
@@ -347,7 +390,7 @@
is rejected from a program.
"""
- def getContext(self, emails, organization, program, site):
+ def getContext(self, emails, organization, program, site, program_messages):
"""Provides notification context of an email to send out when the specified
organization is rejected from the program.
@@ -356,18 +399,19 @@
organization: Organization entity.
program: Program entity.
site: Site entity.
+ program_messages: ProgramMessages entity that holds the message
+ templates provided by the program admins.
"""
- # TODO(daniel): replace with a dynamic mail content
- # program_messages = program.getProgramMessages()
- # template = program_messages.rejected_orgs_msg
- template = DEF_REJECTED_ORG_TEMPLATE
- subject = DEF_REJECTED_ORG % organization.name
+ subject = DEF_REJECTED_ORG % {
+ 'org': organization.name,
+ }
# TODO(daniel): consult what properties are needed.
message_properties = {}
- return getContext(
- site, program, emails, message_properties, subject, template)
+ return getContextFromTemplateString(
+ site, program, emails, message_properties, subject,
+ program_messages.rejected_orgs_msg)
def orgAppContext(data, record, new_status, apply_url):
diff --git a/app/soc/templates/soc/notification/org_accepted.html b/app/soc/templates/soc/notification/org_accepted.html
deleted file mode 100644
index c042bcf..0000000
--- a/app/soc/templates/soc/notification/org_accepted.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "soc/notification/base.html" %}
-{% comment %}
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-{% endcomment %}
-
-{% block content %}
-Your Organization Application for "{{ org }}" in {{ program_name }} has been accepted.<br />
-Please click <a href="{{ url }}">here</a> to fill in the necessary information and create your Organization.
-{% endblock %}
-
-{% block unsubscribe %}
-{% endblock unsubscribe %}
diff --git a/app/soc/templates/soc/notification/org_rejected.html b/app/soc/templates/soc/notification/org_rejected.html
deleted file mode 100644
index defd98d..0000000
--- a/app/soc/templates/soc/notification/org_rejected.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "soc/mail/base.html" %}
-{% comment %}
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-{% endcomment %}
-
-{% block content %}
-Thank you for submitting "{{ org_app_name }}" organization application to {{ program_name }}.
-Unfortunately, we were unable to accept your organization's application at this time.
-We received many more applications for the program than we are able to accommodate,
-and we would encourage you to reapply for future instances of the program.
-{% endblock %}
-
-{% block signature %}
- Best regards, <br />{{ sender_name }}
-{% endblock %}
-
-{% block unsubscribe %}
-{% endblock unsubscribe %}
diff --git a/app/summerofcode/views/org_app.py b/app/summerofcode/views/org_app.py
index 2c4a183..d9a54c6 100644
--- a/app/summerofcode/views/org_app.py
+++ b/app/summerofcode/views/org_app.py
@@ -1110,7 +1110,8 @@
message='Missing or invalid new status in POST data.')
else:
organization = org_key.get()
- org_logic.setStatus(organization, data.program, data.site, new_status)
+ org_logic.setStatus(organization, data.program, data.site,
+ data.program.getProgramMessages(), new_status)
return http.HttpResponse()
diff --git a/tests/app/melange/logic/test_organization.py b/tests/app/melange/logic/test_organization.py
index 8cc6394..b146504 100644
--- a/tests/app/melange/logic/test_organization.py
+++ b/tests/app/melange/logic/test_organization.py
@@ -322,8 +322,8 @@
def testAcceptOrganization(self):
"""Tests that organization is successfully accepted."""
org = org_logic.setStatus(
- self.org, self.program, self.site, org_model.Status.ACCEPTED,
- recipients=[TEST_EMAIL])
+ self.org, self.program, self.site, self.program.getProgramMessages(),
+ org_model.Status.ACCEPTED, recipients=[TEST_EMAIL])
self.assertEqual(org.status, org_model.Status.ACCEPTED)
self.assertEmailSent()
@@ -331,8 +331,8 @@
def testRejectOrganization(self):
"""Tests that organization is successfully rejected."""
org = org_logic.setStatus(
- self.org, self.program, self.site, org_model.Status.REJECTED,
- recipients=[TEST_EMAIL])
+ self.org, self.program, self.site, self.program.getProgramMessages(),
+ org_model.Status.REJECTED, recipients=[TEST_EMAIL])
self.assertEqual(org.status, org_model.Status.REJECTED)
self.assertEmailSent()
@@ -340,8 +340,8 @@
def testPreAcceptOrganization(self):
"""Tests that organization is successfully pre-accepted."""
org = org_logic.setStatus(
- self.org, self.program, self.site, org_model.Status.PRE_ACCEPTED,
- recipients=[TEST_EMAIL])
+ self.org, self.program, self.site, self.program.getProgramMessages(),
+ org_model.Status.PRE_ACCEPTED, recipients=[TEST_EMAIL])
self.assertEqual(org.status, org_model.Status.PRE_ACCEPTED)
@@ -350,8 +350,8 @@
def testPreRejectOrganization(self):
"""Tests that organization is successfully pre-accepted."""
org = org_logic.setStatus(
- self.org, self.program, self.site, org_model.Status.PRE_REJECTED,
- recipients=[TEST_EMAIL])
+ self.org, self.program, self.site, self.program.getProgramMessages(),
+ org_model.Status.PRE_REJECTED, recipients=[TEST_EMAIL])
self.assertEqual(org.status, org_model.Status.PRE_REJECTED)
diff --git a/tests/app/melange/mapreduce/test_apply_org_admission_decisions.py b/tests/app/melange/mapreduce/test_apply_org_admission_decisions.py
index 0d7a563..524f78d 100644
--- a/tests/app/melange/mapreduce/test_apply_org_admission_decisions.py
+++ b/tests/app/melange/mapreduce/test_apply_org_admission_decisions.py
@@ -19,8 +19,10 @@
from melange.models import organization as org_model
from tests import org_utils
+from tests import profile_utils
from tests import test_utils
+from soc.logic.helper import notifications
from soc.models import program as program_model
from soc.mapreduce.helper import control as mapreduce_control
from soc.modules.seeder.logic.seeder import logic as seeder_logic
@@ -49,6 +51,14 @@
status=statuses[i % len(statuses)])
seeded_orgs[i % len(statuses)].append(org.key)
+ self.admins = []
+ for i in range(int(1.5 * len(seeded_orgs))):
+ admin = profile_utils.seedNDBProfile(
+ self.program.key(), admin_for=seeded_orgs[i % len(seeded_orgs)])
+ self.admins.append(admin)
+
+ self.program_messages = self.program.getProgramMessages()
+
self.pre_accepted_orgs = seeded_orgs[0]
self.pre_rejected_orgs = seeded_orgs[1]
self.applying_orgs = seeded_orgs[2]
@@ -80,6 +90,20 @@
org = org_key.get()
self.assertEqual(org.status, org_model.Status.APPLYING)
+ for org_key in self.pre_accepted_orgs:
+ org = org_key.get()
+ subject = notifications.DEF_ACCEPTED_ORG % {
+ 'org': org.name,
+ }
+ self.assertEmailSent(cc=org.contact.email, subject=subject)
+
+ for org_key in self.pre_rejected_orgs:
+ org = org_key.get()
+ subject = notifications.DEF_REJECTED_ORG % {
+ 'org': org.name,
+ }
+ self.assertEmailSent(cc=org.contact.email, subject=subject)
+
def testOrgsForAnotherProgram(self):
"""Tests that status of organizations for another program is untouched."""
# seed another program
diff --git a/tests/program_utils.py b/tests/program_utils.py
index c744fcb..19d1e88 100644
--- a/tests/program_utils.py
+++ b/tests/program_utils.py
@@ -256,10 +256,7 @@
"""
properties = {'parent': program_key}
properties.update(kwargs)
- program_messages = models.program_messages_model(**properties)
- program_messages.put()
-
- return program_messages
+ return seeder_logic.seed(models.program_messages_model, properties)
def seedGSoCProgramMessages(program_key=None, **kwargs):