def send_incomplete_pledge_emails(self): # For every IncompletePledge instance that has not yet been # sent a reminder email, send one. Wait at least some hours # after the user left the page. before = timezone.now() - timedelta(hours=3) for ip in IncompletePledge.objects.filter(created__lt=before, sent_followup_at=None): context = { } context.update(get_branding(ip.via_campaign.brand)) context.update({ "incomplete_pledge": ip, "url": ip.get_return_url(), "trigger": ip.trigger, }) # Send email. send_mail( "contrib/mail/incomplete_pledge", get_branding(ip.via_campaign.brand)["MAIL_FROM_EMAIL"], [ip.email], context, headers={ "Reply-To": get_branding(ip.via_campaign.brand)["CONTACT_EMAIL"], }) # Record that it was sent. ip.sent_followup_at = timezone.now() ip.save()
def send_incomplete_pledge_emails(self): # For every IncompletePledge instance that has not yet been # sent a reminder email, send one. Wait at least some hours # after the user left the page. before = timezone.now() - timedelta(hours=3) for ip in IncompletePledge.objects.filter(created__lt=before, sent_followup_at=None): context = {} context.update(get_branding(ip.via_campaign.brand)) context.update({ "incomplete_pledge": ip, "url": ip.get_return_url(), "trigger": ip.trigger, }) # Send email. send_mail("contrib/mail/incomplete_pledge", get_branding(ip.via_campaign.brand)["MAIL_FROM_EMAIL"], [ip.email], context, headers={ "Reply-To": get_branding(ip.via_campaign.brand)["CONTACT_EMAIL"], }) # Record that it was sent. ip.sent_followup_at = timezone.now() ip.save()
def render_pledge_template(request, pledge, campaign, show_long_title=False, response_page=False): # Get the user's pledges, if any, on any trigger tied to this campaign. import django.template template = django.template.loader.get_template("contrib/contrib.html") ctx = { "response_page": response_page, "show_long_title": show_long_title, "pledge": pledge, "campaign": campaign, "execution": PledgeExecution.objects.filter(pledge=pledge).first(), "contribs": sorted(Contribution.objects.filter( pledge_execution__pledge=pledge).select_related( "action", "recipient"), key=lambda c: (c.recipient_type.value, c.action.name_sort)), "share_url": request.build_absolute_uri(pledge.via_campaign.get_short_url()), } from itfsite.middleware import get_branding ctx.update(get_branding(request)) return template.render(ctx)
def send_pledge_email(self, pre_or_post, pledge): # What will happen when the pledge is executed? recipients = get_pledge_recipients(pledge) if len(recipients) == 0: # This pledge will result in nothing happening. There is # no need to email. return recip_contribs, fees, total_charge = compute_charge(pledge, recipients) context = {} context.update(get_branding(pledge.via_campaign.brand)) context.update({ "profile": pledge.profile, # used in salutation in email_template "pledge": pledge, "until": Pledge.current_algorithm()['pre_execution_warn_time'][1], "total_charge": total_charge, }) # Send email. send_mail("contrib/mail/%s_execution" % pre_or_post, context["MAIL_FROM_EMAIL"], [pledge.user.email], context) # Record that it was sent. field_name = "%s_execution_email_sent_at" % pre_or_post setattr(pledge, field_name, timezone.now()) pledge.save(update_fields=[field_name])
def send_pledge_email(self, pre_or_post, pledge): # What will happen when the pledge is executed? recipients = get_pledge_recipients(pledge) if len(recipients) == 0: # This pledge will result in nothing happening. There is # no need to email. return recip_contribs, fees, total_charge = compute_charge(pledge, recipients) context = { } context.update(get_branding(pledge.via_campaign.brand)) context.update({ "profile": pledge.profile, # used in salutation in email_template "pledge": pledge, "until": Pledge.current_algorithm()['pre_execution_warn_time'][1], "total_charge": total_charge, }) # Send email. send_mail( "contrib/mail/%s_execution" % pre_or_post, context["MAIL_FROM_EMAIL"], [pledge.user.email], context) # Record that it was sent. field_name = "%s_execution_email_sent_at" % pre_or_post setattr(pledge, field_name, timezone.now()) pledge.save(update_fields=[field_name])
def render2(request, template, *args, **kwargs): import os.path from django.template import TemplateDoesNotExist template1 = os.path.join('branding', get_branding(request)['BRAND_ID'], 'templates', template) try: # Try a brand-specific template. return render(request, template1, *args, **kwargs) except TemplateDoesNotExist as e: # Raise if the template that doesn't exist is one that is loaded by an include tag. if hasattr(e, 'django_template_source'): raise # Try a default template. return render(request, template, *args, **kwargs)
def campaign(request, id, action, api_format_ext): # get the object, make sure it is for the right brand that the user is viewing campaign = get_object_or_404(Campaign, id=id, brand=get_branding(request)['BRAND_INDEX']) # redirect to canonical URL if slug does not match # (pass along any query string variables like utm_campaign; when # a .json page is requested, the canonical path omits the slug) if not api_format_ext: canonical_path = campaign.get_absolute_url() + (api_format_ext or "") else: canonical_path = "/a/%d%s" % (campaign.id, api_format_ext) if action: canonical_path += "/" + action qs = (("?" + request.META['QUERY_STRING']) if request.META['QUERY_STRING'] else "") if request.path != canonical_path: @anonymous_view def redirector(request): return redirect(canonical_path + qs) return redirector(request) # The rest of this view is handled in the next two functions. if action is None: f = campaign_show elif api_format_ext is not None: raise Http404() elif action == "contribute": f = campaign_action_trigger else: raise Http404() # Cache? if campaign.status == CampaignStatus.Draft: # When a campaign is in draft status, we won't # cache the output to allow the user editing the # campaign's settings to reload the page to see # updated settings. pass else: # As soon as the campaign exits draft status we'll # mark the response as cachable so that the http # layer strongly caches the output. pass # f = anonymous_view(f) return f(request, campaign, api_format_ext == ".json")
def email_confirmation_response_view(self, request): # The user may be new, so take them to a welcome page. from itfsite.middleware import get_branding from itfsite.accounts import first_time_confirmed_user # Redirect to the most recent pledge of this user on # the same brand site as this request is on. from contrib.models import Pledge pledge = Pledge.objects.filter( user=self.confirmed_user, via_campaign__brand=get_branding(request)['BRAND_INDEX'], ).order_by('-created').first() return first_time_confirmed_user(request, self.confirmed_user, pledge.get_absolute_url() if pledge else "/home")
def mailer(context): from itfsite.middleware import get_branding context.update(get_branding(brand_id)) context.update({ "profile": profile, # used in salutation in email_template "pledge": pledge, "first_try": not self.sentConfirmationEmail, }) from htmlemailer import send_mail send_mail( template, context["MAIL_FROM_EMAIL"], [context['email']], context)
def user_home(request): from contrib.models import Pledge, PledgeStatus # Get the user's actions that took place on the brand site that the user is actually on. brand_filter = { "via_campaign__brand": get_branding(request)['BRAND_INDEX'] } pledges = Pledge.objects.filter(user=request.user, **brand_filter).prefetch_related() actions = list(pledges) actions.sort(key=lambda obj: obj.created, reverse=True) # Get the user's total amount of open pledges, i.e. their total possible # future credit card charges / campaign contributions. open_pledges = pledges.filter(status=PledgeStatus.Open) if len(open_pledges) == 0: total_pledged = 0.0 else: total_pledged = open_pledges.aggregate( total_pledged=Sum('amount'))['total_pledged'] # Get the user's total amount of executed campaign contributions. total_contribs = pledges.filter(status=PledgeStatus.Executed) if len(total_contribs) == 0: total_contribs = 0.0 else: total_contribs = total_contribs.aggregate( total_contribs=Sum('execution__charged'))['total_contribs'] # Get the user's distinct ContributorInfo objects on *open* Pledges, # indicating future submitted info. if len(open_pledges) == 0: profiles = [] else: profiles = set(p.profile for p in open_pledges) return render( request, "itfsite/user_home.html", { 'actions': actions, 'profiles': profiles, 'total_pledged': total_pledged, 'total_contribs': total_contribs, 'notifs_freq': request.user.notifs_freq.name, })
def campaign(request, id, action, api_format_ext): # get the object, make sure it is for the right brand that the user is viewing campaign = get_object_or_404(Campaign, id=id, brand=get_branding(request)['BRAND_INDEX']) # redirect to canonical URL if slug does not match # (pass along any query string variables like utm_campaign; when # a .json page is requested, the canonical path omits the slug) if not api_format_ext: canonical_path = campaign.get_absolute_url() + (api_format_ext or "") else: canonical_path = "/a/%d%s" % (campaign.id, api_format_ext) if action: canonical_path += "/" + action qs = (("?"+request.META['QUERY_STRING']) if request.META['QUERY_STRING'] else "") if request.path != canonical_path: @anonymous_view def redirector(request): return redirect(canonical_path+qs) return redirector(request) # The rest of this view is handled in the next two functions. if action is None: f = campaign_show elif api_format_ext is not None: raise Http404() elif action == "contribute": f = campaign_action_trigger else: raise Http404() # Cache? if campaign.status == CampaignStatus.Draft: # When a campaign is in draft status, we won't # cache the output to allow the user editing the # campaign's settings to reload the page to see # updated settings. pass else: # As soon as the campaign exits draft status we'll # mark the response as cachable so that the http # layer strongly caches the output. pass # f = anonymous_view(f) return f(request, campaign, api_format_ext == ".json")
def render_pledge_template(request, pledge, campaign, show_long_title=False, response_page=False): # Get the user's pledges, if any, on any trigger tied to this campaign. import django.template template = django.template.loader.get_template("contrib/contrib.html") ctx = { "response_page": response_page, "show_long_title": show_long_title, "pledge": pledge, "campaign": campaign, "execution": PledgeExecution.objects.filter(pledge=pledge).first(), "contribs": sorted(Contribution.objects.filter(pledge_execution__pledge=pledge).select_related("action", "recipient"), key=lambda c : (c.recipient_type.value, c.action.name_sort)), "share_url": request.build_absolute_uri(pledge.via_campaign.get_short_url()), } from itfsite.middleware import get_branding ctx.update(get_branding(request)) return template.render(ctx)
def user_home(request): from contrib.models import Pledge, PledgeStatus # Get the user's actions that took place on the brand site that the user is actually on. brand_filter = { "via_campaign__brand": get_branding(request)['BRAND_INDEX'] } pledges = Pledge.objects.filter(user=request.user, **brand_filter).prefetch_related() actions = list(pledges) actions.sort(key = lambda obj : obj.created, reverse=True) # Get the user's total amount of open pledges, i.e. their total possible # future credit card charges / campaign contributions. open_pledges = pledges.filter(status=PledgeStatus.Open) if len(open_pledges) == 0: total_pledged = 0.0 else: total_pledged = open_pledges.aggregate(total_pledged=Sum('amount'))['total_pledged'] # Get the user's total amount of executed campaign contributions. total_contribs = pledges.filter(status=PledgeStatus.Executed) if len(total_contribs) == 0: total_contribs = 0.0 else: total_contribs = total_contribs.aggregate(total_contribs=Sum('execution__charged'))['total_contribs'] # Get the user's distinct ContributorInfo objects on *open* Pledges, # indicating future submitted info. if len(open_pledges) == 0: profiles = [] else: profiles = set(p.profile for p in open_pledges) return render(request, "itfsite/user_home.html", { 'actions': actions, 'profiles': profiles, 'total_pledged': total_pledged, 'total_contribs': total_contribs, 'notifs_freq': request.user.notifs_freq.name, })
def homepage(request): # The site homepage. # What campaigns *can* we show (logically)? Open campaigns for the brand we're looking at. open_campaigns = Campaign.objects.filter( status=CampaignStatus.Open, brand=get_branding(request)['BRAND_INDEX']) # How many to show? count = 12 if not settings.DEBUG else 100 # Actually show recent campaigns (with some activity) + top performing campaigns. # # To efficiently query top performing campaigns we order by the sum of total_pledged (which only contains # Pledges made prior to trigger execution) and total_contributions (which only exists after the trigger # has been executed), since we don't have a field that just counts a simple total (ugh). open_campaigns = open_campaigns\ .annotate(total=Sum('contrib_triggers__total_pledged')+Sum('contrib_triggers__execution__total_contributions'))\ .exclude(total=None) # no Trigger associated with the Campaign open_campaigns = set( # uniqify c for c in list(open_campaigns.order_by('-created')[0:count]) + list(open_campaigns.order_by("-total")[0:count]) if c.total > 0) if len(open_campaigns) > 0: # Order by a mix of recency and popularity. Prefer recency a bit. from math import sqrt newest = max(c.created for c in open_campaigns) oldest = min(c.created for c in open_campaigns) max_t = max(float(getattr(c, 'total', 0)) for c in open_campaigns) or 1.0 # Decimal => float open_campaigns = sorted( open_campaigns, key=lambda campaign: 1.1 - 1.1 * (newest - campaign.created).total_seconds() / ( (newest - oldest).total_seconds() or 1) + sqrt( float(getattr(campaign, 'total', 0)) / max_t), reverse=True)[0:count] return render2(request, "itfsite/homepage.html", { "open_campaigns": open_campaigns, })
def homepage(request): # The site homepage. # What campaigns *can* we show (logically)? Open campaigns for the brand we're looking at. open_campaigns = Campaign.objects.filter(status=CampaignStatus.Open, brand=get_branding(request)['BRAND_INDEX']) # How many to show? count = 12 if not settings.DEBUG else 100 # Actually show recent campaigns (with some activity) + top performing campaigns. # # To efficiently query top performing campaigns we order by the sum of total_pledged (which only contains # Pledges made prior to trigger execution) and total_contributions (which only exists after the trigger # has been executed), since we don't have a field that just counts a simple total (ugh). open_campaigns = open_campaigns\ .annotate(total=Sum('contrib_triggers__total_pledged')+Sum('contrib_triggers__execution__total_contributions'))\ .exclude(total=None) # no Trigger associated with the Campaign open_campaigns = set( # uniqify c for c in list(open_campaigns.order_by('-created')[0:count]) + list(open_campaigns.order_by("-total")[0:count]) if c.total > 0 ) if len(open_campaigns) > 0: # Order by a mix of recency and popularity. Prefer recency a bit. from math import sqrt newest = max(c.created for c in open_campaigns) oldest = min(c.created for c in open_campaigns) max_t = max(float(getattr(c, 'total', 0)) for c in open_campaigns) or 1.0 # Decimal => float open_campaigns = sorted(open_campaigns, key = lambda campaign: 1.1 - 1.1*(newest-campaign.created).total_seconds()/((newest-oldest).total_seconds() or 1) + sqrt(float(getattr(campaign, 'total', 0)) / max_t) , reverse=True)[0:count] return render2(request, "itfsite/homepage.html", { "open_campaigns": open_campaigns, })
def campaign_show(request, campaign, is_json_api): # What Trigger and TriggerCustomization should we show? trigger, tcust = campaign.get_active_trigger() if trigger: outcome_strings = (tcust or trigger).outcome_strings() for i, os in enumerate(outcome_strings): os["id"] = i else: outcome_strings = [] # for .json calls, just return data in JSON format if is_json_api: from .utils import json_response, serialize_obj, mergedicts brand = get_branding(request) return json_response({ "site": { "name": brand["SITE_NAME"], "link": brand["ROOT_URL"], "logo": { "w500": brand["ROOT_URL"] + "/static/branding/" + brand["BRAND_ID"] + "/logo.png", } }, "campaign": mergedicts( serialize_obj(campaign, keys=("id", "created", "updated", "title", "headline", "subhead", "body_text"), render_text_map={ "subhead": "subhead_format", "body_text": "body_format" }), { "link": request.build_absolute_uri(campaign.get_absolute_url()), }), "trigger": mergedicts( serialize_obj( trigger, keys=("id", "created", "updated", "title", "description"), render_text_map={"description": "description_format"}), { "type": trigger.trigger_type.title, "outcomes": outcome_strings, "strings": trigger.trigger_type.strings, "max_split": trigger.max_split(), "desired_outcome": outcome_strings[tcust.outcome] if tcust else None, }) if trigger else None, }) try: pref_outcome = int(request.GET["outcome"]) if pref_outcome < 0 or pref_outcome >= len(outcome_strings): raise ValueError() except: pref_outcome = -1 # it's hard to distinguish 0 from None in templates # render page splash_image_qs = "" try: splash_image_qs += "&blur=" + ( "1" if campaign.extra["style"]["splash"]["blur"] else "") except: pass try: splash_image_qs += "&brightness=" + str( campaign.extra["style"]["splash"]["brightness"] or "") except: pass # a/b testing import random experimental_condition = random.choice([1]) return render( request, "itfsite/campaign_exp%d.html" % experimental_condition, { "campaign": campaign, "splash_image_qs": splash_image_qs, # for contrib.Trigger actions "trigger": trigger, "tcust": tcust, "trigger_outcome_strings": outcome_strings, "pref_outcome": pref_outcome, # for a/b testing "experiment": experimental_condition, })
def user_contribution_details(request): from contrib.models import PledgeExecution # Assemble a table of all line-item transactions. items = [] # Contributions. brand = get_branding( request ) # only looking at contributions made on the site the user is looking at from contrib.models import Contribution contribs = Contribution.objects.filter( pledge_execution__pledge__user=request.user, pledge_execution__pledge__via_campaign__brand=brand['BRAND_INDEX'])\ .select_related('pledge_execution', 'recipient', 'action', 'pledge_execution__trigger_execution__trigger') def contrib_line_item(c): return { 'when': c.pledge_execution.created, 'amount': c.amount, 'recipient': c.name_long(), 'trigger': c.pledge_execution.trigger_execution.trigger, 'campaign': c.pledge_execution.pledge.via_campaign, 'sort': (c.pledge_execution.created, 1, c.recipient.is_challenger, c.id), } items.extend([contrib_line_item(c) for c in contribs]) # Fees. def fees_line_item(p): return { 'when': p.created, 'amount': p.fees, 'recipient': '%s fees' % brand['SITE_NAME'], 'trigger': p.trigger_execution.trigger, 'campaign': p.pledge.via_campaign, 'sort': (p.created, 0), } items.extend([fees_line_item(p) for p in PledgeExecution.objects.filter( pledge__user=request.user, pledge__via_campaign__brand=brand['BRAND_INDEX'])\ .select_related('trigger_execution__trigger')]) # Sort all together. items.sort(key=lambda x: x['sort'], reverse=True) if request.method == 'GET': # GET => HTML return render(request, "itfsite/user_contrib_details.html", { 'items': items, }) else: # POST => CSV from django.http import HttpResponse import csv from io import StringIO buf = StringIO() writer = csv.writer(buf) writer.writerow(['date', 'amount', 'recipient', 'action', 'link']) for item in items: writer.writerow([ item['when'].isoformat(), item['amount'], item['recipient'], item['campaign'].title, item['campaign'].get_short_url(), ]) buf = buf.getvalue().encode('utf8') if True: resp = HttpResponse(buf, content_type="text/csv") resp[ 'Content-Disposition'] = 'attachment; filename="contributions.csv"' else: resp = HttpResponse(buf, content_type="text/plain") resp['Content-Disposition'] = 'inline' resp["Content-Length"] = len(buf) return resp
def user_contribution_details(request): from contrib.models import PledgeExecution # Assemble a table of all line-item transactions. items = [] # Contributions. brand = get_branding(request) # only looking at contributions made on the site the user is looking at from contrib.models import Contribution contribs = Contribution.objects.filter( pledge_execution__pledge__user=request.user, pledge_execution__pledge__via_campaign__brand=brand['BRAND_INDEX'])\ .select_related('pledge_execution', 'recipient', 'action', 'pledge_execution__trigger_execution__trigger') def contrib_line_item(c): return { 'when': c.pledge_execution.created, 'amount': c.amount, 'recipient': c.name_long(), 'trigger': c.pledge_execution.trigger_execution.trigger, 'campaign': c.pledge_execution.pledge.via_campaign, 'sort': (c.pledge_execution.created, 1, c.recipient.is_challenger, c.id), } items.extend([contrib_line_item(c) for c in contribs]) # Fees. def fees_line_item(p): return { 'when': p.created, 'amount': p.fees, 'recipient': '%s fees' % brand['SITE_NAME'], 'trigger': p.trigger_execution.trigger, 'campaign': p.pledge.via_campaign, 'sort': (p.created, 0), } items.extend([fees_line_item(p) for p in PledgeExecution.objects.filter( pledge__user=request.user, pledge__via_campaign__brand=brand['BRAND_INDEX'])\ .select_related('trigger_execution__trigger')]) # Sort all together. items.sort(key = lambda x : x['sort'], reverse=True) if request.method == 'GET': # GET => HTML return render(request, "itfsite/user_contrib_details.html", { 'items': items, }) else: # POST => CSV from django.http import HttpResponse import csv from io import StringIO buf = StringIO() writer = csv.writer(buf) writer.writerow(['date', 'amount', 'recipient', 'action', 'link']) for item in items: writer.writerow([ item['when'].isoformat(), item['amount'], item['recipient'], item['campaign'].title, item['campaign'].get_short_url(), ]) buf = buf.getvalue().encode('utf8') if True: resp = HttpResponse(buf, content_type="text/csv") resp['Content-Disposition'] = 'attachment; filename="contributions.csv"' else: resp = HttpResponse(buf, content_type="text/plain") resp['Content-Disposition'] = 'inline' resp["Content-Length"] = len(buf) return resp
def campaign_show(request, campaign, is_json_api): # What Trigger and TriggerCustomization should we show? trigger, tcust = campaign.get_active_trigger() if trigger: outcome_strings = (tcust or trigger).outcome_strings() for i, os in enumerate(outcome_strings): os["id"] = i else: outcome_strings = [] # for .json calls, just return data in JSON format if is_json_api: from .utils import json_response, serialize_obj, mergedicts brand = get_branding(request) return json_response({ "site": { "name": brand["SITE_NAME"], "link": brand["ROOT_URL"], "logo": { "w500": brand["ROOT_URL"] + "/static/branding/" + brand["BRAND_ID"] + "/logo.png", } }, "campaign": mergedicts( serialize_obj(campaign, keys=("id", "created", "updated", "title", "headline", "subhead", "body_text"), render_text_map={ "subhead": "subhead_format", "body_text": "body_format" }), { "link": request.build_absolute_uri(campaign.get_absolute_url()), }), "trigger": mergedicts( serialize_obj(trigger, keys=("id", "created", "updated", "title", "description"), render_text_map={ "description": "description_format" }), { "type": trigger.trigger_type.title, "outcomes": outcome_strings, "strings": trigger.trigger_type.strings, "max_split": trigger.max_split(), "desired_outcome": outcome_strings[tcust.outcome] if tcust else None, }) if trigger else None, }) try: pref_outcome = int(request.GET["outcome"]) if pref_outcome < 0 or pref_outcome >= len(outcome_strings): raise ValueError() except: pref_outcome = -1 # it's hard to distinguish 0 from None in templates # render page splash_image_qs = "" try: splash_image_qs += "&blur=" + ("1" if campaign.extra["style"]["splash"]["blur"] else "") except: pass try: splash_image_qs += "&brightness=" + str(campaign.extra["style"]["splash"]["brightness"] or "") except: pass # a/b testing import random experimental_condition = random.choice([1]) return render(request, "itfsite/campaign_exp%d.html" % experimental_condition, { "campaign": campaign, "splash_image_qs": splash_image_qs, # for contrib.Trigger actions "trigger": trigger, "tcust": tcust, "trigger_outcome_strings": outcome_strings, "pref_outcome": pref_outcome, # for a/b testing "experiment": experimental_condition, })