def mail_pending_refunds(): # First find all the pending refunds and the addons for them. pending = dict((Refund.objects.filter(status=amo.REFUND_PENDING) .values_list('contribution__addon_id') .annotate(Count('id')))) if not pending: log.info('No refunds to email') return log.info('Mailing pending refunds: %s refunds found' % len(pending)) # Find all owners of those addons. users = (AddonUser.objects.filter(role=amo.AUTHOR_ROLE_OWNER, addon__in=pending.keys()) .values_list('addon_id', 'user__email')) # Group up the owners. An owner could have more than one addon and each # addon can have more than one owner. owners = {} for addon_id, email in users: owners.setdefault(email, []) owners[email].append(addon_id) # Send the emails out. for owner, addon_ids in owners.items(): log.info('Sending refund emails to: %s about %s' % (email, ', '.join([str(i) for i in addon_ids]))) addons = Addon.objects.filter(pk__in=addon_ids) ctx = {'addons': addons, 'refunds': pending} send_mail('Pending refund requests at the Mozilla Marketplace', env.get_template('market/emails/refund-nag.txt').render(ctx), from_email=settings.NOBODY_EMAIL, recipient_list=[owner])
def test_send_attachment(self): path = os.path.join(ATTACHMENTS_DIR, 'bacon.txt') attachments = [(os.path.basename(path), storage.open(path), mimetypes.guess_type(path)[0])] send_mail('test subject', 'test body', from_email='*****@*****.**', recipient_list=['*****@*****.**'], attachments=attachments) eq_(attachments, mail.outbox[0].attachments, 'Attachments not included')
def refund_reason(request, contribution, wizard): addon = contribution.addon if not 'request' in wizard.get_progress(): return redirect('users.support', contribution.pk, 'request') if contribution.is_instant_refund(): paypal.refund(contribution.paykey) paypal_log.info('Refund issued for contribution %r' % contribution.pk) # Note: we have to wait for PayPal to issue an IPN before it's # completely refunded. messages.success(request, _('Refund is being processed.')) return redirect('users.purchases') form = forms.ContactForm(request.POST or None) if request.method == 'POST' and form.is_valid(): url = absolutify(urlparams(addon.get_dev_url('issue_refund'), transaction_id=contribution.transaction_id)) template = jingo.render_to_string(request, wizard.tpl('emails/refund-request.txt'), context={'addon': addon, 'form': form, 'user': request.amo_user, 'contribution': contribution, 'refund_url': url}) log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Refund Request for %s' % addon.name), template, settings.NOBODY_EMAIL, [smart_str(addon.support_email)]) return redirect(reverse('users.support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'form': form})
def refund_reason(request, contribution, wizard): addon = contribution.addon if not 'request' in wizard.get_progress(): return redirect(reverse('users.support', args=[contribution.pk, 'request'])) form = forms.ContactForm(request.POST or None) if request.method == 'POST': if form.is_valid(): # if under 30 minutes, refund # TODO(ashort): add in the logic for under 30 minutes. template = jingo.render_to_string(request, wizard.tpl('emails/refund-request.txt'), context={'addon': addon, 'form': form, 'user': request.amo_user, 'contribution': contribution}) log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Refund Request for %s' % addon.name), template, request.amo_user.email, [smart_str(addon.support_email)]) return redirect(reverse('users.support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'contribut': addon, 'form': form})
def tally_job_results(job_id, **kw): sql = """select sum(1), sum(case when completed IS NOT NULL then 1 else 0 end) from validation_result where validation_job_id=%s""" cursor = connection.cursor() cursor.execute(sql, [job_id]) total, completed = cursor.fetchone() if completed == total: # The job has finished. job = ValidationJob.objects.get(pk=job_id) job.update(completed=datetime.now()) if job.finish_email: send_mail( u"Behold! Validation results for %s %s->%s" % (amo.APP_IDS[job.application.id].pretty, job.curr_max_version.version, job.target_version.version), textwrap.dedent( """ Aww yeah %s """ % absolutify(reverse("zadmin.validation")) ), from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[job.finish_email], )
def edit(request): webapp = settings.APP_PREVIEW # Don't use request.amo_user since it has too much caching. amouser = UserProfile.objects.get(pk=request.user.id) if request.method == "POST": # ModelForm alters the instance you pass in. We need to keep a copy # around in case we need to use it below (to email the user) original_email = amouser.email form = forms.UserEditForm(request.POST, request.FILES, request=request, instance=amouser, webapp=webapp) if form.is_valid(): messages.success(request, _("Profile Updated")) if amouser.email != original_email: # Temporarily block email changes. if settings.APP_PREVIEW: messages.error(request, "Error", "You cannot change your email on the " "developer preview site.") return jingo.render(request, "users/edit.html", {"form": form, "amouser": amouser}) l = {"user": amouser, "mail1": original_email, "mail2": amouser.email} log.info(u"User (%(user)s) has requested email change from" "(%(mail1)s) to (%(mail2)s)" % l) messages.info( request, _("Email Confirmation Sent"), _( u"An email has been sent to {0} to confirm your new " "email address. For the change to take effect, you " "need to click on the link provided in this email. " "Until then, you can keep logging in with your " "current email address." ).format(amouser.email), ) domain = settings.DOMAIN token, hash = EmailResetCode.create(amouser.id, amouser.email) url = "%s%s" % (settings.SITE_URL, reverse("users.emailchange", args=[amouser.id, token, hash])) t = loader.get_template("users/email/emailchange.ltxt") c = {"domain": domain, "url": url} send_mail( _("Please confirm your email address " "change at %s" % domain), t.render(Context(c)), None, [amouser.email], use_blacklist=False, real_email=True, ) # Reset the original email back. We aren't changing their # address until they confirm the new one amouser.email = original_email form.save() return redirect("users.edit") else: messages.error( request, _("Errors Found"), _("There were errors in the changes " "you made. Please correct them and " "resubmit."), ) else: form = forms.UserEditForm(instance=amouser, webapp=webapp) return jingo.render(request, "users/edit.html", {"form": form, "amouser": amouser, "webapp": webapp})
def notify_failed(file_pks, job_pk, data, **kw): log.info("[%s@None] Notifying failed for job %s." % (len(file_pks), job_pk)) job = ValidationJob.objects.get(pk=job_pk) set_user(get_task_user()) for result in ValidationResult.objects.filter(validation_job=job, file__pk__in=file_pks): file = result.file version = file.version addon = version.addon context = get_context(addon, version, job, [result], fileob=file) for author in addon.authors.all(): log.info( u"Emailing %s%s for addon %s, file %s about " "error from bulk validation job %s" % (author.email, " [PREVIEW]" if data["preview_only"] else "", addon.pk, file.pk, job_pk) ) args = (Template(data["subject"]).render(context), Template(data["text"]).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[author.email]) if data["preview_only"]: job.preview_failure_mail(*args, **kwargs) else: send_mail(*args, **kwargs) amo.log( amo.LOG.BULK_VALIDATION_EMAILED, addon, version, details={"version": version.version, "file": file.filename, "target": job.target_version.version}, )
def edit(request): webapp = settings.APP_PREVIEW # Don't use request.amo_user since it has too much caching. amouser = UserProfile.objects.get(pk=request.user.id) if request.method == 'POST': # ModelForm alters the instance you pass in. We need to keep a copy # around in case we need to use it below (to email the user) original_email = amouser.email form = forms.UserEditForm(request.POST, request.FILES, request=request, instance=amouser, webapp=webapp) if form.is_valid(): messages.success(request, _('Profile Updated')) if amouser.email != original_email: # Temporarily block email changes. if settings.APP_PREVIEW: messages.error(request, 'Error', 'You cannot change your email on the ' 'developer preview site.') return jingo.render(request, 'users/edit.html', {'form': form, 'amouser': amouser}) l = {'user': amouser, 'mail1': original_email, 'mail2': amouser.email} log.info(u"User (%(user)s) has requested email change from" "(%(mail1)s) to (%(mail2)s)" % l) messages.info(request, _('Email Confirmation Sent'), _(u'An email has been sent to {0} to confirm your new ' 'email address. For the change to take effect, you ' 'need to click on the link provided in this email. ' 'Until then, you can keep logging in with your ' 'current email address.').format(amouser.email)) domain = settings.DOMAIN token, hash = EmailResetCode.create(amouser.id, amouser.email) url = "%s%s" % (settings.SITE_URL, reverse('users.emailchange', args=[amouser.id, token, hash])) t = loader.get_template('users/email/emailchange.ltxt') c = {'domain': domain, 'url': url} send_mail(_('Please confirm your email address ' 'change at %s' % domain), t.render(Context(c)), None, [amouser.email], use_blacklist=False, real_email=True) # Reset the original email back. We aren't changing their # address until they confirm the new one amouser.email = original_email form.save() return redirect('users.edit') else: messages.error(request, _('Errors Found'), _('There were errors in the changes ' 'you made. Please correct them and ' 'resubmit.')) else: form = forms.UserEditForm(instance=amouser, webapp=webapp) return jingo.render(request, 'users/edit.html', {'form': form, 'amouser': amouser, 'webapp': webapp})
def delete(self, msg): if self.highest_status: log.debug('Adding guid to blacklist: %s' % self.guid) BlacklistedGuid(guid=self.guid, comments=msg).save() log.debug('Deleting add-on: %s' % self.id) authors = [u.email for u in self.authors.all()] to = [settings.FLIGTAR] user = amo.get_user() user_str = "%s, %s (%s)" % (user.display_name or user.username, user.email, user.id) if user else "Unknown" email_msg = u""" The following add-on was deleted. ADD-ON: %s DELETED BY: %s ID: %s GUID: %s AUTHORS: %s TOTAL DOWNLOADS: %s AVERAGE DAILY USERS: %s NOTES: %s """ % (self.name, user_str, self.id, self.guid, authors, self.total_downloads, self.average_daily_users, msg) log.debug('Sending delete email for add-on %s' % self.id) subject = 'Deleting add-on %s' % self.id rv = super(Addon, self).delete() # We want to ensure deletion before notification. if self.highest_status: send_mail(subject, email_msg, recipient_list=to) return rv
def notify_failed(file_pks, job_pk, data, **kw): log.info('[%s@None] Notifying failed for job %s.' % (len(file_pks), job_pk)) job = ValidationJob.objects.get(pk=job_pk) set_user(get_task_user()) for result in ValidationResult.objects.filter(validation_job=job, file__pk__in=file_pks): file = result.file version = file.version addon = version.addon context = get_context(addon, version, job, [result]) for author in addon.authors.all(): log.info(u'Emailing %s%s for addon %s, file %s about ' 'error from bulk validation job %s' % (author.email, ' [PREVIEW]' if data['preview_only'] else '', addon.pk, file.pk, job_pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[author.email]) if data['preview_only']: job.preview_failure_mail(*args, **kwargs) else: send_mail(*args, **kwargs) amo.log(amo.LOG.BULK_VALIDATION_EMAILED, addon, version, details={'version': version.version, 'file': file.filename, 'target': job.target_version.version})
def test_async_will_stop_retrying(self, backend): backend.side_effect = self.make_backend_class([True, True]) with self.assertRaises(RuntimeError): send_mail('test subject', 'test body', async=True, max_retries=1, recipient_list=['*****@*****.**'])
def notify_compatibility(users, job, data, **kw): log.info('[%s@%s] Sending notification mail for job %s.' % (len(users), notify_compatibility.rate_limit, job.pk)) set_user(get_task_user()) dry_run = data['preview_only'] app_id = job.target_version.application.pk stats = collections.defaultdict(int) stats['processed'] = 0 stats['is_dry_run'] = int(dry_run) for user in users: stats['processed'] += 1 for a in chain(user.passing_addons, user.failing_addons): results = job.result_set.filter(file__version__addon=a) a.links = ' '.join(absolutify(reverse('devhub.bulk_compat_result', args=[a.slug, r.pk])) for r in results) a.compat_link = absolutify(reverse('devhub.versions.edit', args=[a.pk, a.current_version.pk])) context = Context({ 'APPLICATION': str(job.application), 'VERSION': job.target_version.version, 'PASSING_ADDONS': user.passing_addons, 'FAILING_ADDONS': user.failing_addons, }) log.info(u'Emailing %s%s for %d addons about ' 'bulk validation job %s' % (user.email, ' [PREVIEW]' if dry_run else '', len(user.passing_addons) + len(user.failing_addons), job.pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email]) if dry_run: job.preview_notify_mail(*args, **kwargs) else: stats['author_emailed'] += 1 send_mail(*args, **kwargs) amo.log(amo.LOG.BULK_VALIDATION_USER_EMAILED, user, details={'passing': [a.id for a in user.passing_addons], 'failing': [a.id for a in user.failing_addons], 'target': job.target_version.version, 'application': app_id}) log.info('[%s@%s] bulk email stats for job %s: {%s}' % (len(users), notify_compatibility.rate_limit, job.pk, ', '.join('%s: %s' % (k, stats[k]) for k in sorted(stats.keys()))))
def email_confirmation_code(self): from amo.utils import send_mail log.debug("Sending account confirmation code for user (%s)", self) url = "%s%s" % (settings.SITE_URL, reverse("users.confirm", args=[self.id, self.confirmationcode])) domain = settings.DOMAIN t = loader.get_template("users/email/confirm.ltxt") c = {"domain": domain, "url": url} send_mail(_("Please confirm your email address"), t.render(Context(c)), None, [self.email], use_blacklist=False)
def test_blacklist_flag(self): to = "*****@*****.**" settings.EMAIL_BLACKLIST = (to,) success = send_mail("test subject", "test body", recipient_list=[to], fail_silently=False, use_blacklist=True) assert success eq_(len(mail.outbox), 0) success = send_mail("test subject", "test body", recipient_list=[to], fail_silently=False, use_blacklist=False) assert success eq_(len(mail.outbox), 1)
def test_send_attachment(self): path = os.path.join(ATTACHMENTS_DIR, "bacon.txt") attachments = [(os.path.basename(path), storage.open(path), mimetypes.guess_type(path)[0])] send_mail( "test subject", "test body", from_email="*****@*****.**", recipient_list=["*****@*****.**"], attachments=attachments, ) eq_(attachments, mail.outbox[0].attachments, "Attachments not included")
def refund_reason(request, contribution, wizard): addon = contribution.addon if not "request" in wizard.get_progress(): return redirect("users.support", contribution.pk, "request") if contribution.transaction_id is None: messages.error( request, _( "A refund cannot be applied for yet. Please try again" " later. If this error persists contact " "[email protected]." ), ) paypal_log.info("Refund requested for contribution with no " "transaction_id: %r" % (contribution.pk,)) return redirect("users.purchases") if contribution.is_instant_refund(): paypal.refund(contribution.paykey) # TODO: Consider requiring a refund reason for instant refunds. refund = contribution.enqueue_refund(amo.REFUND_APPROVED_INSTANT) paypal_log.info("Refund %r issued for contribution %r" % (refund.pk, contribution.pk)) # Note: we have to wait for PayPal to issue an IPN before it's # completely refunded. messages.success(request, _("Refund is being processed.")) return redirect("users.purchases") form = forms.ContactForm(request.POST or None) if request.method == "POST" and form.is_valid(): reason = form.cleaned_data["text"] template = jingo.render_to_string( request, wizard.tpl("emails/refund-request.txt"), context={ "addon": addon, "form": form, "user": request.amo_user, "contribution": contribution, "refund_url": contribution.get_absolute_refund_url(), "refund_reason": reason, }, ) log.info("Refund request sent by user: %s for addon: %s" % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail( _(u"New Refund Request for %s" % addon.name), template, settings.NOBODY_EMAIL, [smart_str(addon.support_email)], ) # Add this refund request to the queue. contribution.enqueue_refund(amo.REFUND_PENDING, reason) return redirect(reverse("users.support", args=[contribution.pk, "refund-sent"])) return wizard.render(request, wizard.tpl("refund.html"), {"form": form})
def restrict(self): from amo.utils import send_mail log.info(u'User (%s: <%s>) is being restricted and ' 'its user-generated content removed.' % (self, self.email)) g = Group.objects.get(rules='Restricted:UGC') GroupUser.objects.create(user=self, group=g) self.reviews.all().delete() self.collections.all().delete() t = loader.get_template('users/email/restricted.ltxt') send_mail(_('Your account has been restricted'), t.render(Context({})), None, [self.email], use_blacklist=False, real_email=True)
def test_dont_localize(self, fake_Context): perm_setting = [] def ctx(d, autoescape): perm_setting.append(unicode(d['perm_setting'])) return TemplateContext(d, autoescape=autoescape) fake_Context.side_effect = ctx user = UserProfile.objects.all()[0] to = user.email translation.activate('zh_TW') send_mail('test subject', 'test body', perm_setting='reply', recipient_list=[to], fail_silently=False) eq_(perm_setting[0], u'an add-on developer replies to my review')
def restrict(self): from amo.utils import send_mail log.info(u'User (%s: <%s>) is being restricted and ' 'its user-generated content removed.' % (self, self.email)) g = Group.objects.get(rules='Restricted:UGC') GroupUser.objects.create(user=self, group=g) self.reviews.all().delete() self.collections.all().delete() t = loader.get_template('users/email/restricted.ltxt') send_mail(_('Your account has been restricted'), t.render(Context({})), None, [self.email], use_blacklist=False)
def email_confirmation_code(self): from amo.utils import send_mail log.debug("Sending account confirmation code for user (%s)", self) url = "%s%s" % (settings.SITE_URL, reverse('users.confirm', args=[self.id, self.confirmationcode])) domain = settings.DOMAIN t = loader.get_template('users/email/confirm.ltxt') c = {'domain': domain, 'url': url, } send_mail(_("Please confirm your email address"), t.render(Context(c)), None, [self.email], use_blacklist=False)
def edit(request): # Don't use request.amo_user since it has too much caching. amouser = UserProfile.objects.get(pk=request.user.id) if request.method == 'POST': # ModelForm alters the instance you pass in. We need to keep a copy # around in case we need to use it below (to email the user) original_email = amouser.email form = forms.UserEditForm(request.POST, request.FILES, request=request, instance=amouser) if form.is_valid(): messages.success(request, _('Profile Updated')) if amouser.email != original_email: l = {'user': amouser, 'mail1': original_email, 'mail2': amouser.email} log.info(u"User (%(user)s) has requested email change from " "(%(mail1)s) to (%(mail2)s)" % l) messages.info(request, _('Email Confirmation Sent'), _(u'An email has been sent to {0} to confirm your new ' 'email address. For the change to take effect, you ' 'need to click on the link provided in this email. ' 'Until then, you can keep logging in with your ' 'current email address.').format(amouser.email)) token, hash_ = EmailResetCode.create(amouser.id, amouser.email) url = '%s%s' % (settings.SITE_URL, reverse('users.emailchange', args=[amouser.id, token, hash_])) t = loader.get_template('users/email/emailchange.ltxt') c = {'domain': settings.DOMAIN, 'url': url} send_mail(_('Please confirm your email address ' 'change at %s' % settings.DOMAIN), t.render(Context(c)), None, [amouser.email], use_blacklist=False, real_email=True) # Reset the original email back. We aren't changing their # address until they confirm the new one amouser.email = original_email form.save() return redirect('users.edit') else: messages.error(request, _('Errors Found'), _('There were errors in the changes ' 'you made. Please correct them and ' 'resubmit.')) else: form = forms.UserEditForm(instance=amouser, request=request) return render(request, 'users/edit.html', {'form': form, 'amouser': amouser})
def email_confirmation_code(self): from amo.utils import send_mail log.debug("Sending account confirmation code for user (%s)", self) url = "%s%s" % (settings.SITE_URL, reverse('users.confirm', args=[self.id, self.confirmationcode])) log.debug("confirmation link (%s)", url) domain = settings.DOMAIN t = loader.get_template('users/email/confirm.ltxt') c = {'domain': domain, 'url': url, } send_mail(_("Please confirm your email address"), t.render(Context(c)), None, [self.email], use_blacklist=False, real_email=True)
def refund_reason(request, contribution, wizard): addon = contribution.addon if not 'request' in wizard.get_progress(): return redirect('users.support', contribution.pk, 'request') if contribution.transaction_id is None: messages.error( request, _('A refund cannot be applied for yet. Please try again' ' later. If this error persists contact ' '[email protected].')) paypal_log.info('Refund requested for contribution with no ' 'transaction_id: %r' % (contribution.pk, )) return redirect('users.purchases') if contribution.is_instant_refund(): paypal.refund(contribution.paykey) # TODO: Consider requiring a refund reason for instant refunds. refund = contribution.enqueue_refund(amo.REFUND_APPROVED_INSTANT) paypal_log.info('Refund %r issued for contribution %r' % (refund.pk, contribution.pk)) # Note: we have to wait for PayPal to issue an IPN before it's # completely refunded. messages.success(request, _('Refund is being processed.')) return redirect('users.purchases') form = forms.ContactForm(request.POST or None) if request.method == 'POST' and form.is_valid(): reason = form.cleaned_data['text'] template = jingo.render_to_string( request, wizard.tpl('emails/refund-request.txt'), context={ 'addon': addon, 'form': form, 'user': request.amo_user, 'contribution': contribution, 'refund_url': contribution.get_absolute_refund_url(), 'refund_reason': reason }) log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Refund Request for %s' % addon.name), template, settings.NOBODY_EMAIL, [smart_str(addon.support_email)]) # Add this refund request to the queue. contribution.enqueue_refund(amo.REFUND_PENDING, reason) return redirect( reverse('users.support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'form': form})
def test_blacklist_flag(self): to = '*****@*****.**' settings.EMAIL_BLACKLIST = (to,) success = send_mail('test subject', 'test body', recipient_list=[to], fail_silently=False, use_blacklist=True) assert success eq_(len(mail.outbox), 0) success = send_mail('test subject', 'test body', recipient_list=[to], fail_silently=False, use_blacklist=False) assert success eq_(len(mail.outbox), 1)
def test_qa_whitelist_with_mixed_emails(self): assert send_mail('test subject', 'test body', recipient_list=['*****@*****.**', '*****@*****.**'], fail_silently=False) eq_(len(mail.outbox), 1) eq_(mail.outbox[0].to, set(['*****@*****.**'])) eq_(FakeEmail.objects.count(), 1)
def mail_thankyou(self, request=None): """ Mail a thankyou note for a completed contribution. Raises a ``ContributionError`` exception when the contribution is not complete or email addresses are not found. """ locale = self._switch_locale() # Thankyous must be enabled. if not self.addon.enable_thankyou: # Not an error condition, just return. return # Contribution must be complete. if not self.transaction_id: raise ContributionError('Transaction not complete') # Send from support_email, developer's email, or default. from_email = settings.DEFAULT_FROM_EMAIL if self.addon.support_email: from_email = str(self.addon.support_email) else: try: author = self.addon.listed_authors[0] if author.email and not author.emailhidden: from_email = author.email except (IndexError, TypeError): # This shouldn't happen, but the default set above is still ok. pass # We need the contributor's email. to_email = self.post_data['payer_email'] if not to_email: raise ContributionError('Empty payer email') # Make sure the url uses the right language. # Setting a prefixer would be nicer, but that requires a request. url_parts = self.addon.meet_the_dev_url().split('/') url_parts[1] = locale.language # Buildup the email components. t = loader.get_template('stats/contribution-thankyou-email.ltxt') c = { 'thankyou_note': self.addon.thankyou_note, 'addon_name': self.addon.name, 'learn_url': '%s%s?src=emailinfo' % (settings.SITE_URL, '/'.join(url_parts)), 'domain': settings.DOMAIN, } body = t.render(Context(c)) subject = _('Thanks for contributing to {addon_name}').format( addon_name=self.addon.name) # Send the email if send_mail(subject, body, from_email, [to_email], fail_silently=True, perm_setting='dev_thanks'): # Clear out contributor identifying information. del(self.post_data['payer_email']) self.save()
def test_success_real_mail(self): assert send_mail('test subject', 'test body', recipient_list=['*****@*****.**'], fail_silently=False) eq_(len(mail.outbox), 1) eq_(mail.outbox[0].subject.find('test subject'), 0) eq_(mail.outbox[0].body.find('test body'), 0)
def test_success_fake_mail(self): assert send_mail('test subject', 'test body', recipient_list=['*****@*****.**'], fail_silently=False) eq_(len(mail.outbox), 0) eq_(FakeEmail.objects.count(), 1) eq_(FakeEmail.objects.get().message.endswith('test body'), True)
def refund_reason(request, contribution, wizard): addon = contribution.addon if not 'request' in wizard.get_progress(): return redirect('users.support', contribution.pk, 'request') if contribution.transaction_id is None: messages.error(request, _('A refund cannot be applied for yet. Please try again' ' later. If this error persists contact ' '[email protected].')) paypal_log.info('Refund requested for contribution with no ' 'transaction_id: %r' % (contribution.pk,)) return redirect('users.purchases') if contribution.is_instant_refund(): paypal.refund(contribution.paykey) # TODO: Consider requiring a refund reason for instant refunds. refund = contribution.enqueue_refund(amo.REFUND_APPROVED_INSTANT) paypal_log.info('Refund %r issued for contribution %r' % (refund.pk, contribution.pk)) # Note: we have to wait for PayPal to issue an IPN before it's # completely refunded. messages.success(request, _('Refund is being processed.')) return redirect('users.purchases') form = forms.ContactForm(request.POST or None) if request.method == 'POST' and form.is_valid(): reason = form.cleaned_data['text'] template = jingo.render_to_string(request, wizard.tpl('emails/refund-request.txt'), context={'addon': addon, 'form': form, 'user': request.amo_user, 'contribution': contribution, 'refund_url': contribution.get_absolute_refund_url(), 'refund_reason': reason}) log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Refund Request for %s' % addon.name), template, settings.NOBODY_EMAIL, [smart_str(addon.support_email)]) # Add this refund request to the queue. contribution.enqueue_refund(amo.REFUND_PENDING, reason) return redirect(reverse('users.support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'form': form})
def send_notification(self, version): user_log.info('Sending addon update notice to %s for %s' % (self.user.email, self.addon.pk)) context = Context({ 'name': self.addon.name, 'url': absolutify(reverse('addons.detail', args=[self.addon.pk])), 'number': version.version, 'review': absolutify(reverse('editors.review', args=[version.pk])), 'SITE_URL': settings.SITE_URL, }) # Not being localised because we don't know the editors locale. subject = 'Mozilla Add-ons: %s Updated' % self.addon.name template = loader.get_template('editors/emails/notify_update.ltxt') send_mail(subject, template.render(Context(context)), recipient_list=[self.user.email], from_email=settings.EDITORS_EMAIL, use_blacklist=False)
def test_qa_whitelist_with_mixed_emails(self): assert send_mail('test subject', 'test body', recipient_list=['*****@*****.**', '*****@*****.**'], fail_silently=False) eq_(len(mail.outbox), 1) eq_(mail.outbox[0].to, ['*****@*****.**']) eq_(FakeEmail.objects.count(), 1)
def test_blacklist(self): to = '*****@*****.**' settings.EMAIL_BLACKLIST = (to,) success = send_mail('test subject', 'test body', recipient_list=[to], fail_silently=False) assert success eq_(len(mail.outbox), 0)
def send_notification(self, version): user_log.info('Sending addon update notice to %s for %s' % (self.user.email, self.addon.pk)) context = Context({ 'name': self.addon.name, 'url': absolutify(reverse('addons.detail', args=[self.addon.pk])), 'number': version.version, 'review': absolutify(reverse('editors.review', args=[self.addon.pk])), 'SITE_URL': settings.SITE_URL, }) # Not being localised because we don't know the editors locale. subject = 'Mozilla Add-ons: %s Updated' % self.addon.name template = loader.get_template('editors/emails/notify_update.ltxt') send_mail(subject, template.render(Context(context)), recipient_list=[self.user.email], from_email=settings.EDITORS_EMAIL, use_blacklist=False)
def test_qa_whitelist(self): assert send_mail('test subject', 'test body', recipient_list=['*****@*****.**'], fail_silently=False) eq_(len(mail.outbox), 1) eq_(mail.outbox[0].subject.find('test subject'), 0) eq_(mail.outbox[0].body.find('test body'), 0) eq_(FakeEmail.objects.count(), 1) eq_(FakeEmail.objects.get().message.endswith('test body'), True)
def test_success(self): to = '*****@*****.**' settings.EMAIL_BLACKLIST = () success = send_mail('test subject', 'test body', recipient_list=[to], fail_silently=False) assert success eq_(len(mail.outbox), 1) assert mail.outbox[0].subject.find('test subject') == 0 assert mail.outbox[0].body.find('test body') == 0
def test_blacklist_flag(self): to = '*****@*****.**' to2 = '*****@*****.**' settings.EMAIL_BLACKLIST = (to,) success = send_mail('test subject', 'test body', recipient_list=[to, to2], fail_silently=False, use_blacklist=True) assert success eq_(len(mail.outbox), 1) eq_(mail.outbox[0].to, [to2])
def support_mozilla(request, contribution, wizard): addon = contribution.addon form = forms.ContactForm(request.POST) if request.method == 'POST': if form.is_valid(): template = jingo.render_to_string(request, wizard.tpl('emails/support-request.txt'), context={'addon': addon, 'form': form, 'contribution': contribution, 'user': request.amo_user}) log.info('Support request to mozilla by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Support Request for %s' % addon.name), template, request.amo_user.email, [settings.FLIGTAR]) return redirect(reverse('users.support', args=[contribution.pk, 'mozilla-sent'])) return wizard.render(request, wizard.tpl('mozilla.html'), {'addon': addon, 'form': form})
def support_mozilla(request, contribution, wizard): addon = contribution.addon form = forms.ContactForm(request.POST or None) if request.method == 'POST': if form.is_valid(): template = jingo.render_to_string(request, wizard.tpl('emails/support-request.txt'), context={'addon': addon, 'form': form, 'contribution': contribution, 'user': request.amo_user}) log.info('Support request to mozilla by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Support Request for %s' % addon.name), template, request.amo_user.email, [settings.MARKETPLACE_EMAIL]) return redirect(reverse('users.support', args=[contribution.pk, 'mozilla-sent'])) return wizard.render(request, wizard.tpl('mozilla.html'), {'addon': addon, 'form': form})
def support_author(request, contribution, wizard): addon = contribution.addon form = forms.ContactForm(request.POST or None) if request.method == 'POST': if form.is_valid(): template = jingo.render_to_string(request, wizard.tpl('emails/support-request.txt'), context={'contribution': contribution, 'addon': addon, 'form': form, 'user': request.amo_user}) log.info('Support request to dev. by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Support Request for %s' % addon.name), template, request.amo_user.email, [smart_str(addon.support_email)]) return redirect(reverse('users.support', args=[contribution.pk, 'author-sent'])) return wizard.render(request, wizard.tpl('author.html'), {'addon': addon, 'webapp': addon.is_webapp(), 'form': form})
def tally_job_results(job_id, **kw): sql = """select sum(1), sum(case when completed IS NOT NULL then 1 else 0 end) from validation_result where validation_job_id=%s""" cursor = connection.cursor() cursor.execute(sql, [job_id]) total, completed = cursor.fetchone() if completed == total: # The job has finished. job = ValidationJob.objects.get(pk=job_id) job.update(completed=datetime.now()) if job.finish_email: send_mail( u'Behold! Validation results for %s %s->%s' % (amo.APP_IDS[job.application.id].pretty, job.curr_max_version.version, job.target_version.version), textwrap.dedent(""" Aww yeah %s """ % absolutify(reverse('zadmin.validation'))), from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[job.finish_email])
def refund_reason(request, contribution, wizard): addon = contribution.addon if not 'request' in wizard.get_progress(): return redirect('users.support', contribution.pk, 'request') if contribution.is_instant_refund(): paypal.refund(contribution.paykey) paypal_log.info('Refund issued for contribution %r' % contribution.pk) # Note: we have to wait for PayPal to issue an IPN before it's # completely refunded. messages.success(request, _('Refund is being processed.')) return redirect('users.purchases') form = forms.ContactForm(request.POST or None) if request.method == 'POST' and form.is_valid(): url = absolutify( urlparams(addon.get_dev_url('issue_refund'), transaction_id=contribution.transaction_id)) template = jingo.render_to_string( request, wizard.tpl('emails/refund-request.txt'), context={ 'addon': addon, 'form': form, 'user': request.amo_user, 'contribution': contribution, 'refund_url': url }) log.info('Refund request sent by user: %s for addon: %s' % (request.amo_user.pk, addon.pk)) # L10n: %s is the addon name. send_mail(_(u'New Refund Request for %s' % addon.name), template, settings.NOBODY_EMAIL, [smart_str(addon.support_email)]) return redirect( reverse('users.support', args=[contribution.pk, 'refund-sent'])) return wizard.render(request, wizard.tpl('refund.html'), {'form': form})
def test_user_setting_unchecked(self): user = UserProfile.objects.all()[0] to = user.email n = users.notifications.NOTIFICATIONS_BY_SHORT['reply'] UserNotification.objects.get_or_create(notification_id=n.id, user=user, enabled=False) # Confirm we're reading from the database eq_(UserNotification.objects.filter(notification_id=n.id).count(), 1) success = send_mail('test subject', 'test body', perm_setting='reply', recipient_list=[to], fail_silently=False) assert success, "Email wasn't sent" eq_(len(mail.outbox), 0)
def test_user_mandatory(self): """ Make sure there's no unsubscribe link in mandatory emails. """ user = UserProfile.objects.all()[0] to = user.email n = users.notifications.NOTIFICATIONS_BY_SHORT['individual_contact'] UserNotification.objects.get_or_create(notification_id=n.id, user=user, enabled=True) assert n.mandatory, "Notification isn't mandatory" success = send_mail('test subject', 'test body', perm_setting=n, recipient_list=[to], fail_silently=False) body = mail.outbox[0].body assert "Unsubscribe:" not in body assert "You can't unsubscribe from" in body
def test_user_setting_default(self): user = UserProfile.objects.all()[0] to = user.email # Confirm there's nothing in the DB and we're using the default eq_(UserNotification.objects.count(), 0) # Make sure that this is True by default setting = users.notifications.NOTIFICATIONS_BY_SHORT['reply'] eq_(setting.default_checked, True) success = send_mail('test subject', 'test body', perm_setting='reply', recipient_list=[to], fail_silently=False) assert success, "Email wasn't sent" eq_(len(mail.outbox), 1) eq_(mail.outbox[0].body.count('users/unsubscribe'), 1) # bug 676601
def test_send_string(self): to = '*****@*****.**' with self.assertRaises(ValueError): send_mail('subj', 'body', recipient_list=to)
def notify_success(version_pks, job_pk, data, **kw): log.info('[%s@None] Updating max version for job %s.' % (len(version_pks), job_pk)) job = ValidationJob.objects.get(pk=job_pk) set_user(get_task_user()) for version in Version.objects.filter(pk__in=version_pks): addon = version.addon file_pks = version.files.values_list('pk', flat=True) errors = (ValidationResult.objects.filter( validation_job=job, file__pk__in=file_pks).values_list('errors', flat=True)) if any(errors): log.info('Version %s for addon %s not updated, ' 'one of the files did not pass validation' % (version.pk, version.addon.pk)) continue app_flag = False dry_run = data['preview_only'] for app in version.apps.filter( application=job.curr_max_version.application): if (app.max.version == job.curr_max_version.version and job.target_version.version != app.max.version): log.info('Updating version %s%s for addon %s from version %s ' 'to version %s' % (version.pk, ' [DRY RUN]' if dry_run else '', version.addon.pk, job.curr_max_version.version, job.target_version.version)) app.max = job.target_version if not dry_run: app.save() app_flag = True else: log.info('Version %s for addon %s not updated, ' 'current max version is %s not %s' % (version.pk, version.addon.pk, app.max.version, job.curr_max_version.version)) if app_flag: results = job.result_set.filter(file__version=version) context = get_context(addon, version, job, results) for author in addon.authors.all(): log.info(u'Emailing %s%s for addon %s, version %s about ' 'success from bulk validation job %s' % (author.email, ' [PREVIEW]' if dry_run else '', addon.pk, version.pk, job_pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[author.email]) if dry_run: job.preview_success_mail(*args, **kwargs) else: send_mail(*args, **kwargs) amo.log(amo.LOG.BULK_VALIDATION_UPDATED, version.addon, version, details={ 'version': version.version, 'target': job.target_version.version })
def _notify(context): """ Notify the admins or the developers what happened. Performing a sanity check in case something went wrong. """ log.info('Completed run of paypal addon checks: %s' % context['checked']) failure_list = [] if context['limit'] and context['rate'] > context['limit']: # This is too cope with something horrible going wrong like, paypal # goes into maintenance mode, or netops changes something and all of # a sudden all the apps start failing their paypal checks. # # It would really suck if one errant current job disabled every app # on the Marketplace. So this is an attempt to sanity check this. for f in context['failed_addons']: failure_list.append(absolutify(f.addon.get_url_path())) context['failure_list'] = failure_list log.info('Too many failed: %s%%, aborting.' % context['limit']) template = 'market/emails/check_error.txt' send_mail('Cron job error on checking addons', env.get_template(template).render(context), recipient_list=[settings.FLIGTAR], from_email=settings.NOBODY_EMAIL) else: if not context['failed_addons']: return for f in context['failed_addons']: addon = f.addon url = absolutify(addon.get_url_path()) # Add this to a list so we can tell the admins who got disabled. failure_list.append(url) if not context['do_disable']: continue # Write to the developers log that it failed to pass and update # the status of the addon. amo.log(amo.LOG.PAYPAL_FAILED, addon, user=get_task_user()) addon.update(status=amo.STATUS_DISABLED) authors = [u.email for u in addon.authors.all()] if addon.is_webapp(): template = 'market/emails/check_developer_app.txt' subject = _('App disabled on the Firefox Marketplace.') else: template = 'market/emails/check_developer_addon.txt' subject = _('Add-on disabled on the Firefox Marketplace.') # Now email the developer and tell them the bad news. send_mail(subject, env.get_template(template).render({ 'addon': addon, 'errors': json.loads(f.failure_data) }), recipient_list=authors, from_email=settings.NOBODY_EMAIL) context['failure_list'] = failure_list # Now email the admins and tell them what happened. template = 'market/emails/check_summary.txt' send_mail('Cron job disabled %s add-ons' % context['failed'], env.get_template(template).render(context), recipient_list=[settings.FLIGTAR], from_email=settings.NOBODY_EMAIL)
def notify_compatibility_chunk(users, job, data, **kw): log.info('[%s@%s] Sending notification mail for job %s.' % (len(users), notify_compatibility.rate_limit, job.pk)) set_user(get_task_user()) dry_run = data['preview_only'] app_id = job.target_version.application stats = collections.defaultdict(int) stats['processed'] = 0 stats['is_dry_run'] = int(dry_run) for user in users: stats['processed'] += 1 try: for a in chain(user.passing_addons, user.failing_addons): try: results = job.result_set.filter(file__version__addon=a) a.links = [ absolutify( reverse('devhub.bulk_compat_result', args=[a.slug, r.pk])) for r in results ] v = a.current_version or a.latest_version a.compat_link = absolutify( reverse('devhub.versions.edit', args=[a.pk, v.pk])) except: task_error = sys.exc_info() log.error( u'Bulk validation email error for user %s, ' u'addon %s: %s: %s' % (user.email, a.slug, task_error[0], task_error[1]), exc_info=False) context = Context({ 'APPLICATION': unicode(amo.APP_IDS[job.application].pretty), 'VERSION': job.target_version.version, 'PASSING_ADDONS': user.passing_addons, 'FAILING_ADDONS': user.failing_addons, }) log.info( u'Emailing %s%s for %d addons about ' 'bulk validation job %s' % (user.email, ' [PREVIEW]' if dry_run else '', len(user.passing_addons) + len(user.failing_addons), job.pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email]) if dry_run: job.preview_notify_mail(*args, **kwargs) else: stats['author_emailed'] += 1 send_mail(*args, **kwargs) amo.log(amo.LOG.BULK_VALIDATION_USER_EMAILED, user, details={ 'passing': [a.id for a in user.passing_addons], 'failing': [a.id for a in user.failing_addons], 'target': job.target_version.version, 'application': app_id }) except: task_error = sys.exc_info() log.error(u'Bulk validation email error for user %s: %s: %s' % (user.email, task_error[0], task_error[1]), exc_info=False) log.info('[%s@%s] bulk email stats for job %s: {%s}' % (len(users), notify_compatibility.rate_limit, job.pk, ', '.join( '%s: %s' % (k, stats[k]) for k in sorted(stats.keys()))))
def test_send_multilines_subjects(self): send_mail('test\nsubject', 'test body', from_email='*****@*****.**', recipient_list=['*****@*****.**']) eq_('test subject', mail.outbox[0].subject, 'Subject not stripped')
def _mail(self, template, subject, context): template = env.get_template(template) body = template.render(context) send_mail(subject, body, settings.MARKETPLACE_EMAIL, [self.user.email], fail_silently=True)
def notify_success(version_pks, job_pk, data, **kw): log.info('[%s@%s] Updating max version for job %s.' % (len(version_pks), notify_success.rate_limit, job_pk)) job = ValidationJob.objects.get(pk=job_pk) set_user(get_task_user()) dry_run = data['preview_only'] stats = collections.defaultdict(int) stats['processed'] = 0 stats['is_dry_run'] = int(dry_run) for version in Version.objects.filter(pk__in=version_pks): stats['processed'] += 1 addon = version.addon file_pks = version.files.values_list('pk', flat=True) errors = (ValidationResult.objects.filter( validation_job=job, file__pk__in=file_pks).values_list('errors', flat=True)) if any(errors): stats['invalid'] += 1 log.info('Version %s for addon %s not updated, ' 'one of the files did not pass validation' % (version.pk, version.addon.pk)) continue app_flag = False for app in version.apps.filter( application=job.curr_max_version.application): if (app.max.version_int >= job.curr_max_version.version_int and app.max.version_int < job.target_version.version_int): stats['bumped'] += 1 log.info('Updating version %s%s for addon %s from version %s ' 'to version %s' % (version.pk, ' [DRY RUN]' if dry_run else '', version.addon.pk, job.curr_max_version.version, job.target_version.version)) app.max = job.target_version if not dry_run: app.save() app_flag = True else: stats['missed_targets'] += 1 log.info('Version %s for addon %s not updated, ' 'current max version is %s not %s' % (version.pk, version.addon.pk, app.max.version, job.curr_max_version.version)) if app_flag: results = job.result_set.filter(file__version=version) context = get_context(addon, version, job, results) for author in addon.authors.all(): log.info(u'Emailing %s%s for addon %s, version %s about ' 'success from bulk validation job %s' % (author.email, ' [PREVIEW]' if dry_run else '', addon.pk, version.pk, job_pk)) args = (Template(data['subject']).render(context), Template(data['text']).render(context)) kwargs = dict(from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[author.email]) if dry_run: job.preview_success_mail(*args, **kwargs) else: stats['author_emailed'] += 1 send_mail(*args, **kwargs) app_id = job.target_version.application.pk amo.log(amo.LOG.MAX_APPVERSION_UPDATED, version.addon, version, details={ 'version': version.version, 'target': job.target_version.version, 'application': app_id }) log.info('[%s@%s] bulk update stats for job %s: {%s}' % (len(version_pks), notify_success.rate_limit, job_pk, ', '.join( '%s: %s' % (k, stats[k]) for k in sorted(stats.keys()))))