diff --git a/app/app/sitemaps.py b/app/app/sitemaps.py index 2dbc478808d..cd2a4e95c7d 100644 --- a/app/app/sitemaps.py +++ b/app/app/sitemaps.py @@ -87,8 +87,8 @@ class ResultsSitemap(Sitemap): priority = 0.6 def items(self): - from retail.utils import programming_languages_full - return programming_languages_full + from retail.utils import programming_languages + return programming_languages def lastmod(self, obj): from django.utils import timezone diff --git a/app/assets/v2/js/base.js b/app/assets/v2/js/base.js index aea43baa8d4..fa523039621 100644 --- a/app/assets/v2/js/base.js +++ b/app/assets/v2/js/base.js @@ -245,7 +245,7 @@ if (document.contxt.github_handle && !document.contxt.persona_is_funder && !docu
- +

${gettext('Are you a Funder or a Contributor?')}

diff --git a/app/assets/v2/js/pages/bounty_details.js b/app/assets/v2/js/pages/bounty_details.js index def1efb4140..d5e96e2d8a0 100644 --- a/app/assets/v2/js/pages/bounty_details.js +++ b/app/assets/v2/js/pages/bounty_details.js @@ -125,31 +125,32 @@ var callbacks = { }, 'status': function(key, val, result) { let ui_status = val; + let ui_status_raw = val; - if (ui_status === 'open') { + if (ui_status_raw === 'open') { ui_status = '' + gettext('OPEN ISSUE') + ''; let can_submit = result['can_submit_after_expiration_date']; - if (!isBountyOwner && can_submit && is_bounty_expired(result)) { + if (!isBountyOwner() && can_submit && is_bounty_expired(result)) { ui_status += '

' + gettext('This issue is past its expiration date, but it is still active.') + '
' + gettext('Check with the submitter to see if they still want to see it fulfilled.') + '

'; } - } else if (ui_status === 'started') { + } else if (ui_status_raw === 'started') { ui_status = '' + gettext('work started') + ''; - } else if (ui_status === 'submitted') { + } else if (ui_status_raw === 'submitted') { ui_status = '' + gettext('work submitted') + ''; - } else if (ui_status === 'done') { + } else if (ui_status_raw === 'done') { ui_status = '' + gettext('done') + ''; - } else if (ui_status === 'cancelled') { + } else if (ui_status_raw === 'cancelled') { ui_status = '' + gettext('cancelled') + ''; } - if (isBountyOwner && is_bounty_expired(result) && - ui_status !== 'done' && ui_status !== 'cancelled') { + if (isBountyOwner() && is_bounty_expired(result) && + ui_status_raw !== 'done' && ui_status_raw !== 'cancelled') { ui_status += '

' + 'This issue has expired. Click here to extend expiration ' + diff --git a/app/avatar/models.py b/app/avatar/models.py index 91786ba469c..9d8397e82fc 100644 --- a/app/avatar/models.py +++ b/app/avatar/models.py @@ -28,6 +28,8 @@ from django.core.files import File from django.core.files.base import ContentFile from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from economy.models import SuperModel @@ -208,3 +210,11 @@ def github_avatar(cls, profile, avatar_img): avatar.png.save(f'{profile.handle}.png', ContentFile(get_temp_image_file(avatar_img).getvalue()), save=True) avatar.svg = avatar.convert_field(avatar.png, 'png', 'svg') return avatar + + +@receiver(post_save, sender=SocialAvatar, dispatch_uid="psave_avatar") +@receiver(post_save, sender=CustomAvatar, dispatch_uid="psave_avatar2") +def psave_avatar(sender, instance, **kwargs): + from dashboard.models import Activity + metadata = {'url': instance.png.url if getattr(instance, 'png', False) else None, } + Activity.objects.create(profile=instance.profile, activity_type='updated_avatar', metadata=metadata) diff --git a/app/dashboard/migrations/0044_auto_20190729_1817.py b/app/dashboard/migrations/0044_auto_20190729_1817.py new file mode 100644 index 00000000000..dacf26b5c3f --- /dev/null +++ b/app/dashboard/migrations/0044_auto_20190729_1817.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.3 on 2019-07-29 18:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0043_auto_20190724_1507'), + ] + + operations = [ + migrations.AlterField( + model_name='activity', + name='activity_type', + field=models.CharField(blank=True, choices=[('new_bounty', 'New Bounty'), ('start_work', 'Work Started'), ('stop_work', 'Work Stopped'), ('work_submitted', 'Work Submitted'), ('work_done', 'Work Done'), ('worker_approved', 'Worker Approved'), ('worker_rejected', 'Worker Rejected'), ('worker_applied', 'Worker Applied'), ('increased_bounty', 'Increased Funding'), ('killed_bounty', 'Canceled Bounty'), ('new_tip', 'New Tip'), ('receive_tip', 'Tip Received'), ('bounty_abandonment_escalation_to_mods', 'Escalated for Abandonment of Bounty'), ('bounty_abandonment_warning', 'Warning for Abandonment of Bounty'), ('bounty_removed_slashed_by_staff', 'Dinged and Removed from Bounty by Staff'), ('bounty_removed_by_staff', 'Removed from Bounty by Staff'), ('bounty_removed_by_funder', 'Removed from Bounty by Funder'), ('new_crowdfund', 'New Crowdfund Contribution'), ('new_grant', 'New Grant'), ('update_grant', 'Updated Grant'), ('killed_grant', 'Cancelled Grant'), ('new_grant_contribution', 'Contributed to Grant'), ('new_grant_subscription', 'Subscribed to Grant'), ('killed_grant_contribution', 'Cancelled Grant Contribution'), ('new_milestone', 'New Milestone'), ('update_milestone', 'Updated Milestone'), ('new_kudos', 'New Kudos'), ('joined', 'Joined Gitcoin'), ('updated_avatar', 'Updated Avatar')], db_index=True, max_length=50), + ), + ] diff --git a/app/dashboard/models.py b/app/dashboard/models.py index c40bcefa216..7bf98ba3e40 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -1686,6 +1686,7 @@ class Activity(SuperModel): ('update_milestone', 'Updated Milestone'), ('new_kudos', 'New Kudos'), ('joined', 'Joined Gitcoin'), + ('updated_avatar', 'Updated Avatar'), ] profile = models.ForeignKey( diff --git a/app/dashboard/templates/shared/profile_activities_misc.html b/app/dashboard/templates/shared/profile_activities_misc.html index 32067846581..95cae6a9965 100644 --- a/app/dashboard/templates/shared/profile_activities_misc.html +++ b/app/dashboard/templates/shared/profile_activities_misc.html @@ -4,7 +4,11 @@ {% elif activity.subscription.grant.reference_url %} href="{{ activity.subscription.grant.reference_url }}" {% endif %}>

- + {% if activity.metadata.url %} + + {% else %} + + {% endif %}
@@ -13,7 +17,7 @@ {{activity.humanized_activity_type}}
- • {{ activity.created_on | naturaltime }} + {{ activity.created_on | naturaltime }}
diff --git a/app/dashboard/views.py b/app/dashboard/views.py index be574516a93..6a0ca8b1bf9 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -1952,7 +1952,7 @@ def profile(request, handle): handle = handle[:-1] profile = profile_helper(handle, current_user=request.user) - all_activities = ['all', 'new_bounty', 'start_work', 'work_submitted', 'work_done', 'new_tip', 'receive_tip', 'new_grant', 'update_grant', 'killed_grant', 'new_grant_contribution', 'new_grant_subscription', 'killed_grant_contribution', 'receive_kudos', 'new_kudos'] + all_activities = ['all', 'new_bounty', 'start_work', 'work_submitted', 'work_done', 'new_tip', 'receive_tip', 'new_grant', 'update_grant', 'killed_grant', 'new_grant_contribution', 'new_grant_subscription', 'killed_grant_contribution', 'receive_kudos', 'new_kudos', 'joined', 'updated_avatar'] activity_tabs = [ (_('All Activity'), all_activities), (_('Bounties'), ['new_bounty', 'start_work', 'work_submitted', 'work_done']), @@ -2041,7 +2041,7 @@ def profile(request, handle): context['sent_kudos_count'] = sent_kudos.count() context['verification'] = profile.get_my_verified_check context['avg_rating'] = profile.get_average_star_rating - + context['suppress_sumo'] = True context['unrated_funded_bounties'] = Bounty.objects.current().prefetch_related('fulfillments', 'interested', 'interested__profile', 'feedbacks') \ .filter( bounty_owner_github_username__iexact=profile.handle, @@ -2864,7 +2864,10 @@ def change_user_profile_banner(request): try: profile = profile_helper(handle, True) - if request.user.profile.id != profile.id: + is_valid = request.user.profile.id == profile.id + if filename[0:7] != '/static' or filename.split('/')[-1] not in load_files_in_directory('wallpapers'): + is_valid = False + if not is_valid: return JsonResponse( {'error': 'Bad request'}, status=401) diff --git a/app/grants/migrations/0025_donation.py b/app/grants/migrations/0025_donation.py new file mode 100644 index 00000000000..c6b40d07bfb --- /dev/null +++ b/app/grants/migrations/0025_donation.py @@ -0,0 +1,39 @@ +# Generated by Django 2.1.7 on 2019-07-22 13:10 + +from django.db import migrations, models +import django.db.models.deletion +import economy.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dashboard', '0041_auto_20190718_1222'), + ('grants', '0024_auto_20190612_1645'), + ] + + operations = [ + migrations.CreateModel( + name='Donation', + 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)), + ('from_address', models.CharField(default='0x0', help_text="The sender's address.", max_length=255)), + ('to_address', models.CharField(default='0x0', help_text='The destination address.', max_length=255)), + ('token_address', models.CharField(default='0x0', help_text='The token address to be used with the Grant.', max_length=255)), + ('token_symbol', models.CharField(default='', help_text="The donation token's symbol.", max_length=255)), + ('token_amount', models.DecimalField(decimal_places=18, default=0, help_text='The donation amount in tokens.', max_digits=64)), + ('token_amount_usdt', models.DecimalField(decimal_places=4, default=0, help_text='The donation amount converted to USDT/DAI at the moment of donation.', max_digits=50)), + ('tx_id', models.CharField(default='0x0', help_text='The transaction ID of the Contribution.', max_length=255)), + ('network', models.CharField(default='mainnet', help_text='The network in which the Subscription resides.', max_length=8)), + ('donation_percentage', models.DecimalField(decimal_places=2, default=0, help_text='The additional percentage selected when the donation is made', max_digits=5)), + ('contribution', models.ForeignKey(help_text='The contribution that this donation was a part of.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='donation', to='grants.Contribution')), + ('profile', models.ForeignKey(help_text="The donator's profile.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='donations', to='dashboard.Profile')), + ('subscription', models.ForeignKey(help_text='The recurring subscription that this donation originated from.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='donations', to='grants.Subscription')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/grants/models.py b/app/grants/models.py index 721bd68525e..f3eb7ca9793 100644 --- a/app/grants/models.py +++ b/app/grants/models.py @@ -734,6 +734,94 @@ def psave_grant(sender, instance, **kwargs): #print("-", subscription.id, value_usdt, instance.monthly_amount_subscribed ) +class DonationQuerySet(models.QuerySet): + """Define the Contribution default queryset and manager.""" + + pass + + +class Donation(SuperModel): + """Define the structure of an optional donation. These donations are + additional funds sent to Gitcoin as part of contributing or subscribing + to a grant.""" + + from_address = models.CharField( + max_length=255, + default='0x0', + help_text=_("The sender's address."), + ) + to_address = models.CharField( + max_length=255, + default='0x0', + help_text=_("The destination address."), + ) + profile = models.ForeignKey( + 'dashboard.Profile', + related_name='donations', + on_delete=models.SET_NULL, + help_text=_("The donator's profile."), + null=True, + ) + token_address = models.CharField( + max_length=255, + default='0x0', + help_text=_('The token address to be used with the Grant.'), + ) + token_symbol = models.CharField( + max_length=255, + default='', + help_text=_("The donation token's symbol."), + ) + token_amount = models.DecimalField( + default=0, + decimal_places=18, + max_digits=64, + help_text=_('The donation amount in tokens.'), + ) + token_amount_usdt = models.DecimalField( + default=0, + decimal_places=4, + max_digits=50, + help_text=_('The donation amount converted to USDT/DAI at the moment of donation.'), + ) + tx_id = models.CharField( + max_length=255, + default='0x0', + help_text=_('The transaction ID of the Contribution.'), + ) + network = models.CharField( + max_length=8, + default='mainnet', + help_text=_('The network in which the Subscription resides.'), + ) + donation_percentage = models.DecimalField( + default=0, + decimal_places=2, + max_digits=5, + help_text=_('The additional percentage selected when the donation is made'), + ) + subscription = models.ForeignKey( + 'grants.subscription', + related_name='donations', + on_delete=models.SET_NULL, + help_text=_("The recurring subscription that this donation originated from."), + null=True, + ) + contribution = models.ForeignKey( + 'grants.contribution', + related_name='donation', + on_delete=models.SET_NULL, + help_text=_("The contribution that this donation was a part of."), + null=True, + ) + + + def __str__(self): + """Return the string representation of this object.""" + from django.contrib.humanize.templatetags.humanize import naturaltime + return f"id: {self.pk}; from:{profile.handle}; {tx_id} => ${token_amount_usdt}; {naturaltime(self.created_on)}" + + class ContributionQuerySet(models.QuerySet): """Define the Contribution default queryset and manager.""" diff --git a/app/kudos/views.py b/app/kudos/views.py index 243c038a4ae..7744ed2072d 100644 --- a/app/kudos/views.py +++ b/app/kudos/views.py @@ -616,6 +616,87 @@ def receive(request, key, txid, network): return TemplateResponse(request, 'transaction/receive.html', params) +def redeem_bulk_coupon(coupon, profile, address, ip_address, save_addr=False): + try: + address = Web3.toChecksumAddress(address) + except: + error = "You must enter a valid Ethereum address (so we know where to send your Kudos). Please try again." + + # handle form submission + kudos_transfer = None + if save_addr: + profile.preferred_payout_address = address + profile.save() + + private_key = settings.KUDOS_PRIVATE_KEY if not coupon.sender_pk else coupon.sender_pk + kudos_owner_address = settings.KUDOS_OWNER_ACCOUNT if not coupon.sender_address else coupon.sender_address + gas_price_confirmation_time = 2 if not coupon.sender_address else 60 + kudos_contract_address = Web3.toChecksumAddress(settings.KUDOS_CONTRACT_MAINNET) + kudos_owner_address = Web3.toChecksumAddress(kudos_owner_address) + w3 = get_web3(coupon.token.contract.network) + contract = w3.eth.contract(Web3.toChecksumAddress(kudos_contract_address), abi=kudos_abi()) + nonce = w3.eth.getTransactionCount(kudos_owner_address) + tx = contract.functions.clone(address, coupon.token.token_id, 1).buildTransaction({ + 'nonce': nonce, + 'gas': 500000, + 'gasPrice': int(recommend_min_gas_price_to_confirm_in_time(gas_price_confirmation_time) * 10**9), + 'value': int(coupon.token.price_finney / 1000.0 * 10**18), + }) + + if not profile.trust_profile and profile.github_created_on > (timezone.now() - timezone.timedelta(days=7)): + error = f'Your github profile is too new. Cannot receive kudos.' + return None, error, None + else: + + signed = w3.eth.account.signTransaction(tx, private_key) + try: + txid = w3.eth.sendRawTransaction(signed.rawTransaction).hex() + + with transaction.atomic(): + kudos_transfer = KudosTransfer.objects.create( + emails=[profile.email], + # For kudos, `token` is a kudos.models.Token instance. + kudos_token_cloned_from=coupon.token, + amount=coupon.token.price_in_eth, + comments_public=coupon.comments_to_put_in_kudos_transfer, + ip=ip_address, + github_url='', + from_name=coupon.sender_profile.handle, + from_email='', + from_username=coupon.sender_profile.handle, + username=profile.handle, + network=coupon.token.contract.network, + from_address=kudos_owner_address, + is_for_bounty_fulfiller=False, + metadata={'coupon_redemption': True, 'nonce': nonce}, + recipient_profile=profile, + sender_profile=coupon.sender_profile, + txid=txid, + receive_txid=txid, + tx_status='pending', + receive_tx_status='pending', + ) + + # save to DB + BulkTransferRedemption.objects.create( + coupon=coupon, + redeemed_by=profile, + ip_address=ip_address, + kudostransfer=kudos_transfer, + ) + + coupon.num_uses_remaining -= 1 + coupon.current_uses += 1 + coupon.save() + + # send email + maybe_market_kudos_to_email(kudos_transfer) + except Exception as e: + logger.exception(e) + error = "Could not redeem your kudos. Please try again soon." + return None, error, None + + return True, None, kudos_transfer @ratelimit(key='ip', rate='10/m', method=ratelimit.UNSAFE, block=True) def receive_bulk(request, secret): @@ -638,90 +719,12 @@ def receive_bulk(request, secret): error = False if request.POST: - try: - address = Web3.toChecksumAddress(request.POST.get('forwarding_address')) - except: - error = "You must enter a valid Ethereum address (so we know where to send your Kudos). Please try again." if request.user.is_anonymous: error = "You must login." - if not error: - user = request.user - profile = user.profile - save_addr = request.POST.get('save_addr') - ip_address = get_ip(request) - - # handle form submission - if save_addr: - profile.preferred_payout_address = address - profile.save() - - private_key = settings.KUDOS_PRIVATE_KEY if not coupon.sender_pk else coupon.sender_pk - kudos_owner_address = settings.KUDOS_OWNER_ACCOUNT if not coupon.sender_address else coupon.sender_address - gas_price_confirmation_time = 2 if not coupon.sender_address else 60 - kudos_contract_address = Web3.toChecksumAddress(settings.KUDOS_CONTRACT_MAINNET) - kudos_owner_address = Web3.toChecksumAddress(kudos_owner_address) - w3 = get_web3(coupon.token.contract.network) - contract = w3.eth.contract(Web3.toChecksumAddress(kudos_contract_address), abi=kudos_abi()) - nonce = w3.eth.getTransactionCount(kudos_owner_address) - tx = contract.functions.clone(address, coupon.token.token_id, 1).buildTransaction({ - 'nonce': nonce, - 'gas': 500000, - 'gasPrice': int(recommend_min_gas_price_to_confirm_in_time(gas_price_confirmation_time) * 10**9), - 'value': int(coupon.token.price_finney / 1000.0 * 10**18), - }) - - if not profile.trust_profile and profile.github_created_on > (timezone.now() - timezone.timedelta(days=7)): - messages.error(request, f'Your github profile is too new. Cannot receive kudos.') - else: - - signed = w3.eth.account.signTransaction(tx, private_key) - try: - txid = w3.eth.sendRawTransaction(signed.rawTransaction).hex() - - with transaction.atomic(): - kudos_transfer = KudosTransfer.objects.create( - emails=[request.user.email], - # For kudos, `token` is a kudos.models.Token instance. - kudos_token_cloned_from=coupon.token, - amount=coupon.token.price_in_eth, - comments_public=coupon.comments_to_put_in_kudos_transfer, - ip=ip_address, - github_url='', - from_name=coupon.sender_profile.handle, - from_email='', - from_username=coupon.sender_profile.handle, - username=profile.handle, - network=coupon.token.contract.network, - from_address=kudos_owner_address, - is_for_bounty_fulfiller=False, - metadata={'coupon_redemption': True, 'nonce': nonce}, - recipient_profile=profile, - sender_profile=coupon.sender_profile, - txid=txid, - receive_txid=txid, - tx_status='pending', - receive_tx_status='pending', - ) - - # save to DB - BulkTransferRedemption.objects.create( - coupon=coupon, - redeemed_by=profile, - ip_address=ip_address, - kudostransfer=kudos_transfer, - ) - - coupon.num_uses_remaining -= 1 - coupon.current_uses += 1 - coupon.save() - - # send email - maybe_market_kudos_to_email(kudos_transfer) - except Exception as e: - logger.exception(e) - error = "Could not redeem your kudos. Please try again soon." - + success, error = redeem_bulk_coupon(coupon, request.user.profile, request.POST.get('forwarding_address'), get_ip(request), request.POST.get('save_addr')) + if error: + messages.error(request, error) title = f"Redeem {coupon.token.humanized_name} Kudos from @{coupon.sender_profile.handle}" desc = f"This Kudos has been AirDropped to you. About this Kudos: {coupon.token.description}" diff --git a/app/marketing/tests/management/commands/test_assemble_leaderboards.py b/app/marketing/tests/management/commands/test_assemble_leaderboards.py index fa95dc413c3..10408192891 100644 --- a/app/marketing/tests/management/commands/test_assemble_leaderboards.py +++ b/app/marketing/tests/management/commands/test_assemble_leaderboards.py @@ -158,9 +158,13 @@ def test_bounty_index_terms(self): assert len(index_terms) == 15 assert 'USDT' in index_terms assert {self.bounty_payer_handle, self.bounty_earner_handle, 'gitcoinco'}.issubset(set(index_terms)) + ''' + these asserts are not worth testing as they break every time the + underlying geoip data gets updated assert {'Tallmadge', 'United States', 'North America'}.issubset(set(index_terms)) assert {'London', 'United Kingdom', 'Europe'}.issubset(set(index_terms)) assert {'Australia', 'Oceania'}.issubset(set(index_terms)) + ''' assert {'python', 'shell'}.issubset(set(index_terms)) def test_tip_index_terms(self): @@ -170,8 +174,12 @@ def test_tip_index_terms(self): assert len(index_terms) == 10 assert 'USDT' in index_terms assert {self.tip_payer_handle, self.tip_earner_handle, 'gitcoinco'}.issubset(set(index_terms)) + ''' + these asserts are not worth testing as they break every time the + underlying geoip data gets updated assert {'Tallmadge', 'United States', 'North America'}.issubset(set(index_terms)) assert {'London', 'United Kingdom', 'Europe'}.issubset(set(index_terms)) + ''' def test_sum_bounties_payer(self): """Test sum bounties leaderboards.""" diff --git a/app/marketing/utils.py b/app/marketing/utils.py index b82fd5c3560..04ba38dea68 100644 --- a/app/marketing/utils.py +++ b/app/marketing/utils.py @@ -39,11 +39,11 @@ def delete_user_from_mailchimp(email_address): result = None try: result = client.search_members.get(query=email_address) + if result: + subscriber_hash = result.get('exact_matches', {}).get('members', [{}])[0].get('id', None) except Exception as e: logger.debug(e) - if result: - subscriber_hash = result['exact_matches']['members'][0]['id'] try: client.lists.members.delete( diff --git a/app/marketing/views.py b/app/marketing/views.py index 9078979c148..f4396253614 100644 --- a/app/marketing/views.py +++ b/app/marketing/views.py @@ -724,7 +724,7 @@ def leaderboard(request, key=''): else: amount_max = 0 - profile_keys = ['_tokens', '_keywords', '_cities', '_countries', '_continents'] + profile_keys = ['tokens', 'keywords', 'cities', 'countries', 'continents'] is_linked_to_profile = any(sub in key for sub in profile_keys) cadence_ui = cadence if cadence != 'all' else 'All-Time' diff --git a/app/retail/templates/shared/activity.html b/app/retail/templates/shared/activity.html index af0e3038d43..30950ac55ca 100644 --- a/app/retail/templates/shared/activity.html +++ b/app/retail/templates/shared/activity.html @@ -23,6 +23,9 @@ {% if row.metadata.to_username %} + {% elif row.metadata.url %} + {% elif row.metadata.grant_logo %} {% endif %} @@ -87,6 +90,8 @@ {% trans "canceled bounty: " %}{{ row.urled_title | safe }} {% elif row.activity_type == 'increased_bounty' %} {% trans "increased funding: " %}{{ row.urled_title | safe }} + {% elif row.activity_type == 'updated_avatar' %} + {% trans "updated their avatar" %} {% elif row.activity_type == 'unknown_event' %} {% trans "made an update to: " %}{{ row.urled_title | safe }} {% else %} diff --git a/app/retail/templates/shared/nav.html b/app/retail/templates/shared/nav.html index 0c533daa997..a0c7bf7e918 100644 --- a/app/retail/templates/shared/nav.html +++ b/app/retail/templates/shared/nav.html @@ -51,7 +51,7 @@ {% trans "Bounties" %} {% trans "Users" %} {% trans "Leaderboard" %} - {% trans "Activity Feed" %} + {% trans "Activity Stream" %}
diff --git a/scripts/debug/resubmit_kudos_bulk_redeem.py b/scripts/debug/resubmit_kudos_bulk_redeem.py new file mode 100644 index 00000000000..0d1ab849d6a --- /dev/null +++ b/scripts/debug/resubmit_kudos_bulk_redeem.py @@ -0,0 +1,21 @@ +#https://twitter.com/anettrolikova/status/1145038336140161024?s=12 + +from dashboard.models import Profile +from kudos.models import BulkTransferCoupon +from kudos.views import redeem_bulk_coupon + +handle = 'AnettRolikova' +secret = 'evil_genius_bot_2019' +address = None + +ip_address = '0.0.0.0' +profile = Profile.objects.get(handle=handle) +coupon = BulkTransferCoupon.objects.get(secret=secret) +address = profile.preferred_payout_address +if not address: + raise Exception("need preferred_payout_address") + +success, error, kudos_transfer = redeem_bulk_coupon(coupon, profile, address, ip_address, save_addr=False) + +print(success) +print(kudos_transfer.txid)