Skip to content
Binary file added app/assets/v2/images/emails/link-regular.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/assets/v2/images/offer.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion app/marketing/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .models import (
AccountDeletionRequest, Alumni, EmailEvent, EmailSubscriber, EmailSupressionList, GithubEvent,
GithubOrgToTwitterHandleMapping, Job, Keyword, LeaderboardRank, ManualStat, MarketingCallback, Match, RoundupEmail,
SlackPresence, SlackUser, Stat,
SlackPresence, SlackUser, Stat, UpcomingDate,
)


Expand Down Expand Up @@ -123,6 +123,7 @@ def membership_length_in_days(self, instance):
admin.site.register(Match, MatchAdmin)
admin.site.register(Job, GeneralAdmin)
admin.site.register(ManualStat, GeneralAdmin)
admin.site.register(UpcomingDate, GeneralAdmin)
admin.site.register(Stat, GeneralAdmin)
admin.site.register(Keyword, GeneralAdmin)
admin.site.register(EmailEvent, EmailEventAdmin)
Expand Down
66 changes: 40 additions & 26 deletions app/marketing/mails.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@
from marketing.utils import func_name, get_or_save_email_subscriber, should_suppress_notification_email
from python_http_client.exceptions import HTTPError, UnauthorizedError
from retail.emails import (
render_admin_contact_funder, render_bounty_changed, render_bounty_expire_warning, render_bounty_feedback,
render_bounty_request, render_bounty_startwork_expire_warning, render_bounty_unintersted, render_comment,
render_faucet_rejected, render_faucet_request, render_featured_funded_bounty, render_funder_payout_reminder,
render_funder_stale, render_gdpr_reconsent, render_gdpr_update, render_grant_cancellation_email,
render_grant_update, render_kudos_email, render_match_distribution, render_match_email, render_mention,
render_new_bounty, render_new_bounty_acceptance, render_new_bounty_rejection, render_new_bounty_roundup,
render_new_grant_email, render_new_supporter_email, render_new_work_submission, render_no_applicant_reminder,
render_nth_day_email_campaign, render_quarterly_stats, render_request_amount_email, render_reserved_issue,
render_share_bounty, render_start_work_applicant_about_to_expire, render_start_work_applicant_expired,
render_start_work_approved, render_start_work_new_applicant, render_start_work_rejected,
render_subscription_terminated_email, render_successful_contribution_email, render_support_cancellation_email,
render_tax_report, render_thank_you_for_supporting_email, render_tip_email,
render_unread_notification_email_weekly_roundup, render_wallpost, render_weekly_recap,
email_to_profile, get_notification_count, render_admin_contact_funder, render_bounty_changed,
render_bounty_expire_warning, render_bounty_feedback, render_bounty_request, render_bounty_startwork_expire_warning,
render_bounty_unintersted, render_comment, render_faucet_rejected, render_faucet_request,
render_featured_funded_bounty, render_funder_payout_reminder, render_funder_stale, render_gdpr_reconsent,
render_gdpr_update, render_grant_cancellation_email, render_grant_update, render_kudos_email,
render_match_distribution, render_match_email, render_mention, render_new_bounty, render_new_bounty_acceptance,
render_new_bounty_rejection, render_new_bounty_roundup, render_new_grant_email, render_new_supporter_email,
render_new_work_submission, render_no_applicant_reminder, render_nth_day_email_campaign, render_quarterly_stats,
render_request_amount_email, render_reserved_issue, render_share_bounty,
render_start_work_applicant_about_to_expire, render_start_work_applicant_expired, render_start_work_approved,
render_start_work_new_applicant, render_start_work_rejected, render_subscription_terminated_email,
render_successful_contribution_email, render_support_cancellation_email, render_tax_report,
render_thank_you_for_supporting_email, render_tip_email, render_unread_notification_email_weekly_roundup,
render_wallpost, render_weekly_recap,
)
from sendgrid.helpers.mail import Attachment, Content, Email, Mail, Personalization
from sendgrid.helpers.stats import Category
Expand Down Expand Up @@ -1178,40 +1179,53 @@ def new_bounty_daily(bounties, old_bounties, to_emails=None):
if to_emails is None:
to_emails = []

from marketing.views import quest_of_the_day, upcoming_grant, upcoming_hackathon, latest_activities
from marketing.views import quest_of_the_day, upcoming_grant, upcoming_hackathon, latest_activities, upcoming_dates, upcoming_dates, email_announcements
quest = quest_of_the_day()
grant = upcoming_grant()
hackathon = upcoming_hackathon()
dates = list(upcoming_hackathon()) + list(upcoming_dates())
announcements = email_announcements()

offers = f""
if to_emails:
offers = ""

profile = email_to_profile(to_emails[0])
notifications = get_notification_count(profile, 7, timezone.now())
if notifications:
plural = 's' if notifications > 1 else ''
notifications = f"💬 {notifications} Notification{plural}"
else:
notifications = ''
has_offer = is_email_townsquare_enabled(to_emails[0]) and is_there_an_action_available()
if has_offer:
offers = f"💰1 New Action"
offers = f"⚡️ 1 New Action"

