diff --git a/app/grants/management/commands/sent_cart_reminder.py b/app/grants/management/commands/sent_cart_reminder.py
new file mode 100644
index 00000000000..ffdef57fa7e
--- /dev/null
+++ b/app/grants/management/commands/sent_cart_reminder.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+"""Define the Grant subminer management command.
+
+Copyright (C) 2020 Gitcoin Core
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see
.
+
+"""
+from datetime import datetime
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from django.db.models import Max, F
+from django.utils import timezone
+
+from dashboard.utils import get_tx_status, has_tx_mined
+from grants.clr import predict_clr
+from grants.models import Contribution, Grant, CartActivity, Subscription
+from grants.views import clr_active, round_end, next_round_start
+from marketing.mails import warn_subscription_failed, remember_your_cart
+from townsquare.models import MatchRound
+
+
+class Command(BaseCommand):
+ help = 'Sent reminder to user who forgot its cart '
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--test',
+ default=False,
+ type=bool,
+ help="Only process and display the carts to being delivered"
+ )
+ parser.add_argument(
+ '--full-cart',
+ default=False,
+ type=bool,
+ help="Should the cart being delivered partially"
+ )
+ parser.add_argument(
+ '--hours',
+ type=int,
+ help="Should the cart being delivered partially"
+ )
+
+
+ def handle(self, *args, **options):
+ last_activity_by_user = CartActivity.objects.filter(latest=True, created_on__gt=next_round_start).exclude(metadata=[])
+ count = 0
+ if options.get('hours'):
+ hours = options.get('hours')
+ else:
+ hours = int((round_end - datetime.now()).total_seconds() / 3600)
+
+ for activity in last_activity_by_user:
+ print(activity)
+
+ # Check if this cart is still valid
+ no_checkout_grants = []
+ for grant_entry in activity.metadata:
+ subscription = Subscription.objects.filter(grant_id=grant_entry['grant_id'],
+ contributor_profile=activity.profile,
+ created_on__gt=activity.created_on,
+ created_on__lte=round_end).first()
+
+ if not subscription:
+ no_checkout_grants.append(grant_entry)
+
+ if options['full_cart']:
+ if len(no_checkout_grants) != len(activity.metadata):
+ print(f' * Activity {activity.id}: The grants were partially contributed but no notification will be delivered')
+ continue
+
+ cart_query = [f'{grant["grant_id"]};{grant.get("grant_donation_amount", 5)};{grant.get("token_local_id", 283)}'
+ for grant in no_checkout_grants]
+
+ cart_query = ','.join(cart_query)
+
+ if not cart_query:
+ print(f'** No items left in the {activity.profile}\'s cart')
+ continue
+
+ if not options['test']:
+ try:
+ remember_your_cart(activity.profile, cart_query, no_checkout_grants, hours)
+ count += 1
+ except Exception as e:
+ print(f'!! Failed to sent cart reminder email to {activity.profile}')
+ print(e)
+
+ print(f'\n\nSent {count} emails of {last_activity_by_user.count()} carts')
+
diff --git a/app/grants/templates/grants/detail/tabs.html b/app/grants/templates/grants/detail/tabs.html
index ad946cf7c8e..5f6183bf7a3 100644
--- a/app/grants/templates/grants/detail/tabs.html
+++ b/app/grants/templates/grants/detail/tabs.html
@@ -114,6 +114,9 @@
Grant Sybil Profile
- Suspicious Ethereum Interactions
- Suspicious SMS Address
- Suspicious Account Activity
+ - BrightID Trust Score (coming soon)
+ - Idena Network Trust Score (coming soon)
+ - KYC (maybe coming soon)
a grant's RiskScore ™️ is equal to its SybilScore ™️ * its matching funds for this round.
diff --git a/app/marketing/mails.py b/app/marketing/mails.py
index 7db847e73cf..1280e579e8e 100644
--- a/app/marketing/mails.py
+++ b/app/marketing/mails.py
@@ -45,7 +45,7 @@
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,
+ render_unread_notification_email_weekly_roundup, render_wallpost, render_weekly_recap, render_remember_your_cart,
)
from sendgrid.helpers.mail import Attachment, Content, Email, Mail, Personalization
from sendgrid.helpers.stats import Category
@@ -118,7 +118,7 @@ def send_mail(from_email, _to_email, subject, body, html=False,
mail.add_attachment(attachment)
# debug logs
- logger.info(f"-- Sending Mail '{subject}' to {to_email.email}")
+ logger.info(f"-- Sending Mail '{subject}' to {to_email}")
try:
response = sg.client.mail.send.post(request_body=mail.get())
except UnauthorizedError as e:
@@ -1031,7 +1031,7 @@ def grant_match_distribution_test_txn(match):
coupon = f"Pick up ONE item of Gitcoin Schwag at http://store.gitcoin.co/ at 50% off with coupon code {settings.GRANTS_COUPON_50_OFF}"
if match.amount > 1000:
coupon = f"Pick up ONE item of Gitcoin Schwag at http://store.gitcoin.co/ at 100% off with coupon code {settings.GRANTS_COUPON_100_OFF}"
- # NOTE: IF YOURE A CLEVER BISCUT AND FOUND THIS BY READING OUR CODEBASE,
+ # NOTE: IF YOURE A CLEVER BISCUT AND FOUND THIS BY READING OUR CODEBASE,
# THEN GOOD FOR YOU! HERE IS A 100% OFF COUPON CODE U CAN USE (LIMIT OF 1 FOR THE FIRST PERSON
# TO FIND THIS EASTER EGG) : GRANTS-ROUND-5-HAXXOR
try:
@@ -1090,9 +1090,9 @@ def grant_match_distribution_final_txn(match):
We have sent your {rounded_amount} DAI to the address on file at {match.grant.admin_address}. The txid of this transaction is {match.payout_tx}.
-Congratulations on a successful Gitcoin Grants Round {match.round_number}.
+Congratulations on a successful Gitcoin Grants Round {match.round_number}.
-What now?
+What now?
1. Send a tweet letting us know how these grant funds are being used to support your project (our twitter username is @gitcoin).
2. Remember to update your grantees on what you use the funds for by clicking through to your grant ( https://gitcoin.co{match.grant.get_absolute_url()} ) and posting to your activity feed.
3. Celebrate 🎉 and then get back to BUIDLing something great. 🛠
@@ -1216,7 +1216,7 @@ def new_bounty_daily(bounties, old_bounties, to_emails=None):
bounties = bounties[0:max_bounties]
if to_emails is None:
to_emails = []
-
+
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()
@@ -1258,7 +1258,7 @@ def new_bounty_daily(bounties, old_bounties, to_emails=None):
elif old_bounties:
plural_old_bounties = "Bounties" if len(old_bounties)>1 else "Bounty"
new_bounties = f"💰{len(old_bounties)} {plural_old_bounties}"
-
+
new_quests = ""
if quest:
new_quests = f"🎯1 Quest"
@@ -1551,7 +1551,7 @@ def quarterly_stats(to_emails=None, platform_wide_stats=None):
)
finally:
translation.activate(cur_language)
-
+
def tax_report(to_emails=None, zip_paths=None, tax_year=None):
if to_emails is None:
@@ -1570,18 +1570,18 @@ def tax_report(to_emails=None, zip_paths=None, tax_year=None):
html, text = render_tax_report(to_email, tax_year)
from_email = settings.CONTACT_EMAIL
send_mail(
- from_email,
- to_email,
- subject,
- text,
- html,
+ from_email,
+ to_email,
+ subject,
+ text,
+ html,
from_name="Kevin Owocki (Gitcoin.co)",
categories=['marketing', func_name()],
zip_path=zip_paths[idx]
)
- finally:
+ finally:
translation.activate(cur_language)
-
+
def bounty_expire_warning(bounty, to_emails=None):
if not bounty or not bounty.value_in_usdt_now:
@@ -1903,3 +1903,19 @@ def fund_request_email(request, to_emails, is_new=False):
send_mail(from_email, to_email, subject, text, html, categories=['transactional', func_name()])
finally:
translation.activate(cur_language)
+
+
+def remember_your_cart(profile, cart_query, grants, hours):
+ to_email = profile.email
+ from_email = settings.CONTACT_EMAIL
+
+ cur_language = translation.get_language()
+ try:
+ setup_lang(to_email)
+ subject = f"⏱{hours} hours left 🛒 Your grant cart is waiting for you 🛒"
+ html, text = render_remember_your_cart(cart_query, grants, hours)
+
+ if not should_suppress_notification_email(to_email, 'grant_updates'):
+ send_mail(from_email, to_email, subject, text, html, categories=['marketing', func_name()])
+ finally:
+ translation.activate(cur_language)
diff --git a/app/retail/emails.py b/app/retail/emails.py
index 53d31ad46f1..53122568268 100644
--- a/app/retail/emails.py
+++ b/app/retail/emails.py
@@ -1621,3 +1621,18 @@ def start_work_applicant_expired(request):
bounty = Bounty.objects.last()
response_html, _, _ = render_start_work_applicant_expired(interest, bounty)
return HttpResponse(response_html)
+
+
+
+def render_remember_your_cart(grants_query, grants, hours):
+ params = {
+ 'base_url': settings.BASE_URL,
+ 'desc': f'Only left {hours} hours until the end of the match round and seems you have some grants on your cart',
+ 'cart_query': grants_query,
+ 'grants': grants
+ }
+
+ response_html = premailer_transform(render_to_string("emails/cart.html", params))
+ response_txt = render_to_string("emails/cart.txt", params)
+
+ return response_html, response_txt
diff --git a/app/retail/templates/emails/cart.html b/app/retail/templates/emails/cart.html
new file mode 100644
index 00000000000..82de27b075c
--- /dev/null
+++ b/app/retail/templates/emails/cart.html
@@ -0,0 +1,79 @@
+{% extends 'emails/template.html' %}
+{% comment %}
+ Copyright (C) 2020 Gitcoin Core
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see
.
+{% endcomment %}
+{% load i18n humanize %}
+
+{% block content %}
+
+
+
+
+
{% trans "Your cart is waiting for you" %}
+
+
+
+
+
+
+
+
+
+
+
+
+ You have the following {{grants|length}} grants in your cart:
+
+
+ {% for grant in grants %}
+ - {{ grant.grant_title }} ({{ grant.grant_donation_amount|default:5 }} {{ grant.grant_donation_currency|default:"DAI" }})
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/app/retail/templates/emails/cart.txt b/app/retail/templates/emails/cart.txt
new file mode 100644
index 00000000000..862bda17e64
--- /dev/null
+++ b/app/retail/templates/emails/cart.txt
@@ -0,0 +1,10 @@
+Your cart is waiting for you
+
+{{ desc }}.
+
+Your cart grants:
+{% for grant in grants %}
+ - {{ grant.grant_title }} ({{ grant.grant_donation_amount|default:5 }} {{ grant.grant_donation_currency|default:"DAI" }})
+{% endfor %}
+
+Checkout your cart: {{base_url}}{% url 'grants:grants_bulk_add' cart_query %}