def message_response(request): """AJAX view to send an email to the user of a response""" form = CrowdsourceMessageResponseForm(request.POST) if form.is_valid(): response = form.cleaned_data['response'] if not request.user.has_perm('crowdsource.change_crowdsource', response.crowdsource): return JsonResponse({'error': 'permission denied'}, status=403) if not response.user or not response.user.email: return JsonResponse({'error': 'no email'}, status=400) msg = TemplateEmail( subject=form.cleaned_data['subject'], from_email='*****@*****.**', reply_to=[request.user.email], user=response.user, text_template='crowdsource/email/message_user.txt', html_template='crowdsource/email/message_user.html', extra_context={ 'body': form.cleaned_data['body'], 'assignment': response.crowdsource, 'from_user': request.user, }, ) msg.send() return JsonResponse({'status': 'ok'}) else: return JsonResponse({'error': 'form invalid'}, status=400)
def agency_redirect_login(request, agency_slug, agency_idx, foia_slug, foia_idx): """View to redirect agency users to the correct page or offer to resend them their login token""" agency = get_object_or_404(Agency, slug=agency_slug, pk=agency_idx) foia = get_object_or_404( FOIARequest, agency=agency, slug=foia_slug, pk=foia_idx, ) valid_emails = agency.get_all_known_emails() if request.method == 'POST': email = request.POST.get('email', '') if email.lower() in valid_emails: msg = TemplateEmail( subject='Login Token', from_email='*****@*****.**', to=[email], text_template='accounts/email/login_token.txt', html_template='accounts/email/login_token.html', extra_context={ 'reply_link': foia.get_agency_reply_link(email=email), }) msg.send(fail_silently=False) messages.success( request, 'Fresh login token succesfully sent to %s. ' 'Please check your email' % email, ) else: messages.error(request, 'Invalid email') return redirect(foia) authed = request.user.is_authenticated() agency_user = authed and request.user.profile.acct_type == 'agency' agency_match = agency_user and request.user.profile.agency == agency email = request.GET.get('email', '') valid = email.lower() in valid_emails if agency_match: return redirect(foia) elif agency_user: return redirect('foia-agency-list') elif authed: return redirect('index') else: return render( request, 'accounts/agency_redirect_login.html', { 'email': email, 'valid': valid }, )
def support(user, message, _task): """Send a response to a user about a task.""" context = {'message': message, 'task': _task} notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/support.txt', html_template='message/notification/support.html', subject=u'Support #%d' % _task.id) notification.send(fail_silently=False)
def notify_project_contributor(user, project, added_by): """Notify a user that they were added as a contributor to a project.""" context = {'project': project, 'added_by': added_by} notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/project.txt', html_template='message/notification/project.html', subject=u'Added to a project') notification.send(fail_silently=False)
def send_notification(self): """Send the user the link to their file""" notification = TemplateEmail( user=self.user, extra_context=self.get_context(), text_template=self.text_template, html_template=self.html_template, subject=self.subject, ) notification.send(fail_silently=False)
def gift(to_user, from_user, gift_description): """Notify the user when they have been gifted requests.""" context = {'from': from_user, 'gift': gift_description} notification = TemplateEmail( user=to_user, extra_context=context, text_template='message/notification/gift.txt', html_template='message/notification/gift.html', subject=u'You got a gift!') notification.send(fail_silently=False)
def reply(self, text, action="reply"): """Send an email reply to the user that raised the flag.""" send_to = [contributor.email for contributor in self.project.contributors.all()] project_email = TemplateEmail( to=send_to, extra_context={"action": action, "message": text, "task": self}, subject="%s %s" % (self.project, action), text_template="message/project/%s.txt" % action, html_template="message/project/%s.html" % action, ) project_email.send(fail_silently=False) return project_email
def email_change(user, old_email): """Notify the user when their email is changed.""" context = {'old_email': old_email, 'new_email': user.email} notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/email_change.txt', html_template='message/notification/email_change.html', subject=u'Changed email address') notification.to.append( old_email) # Send to both the new and old email addresses notification.send(fail_silently=False)
def email_verify(user): """Verify the user's email by sending them a message.""" url = reverse('acct-verify-email') key = user.profile.generate_confirmation_key() context = {'verification_link': user.profile.wrap_url(url, key=key)} notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/email_verify.txt', html_template='message/notification/email_verify.html', subject=u'Verify your email') notification.send(fail_silently=False)
def send_intro_email(self, user): """Send an intro email to the user upon crowdfund creation""" msg = TemplateEmail( subject="Crowdfund Campaign Launched", user=user, bcc=["diagnostics@muckrock", "info@muckrock"], text_template="crowdfund/email/intro.txt", html_template="crowdfund/email/intro.html", extra_context={ "amount": int(self.payment_required), "url": self.get_crowdfund_object().get_absolute_url(), }, ) msg.send(fail_silently=False)
def welcome(user): """Send a welcome notification to a new user. Hello!""" verification_url = reverse('acct-verify-email') key = user.profile.generate_confirmation_key() context = { 'verification_link': user.profile.wrap_url(verification_url, key=key) } notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/welcome.txt', html_template='message/notification/welcome.html', subject=u'Welcome to MuckRock!') notification.send(fail_silently=False)
def welcome_miniregister(user): """Send a welcome notification to a new users who signed up with miniregister. Provide them a link to verify their email and update their username/password.""" completion_url = reverse('accounts-complete-registration') key = user.profile.generate_confirmation_key() context = { 'completion_url': user.profile.wrap_url(completion_url, key=key) } notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/welcome_miniregister.txt', html_template='message/notification/welcome_miniregister.html', subject=u'Welcome to MuckRock!') notification.send(fail_silently=False)
def send_intro_email(self, user): """Send an intro email to the user upon crowdfund creation""" msg = TemplateEmail( subject='Crowdfund Campaign Launched', from_email='*****@*****.**', user=user, bcc=['diagnostics@muckrock', 'info@muckrock'], text_template='crowdfund/email/intro.txt', html_template='crowdfund/email/intro.html', extra_context={ 'amount': int(self.payment_required), 'url': self.get_crowdfund_object().get_absolute_url(), } ) msg.send(fail_silently=False)
def reply(self, text, action='reply'): """Send an email reply to the user that raised the flag.""" send_to = [ contributor.email for contributor in self.project.contributors.all() ] project_email = TemplateEmail( to=send_to, extra_context={'action': action, 'message': text, 'task': self}, subject=u'%s %s' % (self.project, action), text_template='message/project/%s.txt' % action, html_template='message/project/%s.html' % action ) project_email.send(fail_silently=False) return project_email
def support(user_id, message, task_id): """Send a response to a user about a flag task.""" # pylint: disable=import-outside-toplevel from muckrock.task.models import FlaggedTask user = User.objects.get(id=user_id) task_ = FlaggedTask.objects.get(id=task_id) context = {"message": message, "task": task_} notification = TemplateEmail( user=user, extra_context=context, text_template="message/notification/support.txt", html_template="message/notification/support.html", subject="Support #%d" % task_.id, ) notification.send(fail_silently=False)
def notify_project_contributor(user_id, project_id, added_by_id): """Notify a user that they were added as a contributor to a project.""" # pylint: disable=import-outside-toplevel from muckrock.project.models import Project user = User.objects.get(id=user_id) project = Project.objects.get(id=project_id) added_by = User.objects.get(id=added_by_id) context = {"project": project, "added_by": added_by} notification = TemplateEmail( user=user, extra_context=context, text_template="message/notification/project.txt", html_template="message/notification/project.html", subject="Added to a project", ) notification.send(fail_silently=False)
def contact_user(request, idx): """Let staff send messages to users""" user = get_object_or_404(User, pk=idx) if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): email = TemplateEmail( user=user, extra_context={'message': form.cleaned_data['message']}, subject=form.cleaned_data['subject'], text_template='message/accounts/contact.txt', html_template='message/accounts/contact.html', ) email.send(fail_silently=False) messages.success(request, 'Message sent!') else: messages.error(request, 'Message failed!') return redirect(user)
def contact_user(request, idx): """Let staff send messages to users""" user = get_object_or_404(User, pk=idx) if request.method == "POST": form = ContactForm(request.POST) if form.is_valid(): email = TemplateEmail( user=user, extra_context={"message": form.cleaned_data["message"]}, subject=form.cleaned_data["subject"], text_template="message/accounts/contact.txt", html_template="message/accounts/contact.html", ) email.send(fail_silently=False) messages.success(request, "Message sent!") else: messages.error(request, "Message failed!") return redirect(user)
def _contact_user(self, request, foia): """Allow an admin to message the foia's owner""" text = request.POST.get('text') if request.user.is_staff and text: context = { 'text': text, 'foia_url': foia.user.profile.wrap_url(foia.get_absolute_url()), 'foia_title': foia.title, } email = TemplateEmail( user=foia.user, extra_context=context, text_template='message/notification/contact_user.txt', html_template='message/notification/contact_user.html', subject='Message from MuckRock', ) email.send(fail_silently=False) messages.success(request, 'Email sent to %s' % foia.user.email) return redirect(foia)
def contact_user(request, foia): """Allow an admin to message the foia's owner""" form = FOIAContactUserForm(request.POST) if request.user.is_staff and form.is_valid() and form.cleaned_data["text"]: context = { "text": form.cleaned_data["text"], "foia_url": foia.user.profile.wrap_url(foia.get_absolute_url()), "foia_title": foia.title, } email = TemplateEmail( user=foia.user, extra_context=context, text_template="message/notification/contact_user.txt", html_template="message/notification/contact_user.html", subject="Message from MuckRock", ) email.send(fail_silently=False) messages.success(request, "Email sent to %s" % foia.user.email) return _get_redirect(request, foia)
def failed_payment(invoice_id): """Notify a customer about a failed subscription invoice.""" invoice = stripe_retry_on_error( stripe.Invoice.retrieve, invoice_id, ) attempt = invoice.attempt_count subscription_type = get_subscription_type(invoice) profile = Profile.objects.get(customer_id=invoice.customer) user = profile.user # raise the failed payment flag on the profile profile.payment_failed = True profile.save() subject = u'Your payment has failed' org = None if subscription_type == 'org': org = Organization.objects.get(owner=user) if attempt == 4: # on last attempt, cancel the user's subscription and lower the failed payment flag if subscription_type == 'pro': profile.cancel_pro_subscription() elif subscription_type == 'org': org.cancel_subscription() profile.payment_failed = False profile.save() logger.info('%s subscription has been cancelled due to failed payment', user.username) subject = u'Your %s subscription has been cancelled' % subscription_type context = {'attempt': 'final', 'type': subscription_type, 'org': org} else: logger.info('Failed payment by %s, attempt %s', user.username, attempt) context = {'attempt': attempt, 'type': subscription_type, 'org': org} notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/failed_payment.txt', html_template='message/notification/failed_payment.html', subject=subject, ) notification.send(fail_silently=False)
def failed_payment(invoice_id): """Notify a customer about a failed subscription invoice.""" # pylint: disable=too-many-branches # pylint: disable=too-many-statements invoice = stripe_retry_on_error(stripe.Invoice.retrieve, invoice_id) attempt = invoice.attempt_count subscription_type = get_subscription_type(invoice) recurring_donation = None crowdfund = None email_to = [] if subscription_type == "donate": recurring_donation = RecurringDonation.objects.filter( subscription_id=invoice.subscription).first() if recurring_donation: user = recurring_donation.user if user is None: email_to = [recurring_donation.email] recurring_donation.payment_failed = True recurring_donation.save() else: user = None logger.error("No recurring crowdfund found for %s", invoice.subscription) elif subscription_type.startswith("crowdfund"): recurring_payment = RecurringCrowdfundPayment.objects.filter( subscription_id=invoice.subscription).first() if recurring_payment: user = recurring_payment.user if user is None: email_to = [recurring_payment.email] crowdfund = recurring_payment.crowdfund recurring_payment.payment_failed = True recurring_payment.save() else: user = None logger.error("No recurring crowdfund found for %s", invoice.subscription) else: # squarelet handles other types return subject = "Your payment has failed" context = { "attempt": attempt, "type": subscription_type, "crowdfund": crowdfund } if subscription_type.startswith("crowdfund"): context["type"] = "crowdfund" if attempt == 4: # on last attempt, cancel the user's subscription and lower the failed payment flag if subscription_type == "donate" and recurring_donation: recurring_donation.cancel() elif subscription_type.startswith("crowdfund") and recurring_payment: recurring_payment.cancel() logger.info("%s subscription has been cancelled due to failed payment", user) subject = "Your %s subscription has been cancelled" % subscription_type context["attempt"] = "final" else: logger.info("Failed payment by %s, attempt %s", user, attempt) notification = TemplateEmail( user=user, to=email_to, extra_context=context, text_template="message/notification/failed_payment.txt", html_template="message/notification/failed_payment.html", subject=subject, ) notification.send(fail_silently=False)
def agency_redirect_login( request, agency_slug, agency_idx, foia_slug, foia_idx ): """View to redirect agency users to the correct page or offer to resend them their login token""" agency = get_object_or_404(Agency, slug=agency_slug, pk=agency_idx) foia = get_object_or_404( FOIARequest, agency=agency, slug=foia_slug, pk=foia_idx, ) if request.method == 'POST': email = request.POST.get('email', '') # valid if this email is associated with the agency valid = ( EmailAddress.objects.fetch(email).agencies.filter(id=agency.pk) .exists() ) if valid: msg = TemplateEmail( subject='Login Token', from_email='*****@*****.**', to=[email], text_template='accounts/email/login_token.txt', html_template='accounts/email/login_token.html', extra_context={ 'reply_link': foia.get_agency_reply_link(email=email), } ) msg.send(fail_silently=False) messages.success( request, 'Fresh login token succesfully sent to %s. ' 'Please check your email' % email, ) else: messages.error(request, 'Invalid email') return redirect(foia) authed = request.user.is_authenticated() agency_user = authed and request.user.profile.is_agency_user agency_match = agency_user and request.user.profile.agency == agency email = request.GET.get('email', '') # valid if this email is associated with the agency email_address = EmailAddress.objects.fetch(email) valid = ( email_address is not None and email_address.agencies.filter(id=agency.pk).exists() ) if agency_match: return redirect(foia.get_absolute_url() + "#agency-reply") elif agency_user: return redirect('foia-agency-list') elif authed: return redirect('index') else: return render( request, 'accounts/agency_redirect_login.html', {'email': email, 'valid': valid}, )
def agency_redirect_login(request, agency_slug, agency_idx, foia_slug, foia_idx): """View to redirect agency users to the correct page or offer to resend them their login token""" # pylint: disable=too-many-locals agency = get_object_or_404(Agency, slug=agency_slug, pk=agency_idx) foia = get_object_or_404(FOIARequest, agency=agency, slug=foia_slug, pk=foia_idx) if request.method == "POST": email = request.POST.get("email", "") # valid if this email is associated with the agency email_addr = EmailAddress.objects.fetch(email) valid = email_addr.agencies.filter(id=agency.pk).exists() if valid: # rate limit to once per 5 minutes new_uuid = uuid4() old_uuid = cache.get_or_set(f"token:{email_addr.pk}", new_uuid, 300) if new_uuid != old_uuid: messages.error(request, "Please wait 5 minutes to try again") return redirect(foia) msg = TemplateEmail( subject="Login Token", to=[email], text_template="accounts/email/login_token.txt", html_template="accounts/email/login_token.html", extra_context={ "reply_link": foia.get_agency_reply_link(email=email) }, ) msg.send(fail_silently=False) messages.success( request, "Fresh login token succesfully sent to %s. " "Please check your email" % email, ) else: messages.error(request, "Invalid email") return redirect(foia) authed = request.user.is_authenticated agency_user = authed and request.user.profile.is_agency_user agency_match = agency_user and request.user.profile.agency == agency email = request.GET.get("email", "") # valid if this email is associated with the agency email_address = EmailAddress.objects.fetch(email) valid = (email_address is not None and email_address.agencies.filter(id=agency.pk).exists()) if agency_match: return redirect(foia.get_absolute_url() + "#agency-reply") elif agency_user: return redirect("foia-agency-list") elif authed: return redirect("index") else: return render( request, "accounts/agency_redirect_login.html", { "email": email, "valid": valid }, )
def export_csv(foia_pks, user_pk): """Export a csv of the selected FOIA requests""" fields = ( (lambda f: f.user.username, 'User'), (lambda f: f.title, 'Title'), (lambda f: f.get_status_display(), 'Status'), (lambda f: settings.MUCKROCK_URL + f.get_absolute_url(), 'URL'), (lambda f: f.jurisdiction.name, 'Jurisdiction'), (lambda f: f.jurisdiction.pk, 'Jurisdiction ID'), ( lambda f: f.jurisdiction.get_level_display(), 'Jurisdiction Level', ), ( lambda f: f.jurisdiction.parent.name if f.jurisdiction.level == 'l' else f.jurisdiction.name, 'Jurisdiction State', ), (lambda f: f.agency.name if f.agency else '', 'Agency'), (lambda f: f.agency.pk if f.agency else '', 'Agency ID'), (lambda f: f.date_followup, 'Followup Date'), (lambda f: f.date_estimate, 'Estimated Completion Date'), (lambda f: f.composer.requested_docs, 'Requested Documents'), (lambda f: f.current_tracking_id(), 'Tracking Number'), (lambda f: f.embargo, 'Embargo'), (lambda f: f.days_since_submitted, 'Days since submitted'), (lambda f: f.days_since_updated, 'Days since updated'), (lambda f: f.project_names, 'Projects'), (lambda f: f.tag_names, 'Tags'), (lambda f: f.price, 'Price'), (lambda f: f.composer.datetime_submitted, 'Date Submitted'), (lambda f: f.date_due, 'Date Due'), (lambda f: f.datetime_done, 'Date Done'), ) foias = (FOIARequest.objects.filter(pk__in=foia_pks).select_related( 'composer__user', 'agency__jurisdiction__parent', ).only( 'composer__user__username', 'title', 'status', 'slug', 'agency__jurisdiction__name', 'agency__jurisdiction__slug', 'agency__jurisdiction__id', 'agency__jurisdiction__parent__name', 'agency__jurisdiction__parent__id', 'agency__name', 'agency__id', 'date_followup', 'date_estimate', 'embargo', 'composer__requested_docs', ).annotate( days_since_submitted=ExtractDay( Cast(Now() - F('composer__datetime_submitted'), DurationField())), days_since_updated=ExtractDay( Cast(Now() - F('datetime_updated'), DurationField())), project_names=StringAgg('projects__title', ',', distinct=True), tag_names=StringAgg('tags__name', ',', distinct=True), )) conn = S3Connection( settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY, ) bucket = conn.get_bucket(settings.AWS_STORAGE_BUCKET_NAME) today = date.today() file_key = 'exported_csv/{y:4d}/{m:02d}/{d:02d}/{md5}/requests.csv'.format( y=today.year, m=today.month, d=today.day, md5=md5('{}{}'.format( int(timestamp()), ''.join(str(pk) for pk in foia_pks[:100]), )).hexdigest(), ) key = bucket.new_key(file_key) with smart_open(key, 'wb') as csv_file: writer = csv.writer(csv_file) writer.writerow(f[1] for f in fields) for foia in foias.iterator(): writer.writerow(f[0](foia) for f in fields) key.set_acl('public-read') notification = TemplateEmail( user=User.objects.get(pk=user_pk), extra_context={'file': file_key}, text_template='message/notification/csv_export.txt', html_template='message/notification/csv_export.html', subject='Your CSV Export', ) notification.send(fail_silently=False)
def failed_payment(invoice_id): """Notify a customer about a failed subscription invoice.""" # pylint: disable=too-many-branches # pylint: disable=too-many-statements invoice = stripe_retry_on_error( stripe.Invoice.retrieve, invoice_id, ) attempt = invoice.attempt_count subscription_type = get_subscription_type(invoice) recurring_donation = None profile = None crowdfund = None if subscription_type == 'donate': recurring_donation = RecurringDonation.objects.filter( subscription_id=invoice.subscription, ).first() if recurring_donation: user = recurring_donation.user recurring_donation.payment_failed = True recurring_donation.save() else: user = None logger.error( 'No recurring crowdfund found for %s', invoice.subscription, ) elif subscription_type.startswith('crowdfund'): recurring_payment = RecurringCrowdfundPayment.objects.filter( subscription_id=invoice.subscription, ).first() if recurring_payment: user = recurring_payment.user crowdfund = recurring_payment.crowdfund recurring_payment.payment_failed = True recurring_payment.save() else: user = None logger.error( 'No recurring crowdfund found for %s', invoice.subscription, ) else: profile = Profile.objects.get(customer_id=invoice.customer) user = profile.user # raise the failed payment flag on the profile profile.payment_failed = True profile.save() subject = u'Your payment has failed' org = None if subscription_type == 'org': org = Organization.objects.get(owner=user) context = { 'attempt': attempt, 'type': subscription_type, 'org': org, 'crowdfund': crowdfund, } if subscription_type.startswith('crowdfund'): context['type'] = 'crowdfund' if attempt == 4: # on last attempt, cancel the user's subscription and lower the failed payment flag if subscription_type == 'pro': profile.cancel_pro_subscription() elif subscription_type == 'org': org.cancel_subscription() elif subscription_type == 'donate' and recurring_donation: recurring_donation.cancel() elif subscription_type.startswith('crowdfund') and recurring_payment: recurring_payment.cancel() if subscription_type in ('pro', 'org'): profile.payment_failed = False profile.save() logger.info('%s subscription has been cancelled due to failed payment', user) subject = u'Your %s subscription has been cancelled' % subscription_type context['attempt'] = 'final' else: logger.info('Failed payment by %s, attempt %s', user, attempt) notification = TemplateEmail( user=user, extra_context=context, text_template='message/notification/failed_payment.txt', html_template='message/notification/failed_payment.html', subject=subject, ) notification.send(fail_silently=False)