new_bounties = ""
if bounties:
plural_bounties = "Bounties" if len(bounties)>1 else "Bounty"
new_bounties = f"⚡️{len(bounties)} {plural_bounties}"
new_bounties = f"💰{len(bounties)} {plural_bounties}"
elif old_bounties:
plural_old_bounties = "Bounties" if len(old_bounties)>1 else "Bounty"
new_bounties = f"⚡️{len(old_bounties)} {plural_old_bounties}"
new_bounties = f"💰{len(old_bounties)} {plural_old_bounties}"

new_quests = ""
if quest:
new_quests = f"🎯1 Quest"

new_hackathons = ""
if hackathon:
plural_hackathon = "Hackathons" if len(hackathon)>1 else "Hackathon"
new_hackathons = f"🛠️{len(hackathon)} {plural_hackathon}"
new_dates = ""
if dates:
plural_dates = "Events" if len(dates)>1 else "Event"
new_dates = f"🛠📆{len(dates)} {plural_dates}"

new_announcements = ""
if announcements:
plural = "Announcement"
new_announcements = f"📣 1 {plural}"

def comma(a):
return ", " if a and (new_bounties or new_quests or new_hackathons) else ""
return ", " if a and (new_bounties or new_quests or new_dates or new_announcements or notifications) else ""

subject = f"Gitcoin Daily {offers}{comma(offers)}{new_bounties}{comma(new_bounties)}{new_quests}{comma(new_quests)}{new_hackathons}"
subject = f"{notifications}{comma(notifications)}{new_announcements}{comma(new_announcements)}{new_bounties}{comma(new_bounties)}{new_dates}{comma(new_dates)}{new_quests}{comma(new_quests)}{offers}"

for to_email in to_emails:
cur_language = translation.get_language()
Expand All @@ -1220,10 +1234,10 @@ def comma(a):
from_email = settings.CONTACT_EMAIL

from django.contrib.auth.models import User
user = User.objects.get(email__iexact=to_email)
user = User.objects.filter(email__iexact=to_email).first()
activities = latest_activities(user)

html, text = render_new_bounty(to_email, bounties, old_bounties='', quest_of_the_day=quest, upcoming_grant=grant, upcoming_hackathon=hackathon, latest_activities=activities)
html, text = render_new_bounty(to_email, bounties, old_bounties='', quest_of_the_day=quest, upcoming_grant=grant, upcoming_hackathon=upcoming_hackathon(), latest_activities=activities)

if not should_suppress_notification_email(to_email, 'new_bounty_notifications'):
send_mail(from_email, to_email, subject, text, html, categories=['marketing', func_name()])
Expand Down
24 changes: 19 additions & 5 deletions app/marketing/management/commands/new_bounties_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

