Inject user email in notification mail subjects
[astakos] / snf-astakos-app / astakos / im / functions.py
1 # Copyright 2011 GRNET S.A. All rights reserved.
2
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import logging
35 import socket
36
37 from django.utils.translation import ugettext as _
38 from django.template.loader import render_to_string
39 from django.core.mail import send_mail
40 from django.core.urlresolvers import reverse
41 from django.core.exceptions import ValidationError
42 from django.template import Context, loader
43 from django.contrib.auth import login as auth_login, logout as auth_logout
44 from django.http import HttpRequest
45
46 from urllib import quote
47 from urlparse import urljoin
48 from smtplib import SMTPException
49 from datetime import datetime
50 from functools import wraps
51
52 from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, \
53     SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, LOGGING_LEVEL, \
54     VERIFICATION_EMAIL_SUBJECT, ADMIN_NOTIFICATION_EMAIL_SUBJECT, \
55     HELPDESK_NOTIFICATION_EMAIL_SUBJECT, INVITATION_EMAIL_SUBJECT, \
56     GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT, EMAIL_CHANGE_EMAIL_SUBJECT
57 from astakos.im.models import Invitation, AstakosUser
58
59 logger = logging.getLogger(__name__)
60
61 def logged(func, msg):
62     @wraps(func)
63     def with_logging(*args, **kwargs):
64         email = ''
65         user = None
66         if len(args) == 2 and isinstance(args[1], AstakosUser):
67             user = args[1]
68         elif len(args) == 1 and isinstance(args[0], HttpRequest):
69             request = args[0]
70             user = request.user
71         email = user.email if user and user.is_authenticated() else ''
72         r = func(*args, **kwargs)
73         if LOGGING_LEVEL:
74             logger._log(LOGGING_LEVEL, msg % email, [])
75         return r
76     return with_logging
77
78 login = logged(auth_login, '%s logged in.')
79 logout = logged(auth_logout, '%s logged out.')
80
81 def send_verification(user, template_name='im/activation_email.txt'):
82     """
83     Send email to user to verify his/her email and activate his/her account.
84     
85     Raises SendVerificationError
86     """
87     url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
88                                     quote(user.auth_token),
89                                     quote(urljoin(BASEURL, reverse('astakos.im.views.index'))))
90     message = render_to_string(template_name, {
91             'user': user,
92             'url': url,
93             'baseurl': BASEURL,
94             'site_name': SITENAME,
95             'support': DEFAULT_CONTACT_EMAIL})
96     sender = DEFAULT_FROM_EMAIL
97     try:
98         send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
99     except (SMTPException, socket.error) as e:
100         logger.exception(e)
101         raise SendVerificationError()
102     else:
103         msg = 'Sent activation %s' % user.email
104         logger._log(LOGGING_LEVEL, msg, [])
105
106 def send_activation(user, template_name='im/activation_email.txt'):
107     send_verification(user, template_name)
108     user.activation_sent = datetime.now()
109     user.save()
110
111 def send_admin_notification(user, template_name='im/admin_notification.txt'):
112     """
113     Send email to DEFAULT_ADMIN_EMAIL to notify for a new user registration.
114     
115     Raises SendNotificationError
116     """
117     if not DEFAULT_ADMIN_EMAIL:
118         return
119     message = render_to_string(template_name, {
120             'user': user,
121             'baseurl': BASEURL,
122             'site_name': SITENAME,
123             'support': DEFAULT_CONTACT_EMAIL})
124     sender = DEFAULT_FROM_EMAIL
125     try:
126         send_mail(_(ADMIN_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email}, message, sender, [DEFAULT_ADMIN_EMAIL])
127     except (SMTPException, socket.error) as e:
128         logger.exception(e)
129         raise SendNotificationError()
130     else:
131         msg = 'Sent admin notification for user %s' % user.email
132         logger._log(LOGGING_LEVEL, msg, [])
133
134 def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
135     """
136     Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
137     
138     Raises SendNotificationError
139     """
140     if not DEFAULT_CONTACT_EMAIL:
141         return
142     message = render_to_string(template_name, {
143             'user': user,
144             'baseurl': BASEURL,
145             'site_name': SITENAME,
146             'support': DEFAULT_ADMIN_EMAIL})
147     sender = DEFAULT_FROM_EMAIL
148     try:
149         send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email}, message, sender, [DEFAULT_CONTACT_EMAIL])
150     except (SMTPException, socket.error) as e:
151         logger.exception(e)
152         raise SendNotificationError()
153     else:
154         msg = 'Sent helpdesk admin notification for user %s' % user.email
155         logger._log(LOGGING_LEVEL, msg, [])
156
157 def send_invitation(invitation, template_name='im/invitation.txt'):
158     """
159     Send invitation email.
160     
161     Raises SendInvitationError
162     """
163     subject = _(INVITATION_EMAIL_SUBJECT)
164     url = '%s?code=%d' % (urljoin(BASEURL, reverse('astakos.im.views.index')), invitation.code)
165     message = render_to_string('im/invitation.txt', {
166                 'invitation': invitation,
167                 'url': url,
168                 'baseurl': BASEURL,
169                 'site_name': SITENAME,
170                 'support': DEFAULT_CONTACT_EMAIL})
171     sender = DEFAULT_FROM_EMAIL
172     try:
173         send_mail(subject, message, sender, [invitation.username])
174     except (SMTPException, socket.error) as e:
175         logger.exception(e)
176         raise SendInvitationError()
177     else:
178         msg = 'Sent invitation %s' % invitation
179         logger._log(LOGGING_LEVEL, msg, [])
180
181 def send_greeting(user, email_template_name='im/welcome_email.txt'):
182     """
183     Send welcome email.
184     
185     Raises SMTPException, socket.error
186     """
187     subject = _(GREETING_EMAIL_SUBJECT)
188     message = render_to_string(email_template_name, {
189                 'user': user,
190                 'url': urljoin(BASEURL, reverse('astakos.im.views.index')),
191                 'baseurl': BASEURL,
192                 'site_name': SITENAME,
193                 'support': DEFAULT_CONTACT_EMAIL})
194     sender = DEFAULT_FROM_EMAIL
195     try:
196         send_mail(subject, message, sender, [user.email])
197     except (SMTPException, socket.error) as e:
198         logger.exception(e)
199         raise SendGreetingError()
200     else:
201         msg = 'Sent greeting %s' % user.email
202         logger._log(LOGGING_LEVEL, msg, [])
203
204 def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
205     subject = _(FEEDBACK_EMAIL_SUBJECT)
206     from_email = user.email
207     recipient_list = [DEFAULT_CONTACT_EMAIL]
208     content = render_to_string(email_template_name, {
209         'message': msg,
210         'data': data,
211         'user': user})
212     try:
213         send_mail(subject, content, from_email, recipient_list)
214     except (SMTPException, socket.error) as e:
215         logger.exception(e)
216         raise SendFeedbackError()
217     else:
218         msg = 'Sent feedback from %s' % user.email
219         logger._log(LOGGING_LEVEL, msg, [])
220
221 def send_change_email(ec, request, email_template_name='registration/email_change_email.txt'):
222     try:
223         url = reverse('email_change_confirm',
224                       kwargs={'activation_key':ec.activation_key})
225         url = request.build_absolute_uri(url)
226         t = loader.get_template(email_template_name)
227         c = {'url': url, 'site_name': SITENAME}
228         from_email = DEFAULT_FROM_EMAIL
229         send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
230             t.render(Context(c)), from_email, [ec.new_email_address])
231     except (SMTPException, socket.error) as e:
232         logger.exception(e)
233         raise ChangeEmailError()
234     else:
235         msg = 'Sent change email for %s' % ec.user.email
236         logger._log(LOGGING_LEVEL, msg, [])
237
238 def activate(user, email_template_name='im/welcome_email.txt',
239                 helpdesk_email_template_name='im/helpdesk_notification.txt', verify_email=False):
240     """
241     Activates the specific user and sends email.
242     
243     Raises SendGreetingError, ValidationError
244     """
245     user.is_active = True
246     if verify_email:
247         user.email_verified = True
248     user.save()
249     send_helpdesk_notification(user, helpdesk_email_template_name)
250     send_greeting(user, email_template_name)
251
252 def switch_account_to_shibboleth(user, local_user, greeting_template_name='im/welcome_email.txt'):
253     if not user or not isinstance(user, AstakosUser):
254         return
255     
256     if not local_user or not isinstance(user, AstakosUser):
257         return
258     
259     if not user.provider == 'shibboleth':
260         return
261     
262     user.delete()
263     local_user.provider = 'shibboleth'
264     local_user.third_party_identifier = user.third_party_identifier
265     local_user.save()
266     send_greeting(local_user, greeting_template_name)
267     return local_user
268
269 def invite(invitation, inviter, email_template_name='im/welcome_email.txt'):
270     """
271     Send an invitation email and upon success reduces inviter's invitation by one.
272     
273     Raises SendInvitationError
274     """
275     invitation.inviter = inviter
276     invitation.save()
277     send_invitation(invitation, email_template_name)
278     inviter.invitations = max(0, inviter.invitations - 1)
279     inviter.save()
280
281 def set_user_credibility(email, has_credits):
282     try:
283         user = AstakosUser.objects.get(email=email, is_active=True)
284         user.has_credits = has_credits
285         user.save()
286     except AstakosUser.DoesNotExist, e:
287         logger.exception(e)
288     except ValidationError, e:
289         logger.exception(e)
290
291 class SendMailError(Exception):
292     pass
293
294 class SendAdminNotificationError(SendMailError):
295     def __init__(self):
296         self.message = _('Failed to send notification')
297         super(SendAdminNotificationError, self).__init__()
298
299 class SendVerificationError(SendMailError):
300     def __init__(self):
301         self.message = _('Failed to send verification')
302         super(SendVerificationError, self).__init__()
303
304 class SendInvitationError(SendMailError):
305     def __init__(self):
306         self.message = _('Failed to send invitation')
307         super(SendInvitationError, self).__init__()
308
309 class SendGreetingError(SendMailError):
310     def __init__(self):
311         self.message = _('Failed to send greeting')
312         super(SendGreetingError, self).__init__()
313
314 class SendFeedbackError(SendMailError):
315     def __init__(self):
316         self.message = _('Failed to send feedback')
317         super(SendFeedbackError, self).__init__()
318
319 class ChangeEmailError(SendMailError):
320     def __init__(self):
321         self.message = _('Failed to send change email')
322         super(ChangeEmailError, self).__init__()
323
324 class SendNotificationError(SendMailError):
325     def __init__(self):
326         self.message = _('Failed to send notification email')
327         super(SendNotificationError, self).__init__()