'''
import logging
import time
import warnings

from django.conf import settings
from django.core.management.base import BaseCommand
Expand All @@ -26,6 +28,9 @@
from marketing.models import EmailSubscriber
from townsquare.utils import is_email_townsquare_enabled

warnings.filterwarnings("ignore")

override_in_dev = True

def get_bounties_for_keywords(keywords, hours_back):
new_bounties_pks = []
Expand Down Expand Up @@ -57,17 +62,20 @@ class Command(BaseCommand):
help = 'sends new_bounty_daily _emails'

def handle(self, *args, **options):
if settings.DEBUG:
if settings.DEBUG and not override_in_dev:
print("not active in non prod environments")
return
hours_back = 24
eses = EmailSubscriber.objects.filter(active=True).distinct('email')
counter_grant_total = 0
counter_total = 0
counter_sent = 0
print("got {} emails".format(eses.count()))
start_time = time.time()
total_count = eses.count()
print("got {} emails".format(total_count))
for es in eses:
try:
# prep
counter_grant_total += 1
to_email = es.email
keywords = es.keywords
Expand All @@ -77,13 +85,19 @@ def handle(self, *args, **options):
continue
counter_total += 1
new_bounties, all_bounties = get_bounties_for_keywords(keywords, hours_back)
print("{}/{}/{}) {}/{}: got {} new bounties & {} all bounties".format(counter_sent, counter_total, counter_grant_total, to_email, keywords, new_bounties.count(), all_bounties.count()))

# stats
speed = round((time.time() - start_time) / counter_grant_total, 2)
ETA = round((total_count - counter_grant_total) / speed / 3600, 1)
print(f"{counter_sent} sent/{counter_total} enabled/{counter_grant_total} evaluated, {speed}/s, ETA:{ETA}h, working on {to_email} ")

# send
should_send = new_bounties.count() or town_square_enabled
#should_send = new_bounties.count()
if should_send:
print(f"sending to {to_email}")
#print(f"sending to {to_email}")
new_bounty_daily(new_bounties, all_bounties, [to_email])
print(f"/sent to {to_email}")
#print(f"/sent to {to_email}")
counter_sent += 1
except Exception as e:
logging.exception(e)
Expand Down
30 changes: 30 additions & 0 deletions app/marketing/migrations/0014_upcomingdate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 2.2.4 on 2020-05-27 18:20

from django.db import migrations, models
import economy.models


class Migration(migrations.Migration):

dependencies = [
('marketing', '0013_auto_20200413_1223'),
]

operations = [
migrations.CreateModel(
name='UpcomingDate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)),
('modified_on', models.DateTimeField(default=economy.models.get_time)),
('title', models.CharField(max_length=255)),
('date', models.DateTimeField(db_index=True)),
('img_url', models.URLField(blank=True, db_index=True)),
('url', models.URLField(db_index=True)),
('comment', models.TextField(blank=True, default='', max_length=255)),
],
options={
'abstract': False,
},
),
]
18 changes: 18 additions & 0 deletions app/marketing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,21 @@ def get_absolute_url(self):

def __str__(self):
return self.subject

class UpcomingDate(SuperModel):
"""Define the upcoming date model"""

title = models.CharField(max_length=255)
date = models.DateTimeField(db_index=True)
img_url = models.URLField(db_index=True, blank=True)
url = models.URLField(db_index=True)
comment = models.TextField(max_length=255, default='', blank=True)

@property
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo in the future, make grants rounds, hackathons, workshops auto create these objects.

def naturaltime(self):
from django.contrib.humanize.templatetags.humanize import naturaltime
return naturaltime(self.date)


def __str__(self):
return f"{self.title}"
4 changes: 2 additions & 2 deletions app/marketing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def get_or_save_email_subscriber(email, source, send_slack_invite=True, profile=
else:
es = EmailSubscriber.objects.create(**defaults)
created = True
print("EmailSubscriber:", es, "- created" if created else "- updated")
#print("EmailSubscriber:", es, "- created" if created else "- updated")
except EmailSubscriber.MultipleObjectsReturned:
email_subscriber_ids = EmailSubscriber.objects.filter(email__iexact=email) \
.values_list('id', flat=True) \
Expand All @@ -241,7 +241,7 @@ def get_or_save_email_subscriber(email, source, send_slack_invite=True, profile=
es = EmailSubscriber.objects.create(**defaults)
created = True
except Exception as e:
print(f'Failed to update or create email subscriber: ({email}) - {e}')
#print(f'Failed to update or create email subscriber: ({email}) - {e}')
return ''

if created or not es.priv:
Expand Down
17 changes: 13 additions & 4 deletions app/marketing/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@
from marketing.country_codes import COUNTRY_CODES, COUNTRY_NAMES, FLAG_API_LINK, FLAG_ERR_MSG, FLAG_SIZE, FLAG_STYLE
from marketing.mails import new_feedback
from marketing.management.commands.new_bounties_email import get_bounties_for_keywords
from marketing.models import AccountDeletionRequest, EmailSubscriber, Keyword, LeaderboardRank
from marketing.models import AccountDeletionRequest, EmailSubscriber, Keyword, LeaderboardRank, UpcomingDate
from marketing.utils import delete_user_from_mailchimp, get_or_save_email_subscriber, validate_slack_integration
from quests.models import Quest
from retail.emails import ALL_EMAILS, render_new_bounty, render_nth_day_email_campaign
from retail.helpers import get_ip
from townsquare.models import Announcement

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -100,6 +101,11 @@ def get_settings_navs(request):

return tabs

def upcoming_dates():
return UpcomingDate.objects.filter(date__gt=timezone.now()).order_by('date')

def email_announcements():
return Announcement.objects.filter(key='founders_note_daily_email', valid_from__lt=timezone.now(), valid_to__gt=timezone.now()).order_by('valid_to').first()

def settings_helper_get_auth(request, key=None):
# setup
Expand Down Expand Up @@ -994,17 +1000,20 @@ def upcoming_grant():

def upcoming_hackathon():
try:
return HackathonEvent.objects.filter(end_date__gt=timezone.now()).order_by('-start_date')
return HackathonEvent.objects.filter(end_date__gt=timezone.now(), visible=True).order_by('-start_date')
except HackathonEvent.DoesNotExist:
try:
return [HackathonEvent.objects.filter(start_date__gte=timezone.now()).order_by('start_date').first()]
return [HackathonEvent.objects.filter(start_date__gte=timezone.now(), visible=True).order_by('start_date').first()]
except HackathonEvent.DoesNotExist:
return None

def latest_activities(user):
from retail.views import get_specific_activities
cutoff_date = timezone.now() - timezone.timedelta(days=7)
from townsquare.tasks import increment_view_counts
cutoff_date = timezone.now() - timezone.timedelta(days=1)
activities = get_specific_activities('connect', 0, user, 0)[:4]
activities_pks = list(activities.values_list('pk', flat=True))
increment_view_counts.delay(activities_pks)
return activities

@staff_member_required
Expand Down
Loading